apisonator 2.100.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +317 -0
  3. data/Gemfile +11 -0
  4. data/Gemfile.base +65 -0
  5. data/Gemfile.lock +319 -0
  6. data/Gemfile.on_prem +1 -0
  7. data/Gemfile.on_prem.lock +297 -0
  8. data/LICENSE +202 -0
  9. data/NOTICE +15 -0
  10. data/README.md +230 -0
  11. data/Rakefile +287 -0
  12. data/apisonator.gemspec +47 -0
  13. data/app/api/api.rb +13 -0
  14. data/app/api/internal/alert_limits.rb +32 -0
  15. data/app/api/internal/application_keys.rb +49 -0
  16. data/app/api/internal/application_referrer_filters.rb +43 -0
  17. data/app/api/internal/applications.rb +77 -0
  18. data/app/api/internal/errors.rb +54 -0
  19. data/app/api/internal/events.rb +42 -0
  20. data/app/api/internal/internal.rb +104 -0
  21. data/app/api/internal/metrics.rb +40 -0
  22. data/app/api/internal/service_tokens.rb +46 -0
  23. data/app/api/internal/services.rb +58 -0
  24. data/app/api/internal/stats.rb +42 -0
  25. data/app/api/internal/usagelimits.rb +62 -0
  26. data/app/api/internal/utilization.rb +23 -0
  27. data/bin/3scale_backend +223 -0
  28. data/bin/3scale_backend_worker +26 -0
  29. data/config.ru +4 -0
  30. data/config/puma.rb +192 -0
  31. data/config/schedule.rb +9 -0
  32. data/ext/mkrf_conf.rb +64 -0
  33. data/lib/3scale/backend.rb +67 -0
  34. data/lib/3scale/backend/alert_limit.rb +56 -0
  35. data/lib/3scale/backend/alerts.rb +137 -0
  36. data/lib/3scale/backend/analytics/kinesis.rb +3 -0
  37. data/lib/3scale/backend/analytics/kinesis/adapter.rb +180 -0
  38. data/lib/3scale/backend/analytics/kinesis/exporter.rb +86 -0
  39. data/lib/3scale/backend/analytics/kinesis/job.rb +135 -0
  40. data/lib/3scale/backend/analytics/redshift.rb +3 -0
  41. data/lib/3scale/backend/analytics/redshift/adapter.rb +367 -0
  42. data/lib/3scale/backend/analytics/redshift/importer.rb +83 -0
  43. data/lib/3scale/backend/analytics/redshift/job.rb +33 -0
  44. data/lib/3scale/backend/application.rb +330 -0
  45. data/lib/3scale/backend/application_events.rb +76 -0
  46. data/lib/3scale/backend/background_job.rb +65 -0
  47. data/lib/3scale/backend/configurable.rb +20 -0
  48. data/lib/3scale/backend/configuration.rb +151 -0
  49. data/lib/3scale/backend/configuration/loader.rb +42 -0
  50. data/lib/3scale/backend/constants.rb +19 -0
  51. data/lib/3scale/backend/cors.rb +84 -0
  52. data/lib/3scale/backend/distributed_lock.rb +67 -0
  53. data/lib/3scale/backend/environment.rb +21 -0
  54. data/lib/3scale/backend/error_storage.rb +52 -0
  55. data/lib/3scale/backend/errors.rb +343 -0
  56. data/lib/3scale/backend/event_storage.rb +120 -0
  57. data/lib/3scale/backend/experiment.rb +84 -0
  58. data/lib/3scale/backend/extensions.rb +5 -0
  59. data/lib/3scale/backend/extensions/array.rb +19 -0
  60. data/lib/3scale/backend/extensions/hash.rb +26 -0
  61. data/lib/3scale/backend/extensions/nil_class.rb +13 -0
  62. data/lib/3scale/backend/extensions/redis.rb +44 -0
  63. data/lib/3scale/backend/extensions/string.rb +13 -0
  64. data/lib/3scale/backend/extensions/time.rb +110 -0
  65. data/lib/3scale/backend/failed_jobs_scheduler.rb +141 -0
  66. data/lib/3scale/backend/job_fetcher.rb +122 -0
  67. data/lib/3scale/backend/listener.rb +728 -0
  68. data/lib/3scale/backend/listener_metrics.rb +99 -0
  69. data/lib/3scale/backend/logging.rb +48 -0
  70. data/lib/3scale/backend/logging/external.rb +44 -0
  71. data/lib/3scale/backend/logging/external/impl.rb +93 -0
  72. data/lib/3scale/backend/logging/external/impl/airbrake.rb +66 -0
  73. data/lib/3scale/backend/logging/external/impl/bugsnag.rb +69 -0
  74. data/lib/3scale/backend/logging/external/impl/default.rb +18 -0
  75. data/lib/3scale/backend/logging/external/resque.rb +57 -0
  76. data/lib/3scale/backend/logging/logger.rb +18 -0
  77. data/lib/3scale/backend/logging/middleware.rb +62 -0
  78. data/lib/3scale/backend/logging/middleware/json_writer.rb +21 -0
  79. data/lib/3scale/backend/logging/middleware/text_writer.rb +60 -0
  80. data/lib/3scale/backend/logging/middleware/writer.rb +143 -0
  81. data/lib/3scale/backend/logging/worker.rb +107 -0
  82. data/lib/3scale/backend/manifest.rb +80 -0
  83. data/lib/3scale/backend/memoizer.rb +277 -0
  84. data/lib/3scale/backend/metric.rb +275 -0
  85. data/lib/3scale/backend/metric/collection.rb +91 -0
  86. data/lib/3scale/backend/oauth.rb +4 -0
  87. data/lib/3scale/backend/oauth/token.rb +26 -0
  88. data/lib/3scale/backend/oauth/token_key.rb +30 -0
  89. data/lib/3scale/backend/oauth/token_storage.rb +313 -0
  90. data/lib/3scale/backend/oauth/token_value.rb +25 -0
  91. data/lib/3scale/backend/period.rb +3 -0
  92. data/lib/3scale/backend/period/boundary.rb +107 -0
  93. data/lib/3scale/backend/period/cache.rb +28 -0
  94. data/lib/3scale/backend/period/period.rb +402 -0
  95. data/lib/3scale/backend/queue_storage.rb +16 -0
  96. data/lib/3scale/backend/rack.rb +49 -0
  97. data/lib/3scale/backend/rack/exception_catcher.rb +136 -0
  98. data/lib/3scale/backend/rack/internal_error_catcher.rb +23 -0
  99. data/lib/3scale/backend/rack/prometheus.rb +19 -0
  100. data/lib/3scale/backend/saas.rb +6 -0
  101. data/lib/3scale/backend/saas_analytics.rb +4 -0
  102. data/lib/3scale/backend/server.rb +30 -0
  103. data/lib/3scale/backend/server/falcon.rb +52 -0
  104. data/lib/3scale/backend/server/puma.rb +71 -0
  105. data/lib/3scale/backend/service.rb +317 -0
  106. data/lib/3scale/backend/service_token.rb +97 -0
  107. data/lib/3scale/backend/stats.rb +8 -0
  108. data/lib/3scale/backend/stats/aggregator.rb +170 -0
  109. data/lib/3scale/backend/stats/aggregators/base.rb +72 -0
  110. data/lib/3scale/backend/stats/aggregators/response_code.rb +58 -0
  111. data/lib/3scale/backend/stats/aggregators/usage.rb +34 -0
  112. data/lib/3scale/backend/stats/bucket_reader.rb +135 -0
  113. data/lib/3scale/backend/stats/bucket_storage.rb +108 -0
  114. data/lib/3scale/backend/stats/cleaner.rb +195 -0
  115. data/lib/3scale/backend/stats/codes_commons.rb +14 -0
  116. data/lib/3scale/backend/stats/delete_job_def.rb +60 -0
  117. data/lib/3scale/backend/stats/key_generator.rb +73 -0
  118. data/lib/3scale/backend/stats/keys.rb +104 -0
  119. data/lib/3scale/backend/stats/partition_eraser_job.rb +58 -0
  120. data/lib/3scale/backend/stats/partition_generator_job.rb +46 -0
  121. data/lib/3scale/backend/stats/period_commons.rb +34 -0
  122. data/lib/3scale/backend/stats/stats_parser.rb +141 -0
  123. data/lib/3scale/backend/stats/storage.rb +113 -0
  124. data/lib/3scale/backend/statsd.rb +14 -0
  125. data/lib/3scale/backend/storable.rb +35 -0
  126. data/lib/3scale/backend/storage.rb +40 -0
  127. data/lib/3scale/backend/storage_async.rb +4 -0
  128. data/lib/3scale/backend/storage_async/async_redis.rb +21 -0
  129. data/lib/3scale/backend/storage_async/client.rb +205 -0
  130. data/lib/3scale/backend/storage_async/pipeline.rb +79 -0
  131. data/lib/3scale/backend/storage_async/resque_extensions.rb +30 -0
  132. data/lib/3scale/backend/storage_helpers.rb +278 -0
  133. data/lib/3scale/backend/storage_key_helpers.rb +9 -0
  134. data/lib/3scale/backend/storage_sync.rb +43 -0
  135. data/lib/3scale/backend/transaction.rb +62 -0
  136. data/lib/3scale/backend/transactor.rb +177 -0
  137. data/lib/3scale/backend/transactor/limit_headers.rb +54 -0
  138. data/lib/3scale/backend/transactor/notify_batcher.rb +139 -0
  139. data/lib/3scale/backend/transactor/notify_job.rb +47 -0
  140. data/lib/3scale/backend/transactor/process_job.rb +33 -0
  141. data/lib/3scale/backend/transactor/report_job.rb +84 -0
  142. data/lib/3scale/backend/transactor/status.rb +236 -0
  143. data/lib/3scale/backend/transactor/usage_report.rb +182 -0
  144. data/lib/3scale/backend/usage.rb +63 -0
  145. data/lib/3scale/backend/usage_limit.rb +115 -0
  146. data/lib/3scale/backend/use_cases/provider_key_change_use_case.rb +60 -0
  147. data/lib/3scale/backend/util.rb +17 -0
  148. data/lib/3scale/backend/validators.rb +26 -0
  149. data/lib/3scale/backend/validators/base.rb +36 -0
  150. data/lib/3scale/backend/validators/key.rb +17 -0
  151. data/lib/3scale/backend/validators/limits.rb +57 -0
  152. data/lib/3scale/backend/validators/oauth_key.rb +15 -0
  153. data/lib/3scale/backend/validators/oauth_setting.rb +15 -0
  154. data/lib/3scale/backend/validators/redirect_uri.rb +33 -0
  155. data/lib/3scale/backend/validators/referrer.rb +60 -0
  156. data/lib/3scale/backend/validators/service_state.rb +15 -0
  157. data/lib/3scale/backend/validators/state.rb +15 -0
  158. data/lib/3scale/backend/version.rb +5 -0
  159. data/lib/3scale/backend/views/oauth_access_tokens.builder +14 -0
  160. data/lib/3scale/backend/views/oauth_app_id_by_token.builder +4 -0
  161. data/lib/3scale/backend/worker.rb +87 -0
  162. data/lib/3scale/backend/worker_async.rb +88 -0
  163. data/lib/3scale/backend/worker_metrics.rb +44 -0
  164. data/lib/3scale/backend/worker_sync.rb +32 -0
  165. data/lib/3scale/bundler_shim.rb +17 -0
  166. data/lib/3scale/prometheus_server.rb +10 -0
  167. data/lib/3scale/tasks/connectivity.rake +41 -0
  168. data/lib/3scale/tasks/helpers.rb +3 -0
  169. data/lib/3scale/tasks/helpers/environment.rb +23 -0
  170. data/lib/3scale/tasks/stats.rake +131 -0
  171. data/lib/3scale/tasks/swagger.rake +46 -0
  172. data/licenses.xml +1215 -0
  173. metadata +227 -0
@@ -0,0 +1,91 @@
1
+ module ThreeScale
2
+ module Backend
3
+ class Metric
4
+ class Collection
5
+ include Storable
6
+
7
+ def initialize(service_id)
8
+ @service_id = service_id
9
+ @metric_ids = {}
10
+ @parent_ids = {}
11
+ end
12
+
13
+ # Accepts usage as {'metric_name' => value, ...} and converts it into
14
+ # {metric_id => value, ...}, evaluating also metric hierarchy.
15
+ #
16
+ # == Example
17
+ #
18
+ # Let's supose there is a metric called "hits" with id 1001 and it has one child
19
+ # metric called "search_queries" with id 1002. Then:
20
+ #
21
+ # metrics.process_usage('search_queries' => 42)
22
+ #
23
+ # will produce:
24
+ #
25
+ # {1001 => 42, 1002 => 42}
26
+ #
27
+ def process_usage(raw_usage, flat_usage = false)
28
+ return {} unless raw_usage
29
+ usage = parse_usage(raw_usage)
30
+ flat_usage ? usage : process_parents(usage)
31
+ end
32
+
33
+ private
34
+
35
+ def parse_usage(raw_usage)
36
+ raw_usage.inject({}) do |usage, (name, value)|
37
+ name = name.strip
38
+ raise UsageValueInvalid.new(name, value) unless sane_value?(value)
39
+ usage.update(metric_id(name) => value)
40
+ end
41
+ end
42
+
43
+ # Propagates the usage to all the levels of the hierarchy.
44
+ # For example, in this scenario:
45
+ # m1 --child_of--> m2 --child_of--> m3
46
+ # If there's a +1 in m1, this method will set the +1 in the other 2 as
47
+ # well.
48
+ def process_parents(usage)
49
+ usage.inject(usage.dup) do |memo, (id, val)|
50
+ is_set_op = Usage.is_set?(val)
51
+
52
+ while (id = parent_id(id))
53
+ if is_set_op
54
+ memo[id] = val
55
+ else
56
+ # need the to_i here instead of in parse_usage because the value
57
+ # can be a string if the parent is passed explicitly on the usage
58
+ # since the value might not be a Fixnum but a '#'Fixnum
59
+ # (also because memo[p_id] might be nil)
60
+ memo[id] = memo[id].to_i
61
+ memo[id] += val.to_i
62
+ end
63
+ end
64
+
65
+ memo
66
+ end
67
+ end
68
+
69
+ def parent_id(id)
70
+ @parent_ids[id] ||= Metric.load_parent_id(@service_id, id)
71
+ end
72
+
73
+ def metric_id(name)
74
+ @metric_ids[name] ||= load_metric_id(name)
75
+ end
76
+
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))
82
+ end
83
+
84
+ ## accepts postive integers or positive integers preffixed with # (for sets)
85
+ def sane_value?(value)
86
+ value.is_a?(Numeric) || value.to_s =~ /\A\s*#?\d+\s*\Z/
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,4 @@
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'
@@ -0,0 +1,26 @@
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
@@ -0,0 +1,30 @@
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
@@ -0,0 +1,313 @@
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