apisonator 2.100.2 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +36 -0
- data/Gemfile.base +0 -1
- data/Gemfile.lock +7 -21
- data/Gemfile.on_prem.lock +7 -21
- data/lib/3scale/backend.rb +0 -1
- data/lib/3scale/backend/application.rb +22 -6
- data/lib/3scale/backend/configuration.rb +2 -3
- data/lib/3scale/backend/errors.rb +0 -36
- data/lib/3scale/backend/listener.rb +2 -83
- data/lib/3scale/backend/metric/collection.rb +1 -4
- data/lib/3scale/backend/storage_async/client.rb +3 -1
- data/lib/3scale/backend/storage_helpers.rb +7 -2
- data/lib/3scale/backend/transactor.rb +27 -14
- data/lib/3scale/backend/transactor/report_job.rb +1 -1
- data/lib/3scale/backend/transactor/status.rb +0 -5
- data/lib/3scale/backend/usage_limit.rb +20 -11
- data/lib/3scale/backend/version.rb +1 -1
- data/licenses.xml +6 -66
- metadata +2 -9
- data/lib/3scale/backend/oauth.rb +0 -4
- data/lib/3scale/backend/oauth/token.rb +0 -26
- data/lib/3scale/backend/oauth/token_key.rb +0 -30
- data/lib/3scale/backend/oauth/token_storage.rb +0 -313
- data/lib/3scale/backend/oauth/token_value.rb +0 -25
- data/lib/3scale/backend/views/oauth_access_tokens.builder +0 -14
- data/lib/3scale/backend/views/oauth_app_id_by_token.builder +0 -4
@@ -75,10 +75,7 @@ module ThreeScale
|
|
75
75
|
end
|
76
76
|
|
77
77
|
def load_metric_id(name)
|
78
|
-
|
79
|
-
:load_metric_id, @service_id, name)) do
|
80
|
-
storage.get(encode_key("metric/service_id:#{@service_id}/name:#{name}/id"))
|
81
|
-
end || raise(MetricInvalid.new(name))
|
78
|
+
Metric.load_id(@service_id, name) || raise(MetricInvalid.new(name))
|
82
79
|
end
|
83
80
|
|
84
81
|
## accepts postive integers or positive integers preffixed with # (for sets)
|
@@ -46,7 +46,9 @@ module ThreeScale
|
|
46
46
|
port ||= DEFAULT_PORT
|
47
47
|
|
48
48
|
endpoint = Async::IO::Endpoint.tcp(host, port)
|
49
|
-
@redis_async = Async::Redis::Client.new(
|
49
|
+
@redis_async = Async::Redis::Client.new(
|
50
|
+
endpoint, connection_limit: opts[:max_connections]
|
51
|
+
)
|
50
52
|
@building_pipeline = false
|
51
53
|
end
|
52
54
|
|
@@ -52,13 +52,18 @@ module ThreeScale
|
|
52
52
|
# duplicated commands because of this setting.
|
53
53
|
reconnect_attempts: 1,
|
54
54
|
# use by default the C extension client
|
55
|
-
driver: :hiredis
|
55
|
+
driver: :hiredis,
|
56
|
+
# applies only to async mode. The sync library opens 1 connection
|
57
|
+
# per process.
|
58
|
+
max_connections: 10,
|
56
59
|
}.freeze
|
57
60
|
private_constant :CONN_OPTIONS
|
58
61
|
|
59
62
|
# CONN_WHITELIST - Connection options that can be specified in config
|
60
63
|
# Note: we don't expose reconnect_attempts until the bug above is fixed
|
61
|
-
CONN_WHITELIST = [
|
64
|
+
CONN_WHITELIST = [
|
65
|
+
:connect_timeout, :read_timeout, :write_timeout, :max_connections
|
66
|
+
].freeze
|
62
67
|
private_constant :CONN_WHITELIST
|
63
68
|
|
64
69
|
# Parameters regarding target server we will take from a config object
|
@@ -65,19 +65,7 @@ module ThreeScale
|
|
65
65
|
params[:app_id] = nil if app_id && app_id.empty?
|
66
66
|
|
67
67
|
if oauth
|
68
|
-
if app_id.nil?
|
69
|
-
access_token = params[:access_token]
|
70
|
-
access_token = nil if access_token && access_token.empty?
|
71
|
-
|
72
|
-
if access_token.nil?
|
73
|
-
raise ApplicationNotFound.new nil if app_id.nil?
|
74
|
-
else
|
75
|
-
app_id = get_token_ids(access_token, service_id, app_id)
|
76
|
-
# update params, since they are checked elsewhere
|
77
|
-
params[:app_id] = app_id
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
68
|
+
raise ApplicationNotFound.new nil if app_id.nil?
|
81
69
|
validators = Validators::OAUTH_VALIDATORS
|
82
70
|
else
|
83
71
|
validators = Validators::VALIDATORS
|
@@ -87,9 +75,13 @@ module ThreeScale
|
|
87
75
|
application = Application.load_by_id_or_user_key!(service_id,
|
88
76
|
app_id,
|
89
77
|
params[:user_key])
|
78
|
+
|
79
|
+
extensions = request_info && request_info[:extensions] || {}
|
80
|
+
|
81
|
+
preload_usage_limits(application, extensions[:no_body], params[:usage])
|
82
|
+
|
90
83
|
now = Time.now.getutc
|
91
84
|
usage_values = Usage.application_usage(application, now)
|
92
|
-
extensions = request_info && request_info[:extensions] || {}
|
93
85
|
status_attrs = {
|
94
86
|
service_id: service_id,
|
95
87
|
application: application,
|
@@ -169,6 +161,27 @@ module ThreeScale
|
|
169
161
|
Resque.enqueue(ReportJob, service_id, data, Time.now.getutc.to_f, context_info)
|
170
162
|
end
|
171
163
|
|
164
|
+
# Loads the usage limits that are needed to authorize the current request.
|
165
|
+
# These are the cases:
|
166
|
+
# - When no_body is enabled, we only need to load the limits related with
|
167
|
+
# the metrics included in the request, that is, the ones included plus all
|
168
|
+
# their ancestors in the hierarchy. That's all we need to verify if the
|
169
|
+
# request is within the limits defined.
|
170
|
+
# - When no_body is disabled, we need to load all the limits because they
|
171
|
+
# are needed to generate the XML response.
|
172
|
+
# - When the usage reported in the request is empty, apisonator returns
|
173
|
+
# "limits_exceeded" if any of the limits defined has been violated. That's
|
174
|
+
# why in that scenario we need to load all the limits.
|
175
|
+
def preload_usage_limits(application, no_body_enabled, usage_in_params)
|
176
|
+
metric_names_in_usage = usage_in_params&.keys || {}
|
177
|
+
|
178
|
+
if no_body_enabled && !metric_names_in_usage.empty?
|
179
|
+
application.load_usage_limits_affected_by(metric_names_in_usage)
|
180
|
+
else
|
181
|
+
application.load_all_usage_limits
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
172
185
|
def storage
|
173
186
|
Storage.instance
|
174
187
|
end
|
@@ -59,7 +59,7 @@ module ThreeScale
|
|
59
59
|
return [] if transactions.empty?
|
60
60
|
transactions = transactions.values if transactions.respond_to?(:values)
|
61
61
|
transactions.group_by do |transaction|
|
62
|
-
Application.extract_id!(service_id, transaction['app_id'], transaction['user_key']
|
62
|
+
Application.extract_id!(service_id, transaction['app_id'], transaction['user_key'])
|
63
63
|
end.each(&block)
|
64
64
|
end
|
65
65
|
|
@@ -80,11 +80,6 @@ module ThreeScale
|
|
80
80
|
values && values[usage_limit.metric_id] || 0
|
81
81
|
end
|
82
82
|
|
83
|
-
def value_for_application_usage_limit(usage_limit)
|
84
|
-
values = @values[usage_limit.period]
|
85
|
-
values && values[usage_limit.metric_id] || 0
|
86
|
-
end
|
87
|
-
|
88
83
|
# provides a hierarchy hash with metrics as symbolic names
|
89
84
|
def hierarchy
|
90
85
|
@hierarchy ||= Metric.hierarchy service_id
|
@@ -22,20 +22,15 @@ module ThreeScale
|
|
22
22
|
|
23
23
|
def load_all(service_id, plan_id)
|
24
24
|
metric_ids = Metric.load_all_ids(service_id)
|
25
|
-
|
26
|
-
|
27
|
-
results = []
|
28
|
-
with_pairs_and_values service_id, plan_id, metric_ids do |pair, value|
|
29
|
-
value and results << new(service_id: service_id,
|
30
|
-
plan_id: plan_id,
|
31
|
-
metric_id: pair[0],
|
32
|
-
period: pair[1],
|
33
|
-
value: value.to_i)
|
34
|
-
end
|
35
|
-
results
|
25
|
+
generate_for_metrics(service_id, plan_id, metric_ids)
|
36
26
|
end
|
37
27
|
memoize :load_all
|
38
28
|
|
29
|
+
def load_for_affecting_metrics(service_id, plan_id, metric_ids)
|
30
|
+
generate_for_metrics(service_id, plan_id, metric_ids)
|
31
|
+
end
|
32
|
+
memoize :load_for_affecting_metrics
|
33
|
+
|
39
34
|
def load_value(service_id, plan_id, metric_id, period)
|
40
35
|
raw_value = storage.get(key(service_id, plan_id, metric_id, period))
|
41
36
|
raw_value and raw_value.to_i
|
@@ -80,6 +75,20 @@ module ThreeScale
|
|
80
75
|
encode_key(key_pre + period.to_s)
|
81
76
|
end
|
82
77
|
|
78
|
+
def generate_for_metrics(service_id, plan_id, metric_ids)
|
79
|
+
return metric_ids if metric_ids.empty?
|
80
|
+
|
81
|
+
results = []
|
82
|
+
with_pairs_and_values service_id, plan_id, metric_ids do |pair, value|
|
83
|
+
value and results << new(service_id: service_id,
|
84
|
+
plan_id: plan_id,
|
85
|
+
metric_id: pair[0],
|
86
|
+
period: pair[1],
|
87
|
+
value: value.to_i)
|
88
|
+
end
|
89
|
+
results
|
90
|
+
end
|
91
|
+
|
83
92
|
# yields [pair(metric_id, period), value]
|
84
93
|
def with_pairs_and_values(service_id, plan_id, metric_ids, &blk)
|
85
94
|
pairs, values = get_pairs_and_values_for service_id, plan_id, metric_ids
|
data/licenses.xml
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
<dependencies>
|
4
4
|
<dependency>
|
5
5
|
<packageName>activesupport</packageName>
|
6
|
-
<version>5.
|
6
|
+
<version>5.2.4.3</version>
|
7
7
|
<licenses>
|
8
8
|
<license>
|
9
9
|
<name>MIT</name>
|
@@ -23,7 +23,7 @@
|
|
23
23
|
</dependency>
|
24
24
|
<dependency>
|
25
25
|
<packageName>apisonator</packageName>
|
26
|
-
<version>
|
26
|
+
<version>3.0.0</version>
|
27
27
|
<licenses>
|
28
28
|
<license>
|
29
29
|
<name>Apache 2.0</name>
|
@@ -243,7 +243,7 @@
|
|
243
243
|
</dependency>
|
244
244
|
<dependency>
|
245
245
|
<packageName>concurrent-ruby</packageName>
|
246
|
-
<version>1.
|
246
|
+
<version>1.1.6</version>
|
247
247
|
<licenses>
|
248
248
|
<license>
|
249
249
|
<name>MIT</name>
|
@@ -318,16 +318,6 @@
|
|
318
318
|
<url>http://opensource.org/licenses/mit-license</url>
|
319
319
|
</license>
|
320
320
|
</licenses>
|
321
|
-
</dependency>
|
322
|
-
<dependency>
|
323
|
-
<packageName>faraday</packageName>
|
324
|
-
<version>0.13.1</version>
|
325
|
-
<licenses>
|
326
|
-
<license>
|
327
|
-
<name>MIT</name>
|
328
|
-
<url>http://opensource.org/licenses/mit-license</url>
|
329
|
-
</license>
|
330
|
-
</licenses>
|
331
321
|
</dependency>
|
332
322
|
<dependency>
|
333
323
|
<packageName>ffi</packageName>
|
@@ -338,16 +328,6 @@
|
|
338
328
|
<url>http://opensource.org/licenses/BSD-3-Clause</url>
|
339
329
|
</license>
|
340
330
|
</licenses>
|
341
|
-
</dependency>
|
342
|
-
<dependency>
|
343
|
-
<packageName>geminabox</packageName>
|
344
|
-
<version>0.13.11</version>
|
345
|
-
<licenses>
|
346
|
-
<license>
|
347
|
-
<name>MIT-LICENSE</name>
|
348
|
-
<url></url>
|
349
|
-
</license>
|
350
|
-
</licenses>
|
351
331
|
</dependency>
|
352
332
|
<dependency>
|
353
333
|
<packageName>gli</packageName>
|
@@ -368,20 +348,10 @@
|
|
368
348
|
<url>http://opensource.org/licenses/BSD-3-Clause</url>
|
369
349
|
</license>
|
370
350
|
</licenses>
|
371
|
-
</dependency>
|
372
|
-
<dependency>
|
373
|
-
<packageName>httpclient</packageName>
|
374
|
-
<version>2.8.3</version>
|
375
|
-
<licenses>
|
376
|
-
<license>
|
377
|
-
<name>ruby</name>
|
378
|
-
<url>http://www.ruby-lang.org/en/LICENSE.txt</url>
|
379
|
-
</license>
|
380
|
-
</licenses>
|
381
351
|
</dependency>
|
382
352
|
<dependency>
|
383
353
|
<packageName>i18n</packageName>
|
384
|
-
<version>
|
354
|
+
<version>1.8.2</version>
|
385
355
|
<licenses>
|
386
356
|
<license>
|
387
357
|
<name>MIT</name>
|
@@ -471,7 +441,7 @@
|
|
471
441
|
</dependency>
|
472
442
|
<dependency>
|
473
443
|
<packageName>minitest</packageName>
|
474
|
-
<version>5.
|
444
|
+
<version>5.14.1</version>
|
475
445
|
<licenses>
|
476
446
|
<license>
|
477
447
|
<name>MIT</name>
|
@@ -512,16 +482,6 @@
|
|
512
482
|
<url>http://opensource.org/licenses/mit-license</url>
|
513
483
|
</license>
|
514
484
|
</licenses>
|
515
|
-
</dependency>
|
516
|
-
<dependency>
|
517
|
-
<packageName>multipart-post</packageName>
|
518
|
-
<version>2.0.0</version>
|
519
|
-
<licenses>
|
520
|
-
<license>
|
521
|
-
<name>MIT</name>
|
522
|
-
<url>http://opensource.org/licenses/mit-license</url>
|
523
|
-
</license>
|
524
|
-
</licenses>
|
525
485
|
</dependency>
|
526
486
|
<dependency>
|
527
487
|
<packageName>mustache</packageName>
|
@@ -542,16 +502,6 @@
|
|
542
502
|
<url>http://opensource.org/licenses/mit-license</url>
|
543
503
|
</license>
|
544
504
|
</licenses>
|
545
|
-
</dependency>
|
546
|
-
<dependency>
|
547
|
-
<packageName>nesty</packageName>
|
548
|
-
<version>1.0.2</version>
|
549
|
-
<licenses>
|
550
|
-
<license>
|
551
|
-
<name>MIT</name>
|
552
|
-
<url>http://opensource.org/licenses/mit-license</url>
|
553
|
-
</license>
|
554
|
-
</licenses>
|
555
505
|
</dependency>
|
556
506
|
<dependency>
|
557
507
|
<packageName>net-scp</packageName>
|
@@ -826,16 +776,6 @@
|
|
826
776
|
<url>http://opensource.org/licenses/mit-license</url>
|
827
777
|
</license>
|
828
778
|
</licenses>
|
829
|
-
</dependency>
|
830
|
-
<dependency>
|
831
|
-
<packageName>reentrant_flock</packageName>
|
832
|
-
<version>0.1.1</version>
|
833
|
-
<licenses>
|
834
|
-
<license>
|
835
|
-
<name>MIT</name>
|
836
|
-
<url>http://opensource.org/licenses/mit-license</url>
|
837
|
-
</license>
|
838
|
-
</licenses>
|
839
779
|
</dependency>
|
840
780
|
<dependency>
|
841
781
|
<packageName>resque</packageName>
|
@@ -1123,7 +1063,7 @@
|
|
1123
1063
|
</dependency>
|
1124
1064
|
<dependency>
|
1125
1065
|
<packageName>tzinfo</packageName>
|
1126
|
-
<version>1.2.
|
1066
|
+
<version>1.2.7</version>
|
1127
1067
|
<licenses>
|
1128
1068
|
<license>
|
1129
1069
|
<name>MIT</name>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: apisonator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Ciganek
|
@@ -16,7 +16,7 @@ authors:
|
|
16
16
|
autorequire:
|
17
17
|
bindir: bin
|
18
18
|
cert_chain: []
|
19
|
-
date: 2020-
|
19
|
+
date: 2020-06-19 00:00:00.000000000 Z
|
20
20
|
dependencies: []
|
21
21
|
description: This gem provides a daemon that handles authorization and reporting of
|
22
22
|
web services managed by 3scale.
|
@@ -111,11 +111,6 @@ files:
|
|
111
111
|
- lib/3scale/backend/memoizer.rb
|
112
112
|
- lib/3scale/backend/metric.rb
|
113
113
|
- lib/3scale/backend/metric/collection.rb
|
114
|
-
- lib/3scale/backend/oauth.rb
|
115
|
-
- lib/3scale/backend/oauth/token.rb
|
116
|
-
- lib/3scale/backend/oauth/token_key.rb
|
117
|
-
- lib/3scale/backend/oauth/token_storage.rb
|
118
|
-
- lib/3scale/backend/oauth/token_value.rb
|
119
114
|
- lib/3scale/backend/period.rb
|
120
115
|
- lib/3scale/backend/period/boundary.rb
|
121
116
|
- lib/3scale/backend/period/cache.rb
|
@@ -184,8 +179,6 @@ files:
|
|
184
179
|
- lib/3scale/backend/validators/service_state.rb
|
185
180
|
- lib/3scale/backend/validators/state.rb
|
186
181
|
- lib/3scale/backend/version.rb
|
187
|
-
- lib/3scale/backend/views/oauth_access_tokens.builder
|
188
|
-
- lib/3scale/backend/views/oauth_app_id_by_token.builder
|
189
182
|
- lib/3scale/backend/worker.rb
|
190
183
|
- lib/3scale/backend/worker_async.rb
|
191
184
|
- lib/3scale/backend/worker_metrics.rb
|
data/lib/3scale/backend/oauth.rb
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
module ThreeScale
|
2
|
-
module Backend
|
3
|
-
module OAuth
|
4
|
-
class Token
|
5
|
-
attr_reader :service_id, :token, :key
|
6
|
-
attr_accessor :ttl, :app_id
|
7
|
-
|
8
|
-
def initialize(token, service_id, app_id, ttl)
|
9
|
-
@token = token
|
10
|
-
@service_id = service_id
|
11
|
-
@app_id = app_id
|
12
|
-
@ttl = ttl
|
13
|
-
@key = Key.for token, service_id
|
14
|
-
end
|
15
|
-
|
16
|
-
def value
|
17
|
-
Value.for app_id
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.from_value(token, service_id, value, ttl)
|
21
|
-
new token, service_id, *Value.from(value), ttl
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,30 +0,0 @@
|
|
1
|
-
# This module defines the format of the keys for OAuth tokens and token sets.
|
2
|
-
#
|
3
|
-
# Note that while we can build the key easily, we cannot reliably obtain a token
|
4
|
-
# and a service_id out of the key, because there are no constraints on them:
|
5
|
-
#
|
6
|
-
# "oauth_access_tokens/service:some/servicegoeshere/andthisis_a_/valid_token"
|
7
|
-
#
|
8
|
-
module ThreeScale
|
9
|
-
module Backend
|
10
|
-
module OAuth
|
11
|
-
class Token
|
12
|
-
module Key
|
13
|
-
class << self
|
14
|
-
def for(token, service_id)
|
15
|
-
"oauth_access_tokens/service:#{service_id}/#{token}"
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
module Set
|
20
|
-
class << self
|
21
|
-
def for(service_id, app_id)
|
22
|
-
"oauth_access_tokens/service:#{service_id}/app:#{app_id}/"
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
@@ -1,313 +0,0 @@
|
|
1
|
-
module ThreeScale
|
2
|
-
module Backend
|
3
|
-
module OAuth
|
4
|
-
class Token
|
5
|
-
module Storage
|
6
|
-
include Configurable
|
7
|
-
|
8
|
-
# Default token size is 4K - 512 (to allow for some metadata)
|
9
|
-
MAXIMUM_TOKEN_SIZE = configuration.oauth.max_token_size || 3584
|
10
|
-
private_constant :MAXIMUM_TOKEN_SIZE
|
11
|
-
TOKEN_MAX_REDIS_SLICE_SIZE = 500
|
12
|
-
private_constant :TOKEN_MAX_REDIS_SLICE_SIZE
|
13
|
-
TOKEN_TTL_DEFAULT = 86400
|
14
|
-
private_constant :TOKEN_TTL_DEFAULT
|
15
|
-
TOKEN_TTL_PERMANENT = 0
|
16
|
-
private_constant :TOKEN_TTL_PERMANENT
|
17
|
-
|
18
|
-
Error = Class.new StandardError
|
19
|
-
InconsistencyError = Class.new Error
|
20
|
-
|
21
|
-
class << self
|
22
|
-
include Backend::Logging
|
23
|
-
include Backend::StorageHelpers
|
24
|
-
|
25
|
-
def create(token, service_id, app_id, ttl = nil)
|
26
|
-
raise AccessTokenFormatInvalid if token.nil? || token.empty? ||
|
27
|
-
!token.is_a?(String) || token.bytesize > MAXIMUM_TOKEN_SIZE
|
28
|
-
|
29
|
-
# raises if TTL is invalid
|
30
|
-
ttl = sanitized_ttl ttl
|
31
|
-
|
32
|
-
key = Key.for token, service_id
|
33
|
-
raise AccessTokenAlreadyExists.new(token) unless storage.get(key).nil?
|
34
|
-
|
35
|
-
value = Value.for app_id
|
36
|
-
token_set = Key::Set.for(service_id, app_id)
|
37
|
-
|
38
|
-
store_token token, token_set, key, value, ttl
|
39
|
-
ensure_stored! token, token_set, key, value
|
40
|
-
end
|
41
|
-
|
42
|
-
# Deletes a token
|
43
|
-
#
|
44
|
-
# Returns the associated app_id or nil
|
45
|
-
#
|
46
|
-
def delete(token, service_id)
|
47
|
-
key = Key.for token, service_id
|
48
|
-
val = storage.get key
|
49
|
-
if val
|
50
|
-
app_id = Value.from val
|
51
|
-
token_set = Key::Set.for(service_id, app_id)
|
52
|
-
|
53
|
-
existed, * = remove_a_token token_set, token, key
|
54
|
-
|
55
|
-
unless existed
|
56
|
-
logger.notify(InconsistencyError.new("Found OAuth token " \
|
57
|
-
"#{token} for service #{service_id} and app #{app_id} as " \
|
58
|
-
"key but not in set!"))
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
val
|
63
|
-
end
|
64
|
-
|
65
|
-
# Get a token's associated app_id
|
66
|
-
def get_credentials(token, service_id)
|
67
|
-
app_id = Value.from(storage.get(Key.for(token, service_id)))
|
68
|
-
raise AccessTokenInvalid.new token if app_id.nil?
|
69
|
-
app_id
|
70
|
-
end
|
71
|
-
|
72
|
-
# This is used to list tokens by service and app.
|
73
|
-
#
|
74
|
-
# Note: this deletes tokens that have not been found from the set of
|
75
|
-
# tokens for the given app - those have to be expired tokens.
|
76
|
-
def all_by_service_and_app(service_id, app_id)
|
77
|
-
token_set = Key::Set.for(service_id, app_id)
|
78
|
-
deltokens = []
|
79
|
-
tokens_n_values_flat(token_set, service_id)
|
80
|
-
.select do |(token, _key, value, _ttl)|
|
81
|
-
app_id = Value.from value
|
82
|
-
if app_id.nil?
|
83
|
-
deltokens << token
|
84
|
-
false
|
85
|
-
else
|
86
|
-
true
|
87
|
-
end
|
88
|
-
end
|
89
|
-
.map do |(token, _key, value, ttl)|
|
90
|
-
Token.from_value token, service_id, value, ttl
|
91
|
-
end
|
92
|
-
.tap do
|
93
|
-
# delete expired tokens (nil values) from token set
|
94
|
-
deltokens.each_slice(TOKEN_MAX_REDIS_SLICE_SIZE) do |delgrp|
|
95
|
-
storage.srem token_set, delgrp
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
# Remove tokens by app_id.
|
101
|
-
#
|
102
|
-
# Triggered by Application deletion.
|
103
|
-
#
|
104
|
-
def remove_tokens(service_id, app_id)
|
105
|
-
remove_tokens_by service_id, app_id
|
106
|
-
end
|
107
|
-
|
108
|
-
|
109
|
-
private
|
110
|
-
|
111
|
-
# Remove all tokens
|
112
|
-
#
|
113
|
-
# I thought of leaving this one public, but remove_*_tokens removed
|
114
|
-
# my use cases for the time being.
|
115
|
-
def remove_tokens_by(service_id, app_id)
|
116
|
-
token_set = Key::Set.for(service_id, app_id)
|
117
|
-
|
118
|
-
remove_whole_token_set(token_set, service_id)
|
119
|
-
end
|
120
|
-
|
121
|
-
def remove_token_set_by(token_set, service_id, &blk)
|
122
|
-
# Get tokens. Filter them. Group them into manageable groups.
|
123
|
-
# Extract tokens and keys into separate arrays, one for each.
|
124
|
-
# Remove tokens from token set (they are keys in a set) and token
|
125
|
-
# keys themselves.
|
126
|
-
tokens_n_values_flat(token_set, service_id, false)
|
127
|
-
.select(&blk)
|
128
|
-
.each_slice(TOKEN_MAX_REDIS_SLICE_SIZE)
|
129
|
-
.inject([[], []]) do |acc, groups|
|
130
|
-
groups.each do |token, key, _value|
|
131
|
-
acc[0] << token
|
132
|
-
acc[1] << key
|
133
|
-
end
|
134
|
-
acc
|
135
|
-
end
|
136
|
-
.each_slice(2)
|
137
|
-
.inject([]) do |acc, (tokens, keys)|
|
138
|
-
storage.pipelined do
|
139
|
-
if tokens && !tokens.empty?
|
140
|
-
storage.srem token_set, tokens
|
141
|
-
acc.concat tokens
|
142
|
-
end
|
143
|
-
storage.del keys if keys && !keys.empty?
|
144
|
-
end
|
145
|
-
acc
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
def remove_a_token(token_set, token, key)
|
150
|
-
storage.pipelined do
|
151
|
-
storage.srem token_set, token
|
152
|
-
storage.del key
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
def remove_whole_token_set(token_set, service_id)
|
157
|
-
_token_groups, key_groups = tokens_n_keys(token_set, service_id)
|
158
|
-
storage.pipelined do
|
159
|
-
storage.del token_set
|
160
|
-
# remove all tokens for this app
|
161
|
-
key_groups.each do |keys|
|
162
|
-
storage.del keys
|
163
|
-
end
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
# TODO: provide a SSCAN interface with lazy enums because SMEMBERS
|
168
|
-
# is prone to DoSing and timeouts
|
169
|
-
def tokens_from(token_set)
|
170
|
-
storage.smembers(token_set)
|
171
|
-
end
|
172
|
-
|
173
|
-
def tokens_n_keys(token_set, service_id)
|
174
|
-
token_groups = tokens_from(token_set).each_slice(TOKEN_MAX_REDIS_SLICE_SIZE)
|
175
|
-
key_groups = token_groups.map do |tokens|
|
176
|
-
tokens.map do |token|
|
177
|
-
Key.for token, service_id
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
[token_groups, key_groups]
|
182
|
-
end
|
183
|
-
|
184
|
-
# Provides grouped data which matches respectively in each array
|
185
|
-
# position, ie. 1st group of data contains a group of tokens, keys
|
186
|
-
# and values with ttls, and position N of the tokens group has key
|
187
|
-
# in position N of the keys group, and so on.
|
188
|
-
#
|
189
|
-
# [[[token group], [key group], [value_with_ttls_group]], ...]
|
190
|
-
#
|
191
|
-
def tokens_n_values_groups(token_set, service_id, with_ttls)
|
192
|
-
token_groups, key_groups = tokens_n_keys(token_set, service_id)
|
193
|
-
value_ttl_groups = key_groups.map do |keys|
|
194
|
-
# pipelining will create an array with the results of commands
|
195
|
-
res = storage.pipelined do
|
196
|
-
storage.mget(keys)
|
197
|
-
if with_ttls
|
198
|
-
keys.map do |key|
|
199
|
-
storage.ttl key
|
200
|
-
end
|
201
|
-
end
|
202
|
-
end
|
203
|
-
# [mget array, 0..n ttls] => [mget array, ttls array]
|
204
|
-
[res.shift, res]
|
205
|
-
end
|
206
|
-
token_groups.zip(key_groups, value_ttl_groups)
|
207
|
-
end
|
208
|
-
|
209
|
-
# Zips the data provided by tokens_n_values_groups so that you stop
|
210
|
-
# looking at indexes in the respective arrays and instead have:
|
211
|
-
#
|
212
|
-
# [group 0, ..., group N] where each group is made of:
|
213
|
-
# [[token 0, key 0, value 0, ttl 0], ..., [token N, key N, value
|
214
|
-
# N, ttl N]]
|
215
|
-
#
|
216
|
-
def tokens_n_values_zipped_groups(token_set, service_id, with_ttls = true)
|
217
|
-
tokens_n_values_groups(token_set,
|
218
|
-
service_id,
|
219
|
-
with_ttls).map do |tokens, keys, (values, ttls)|
|
220
|
-
tokens.zip keys, values, ttls
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
# Flattens the data provided by tokens_n_values_zipped_groups so
|
225
|
-
# that you have a transparent iterator with all needed data and can
|
226
|
-
# stop worrying about streaming groups of elements.
|
227
|
-
#
|
228
|
-
def tokens_n_values_flat(token_set, service_id, with_ttls = true)
|
229
|
-
tokens_n_values_zipped_groups(token_set,
|
230
|
-
service_id,
|
231
|
-
with_ttls).flat_map do |groups|
|
232
|
-
groups.map do |token, key, value, ttl|
|
233
|
-
[token, key, value, ttl]
|
234
|
-
end
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
# Store the specified token in Redis
|
239
|
-
#
|
240
|
-
# TTL specified in seconds.
|
241
|
-
# A TTL of 0 stores a permanent token
|
242
|
-
def store_token(token, token_set, key, value, ttl)
|
243
|
-
# build the storage command so that we can pipeline everything cleanly
|
244
|
-
command = :set
|
245
|
-
args = [key]
|
246
|
-
|
247
|
-
if !permanent_ttl? ttl
|
248
|
-
command = :setex
|
249
|
-
args << ttl
|
250
|
-
end
|
251
|
-
|
252
|
-
args << value
|
253
|
-
|
254
|
-
# pipelined will return nil if it is embedded into another
|
255
|
-
# pipeline(which would be an error at this point) or if shutting
|
256
|
-
# down and a connection error happens. Both things being abnormal
|
257
|
-
# means we should just raise a storage error.
|
258
|
-
raise AccessTokenStorageError, token unless storage.pipelined do
|
259
|
-
storage.send(command, *args)
|
260
|
-
storage.sadd(token_set, token)
|
261
|
-
end
|
262
|
-
end
|
263
|
-
|
264
|
-
|
265
|
-
# Make sure everything ended up there
|
266
|
-
#
|
267
|
-
# TODO: review and possibly reimplement trying to leave it
|
268
|
-
# consistent as much as possible.
|
269
|
-
#
|
270
|
-
# Note that we have a sharding proxy and pipelines can't be guaranteed
|
271
|
-
# to behave like transactions, since we might have one non-working
|
272
|
-
# shard. Instead of relying on proxy-specific responses, we just check
|
273
|
-
# that the data we should have in the store is really there.
|
274
|
-
def ensure_stored!(token, token_set, key, value)
|
275
|
-
results = storage.pipelined do
|
276
|
-
storage.get(key)
|
277
|
-
storage.sismember(token_set, token)
|
278
|
-
end
|
279
|
-
|
280
|
-
results.last && results.first == value ||
|
281
|
-
raise(AccessTokenStorageError, token)
|
282
|
-
end
|
283
|
-
|
284
|
-
# Validation for the TTL value
|
285
|
-
#
|
286
|
-
# 0 is accepted (understood as permanent token)
|
287
|
-
# Negative values are not accepted
|
288
|
-
# Integer(ttl) validation is required (if input is nil, default applies)
|
289
|
-
def sanitized_ttl(ttl)
|
290
|
-
ttl = begin
|
291
|
-
Integer(ttl)
|
292
|
-
rescue TypeError
|
293
|
-
# ttl is nil
|
294
|
-
TOKEN_TTL_DEFAULT
|
295
|
-
rescue
|
296
|
-
# NaN
|
297
|
-
-1
|
298
|
-
end
|
299
|
-
raise AccessTokenInvalidTTL if ttl < 0
|
300
|
-
|
301
|
-
ttl
|
302
|
-
end
|
303
|
-
|
304
|
-
# Check whether a TTL has the magic value for a permanent token
|
305
|
-
def permanent_ttl?(ttl)
|
306
|
-
ttl == TOKEN_TTL_PERMANENT
|
307
|
-
end
|
308
|
-
end
|
309
|
-
end
|
310
|
-
end
|
311
|
-
end
|
312
|
-
end
|
313
|
-
end
|