apisonator 2.100.2.pre1 → 3.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
@@ -75,10 +75,7 @@ module ThreeScale
75
75
  end
76
76
 
77
77
  def load_metric_id(name)
78
- Memoizer.memoize_block(Memoizer.build_key(self,
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(endpoint)
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 = [:connect_timeout, :read_timeout, :write_timeout].freeze
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'], transaction['access_token'])
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
- return metric_ids if metric_ids.empty?
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
@@ -1,5 +1,5 @@
1
1
  module ThreeScale
2
2
  module Backend
3
- VERSION = '2.100.2.pre1'
3
+ VERSION = '3.0.0.pre1'
4
4
  end
5
5
  end
@@ -3,7 +3,7 @@
3
3
  <dependencies>
4
4
  <dependency>
5
5
  <packageName>activesupport</packageName>
6
- <version>5.1.4</version>
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>2.100.1</version>
26
+ <version>2.101.1</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.0.5</version>
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>0.9.1</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.10.3</version>
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.4</version>
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: 2.100.2.pre1
4
+ version: 3.0.0.pre1
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-05-08 00:00:00.000000000 Z
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
@@ -1,4 +0,0 @@
1
- require '3scale/backend/oauth/token'
2
- require '3scale/backend/oauth/token_key'
3
- require '3scale/backend/oauth/token_value'
4
- require '3scale/backend/oauth/token_storage'
@@ -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