apisonator 2.101.1 → 3.1.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 +50 -0
- data/Gemfile.base +2 -2
- data/Gemfile.lock +10 -9
- data/Gemfile.on_prem.lock +10 -9
- data/lib/3scale/backend.rb +0 -1
- data/lib/3scale/backend/application.rb +3 -6
- data/lib/3scale/backend/configuration.rb +1 -4
- data/lib/3scale/backend/errors.rb +0 -36
- data/lib/3scale/backend/listener.rb +2 -79
- data/lib/3scale/backend/listener_metrics.rb +65 -8
- data/lib/3scale/backend/rack/prometheus.rb +9 -1
- data/lib/3scale/backend/storage_async/client.rb +1 -1
- data/lib/3scale/backend/transactor.rb +1 -13
- data/lib/3scale/backend/transactor/notify_batcher.rb +6 -2
- data/lib/3scale/backend/transactor/notify_job.rb +37 -17
- data/lib/3scale/backend/transactor/report_job.rb +1 -1
- data/lib/3scale/backend/version.rb +1 -1
- data/licenses.xml +6 -6
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: edd1887f6aa71c17fb5279f98988c9a3b5e631e80bc1f4142b6619b5f5e24f48
|
4
|
+
data.tar.gz: b6478d14e88a177480396a7d46d5f4e33cea683aea17303175ad18ae646e546f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 944b75385817abc4d2f8828c61b2301d5cdfbb6c1bb681b7a3165590f23ad1b6c80451fcf579b8f3cf0010fe5a95e65605998f5d8ec94f1ff4ce690556b3011a
|
7
|
+
data.tar.gz: f3f44b1b2ebfe6c8fcbbf36b90dd44e5a7875d997a36b97288417472fa4156689508bfc95afdf196a7a168f768aa6e38521502b93078f128994959d184c1cd37
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,56 @@
|
|
2
2
|
|
3
3
|
Notable changes to Apisonator will be tracked in this document.
|
4
4
|
|
5
|
+
## 3.1.0 - 2020-10-14
|
6
|
+
|
7
|
+
### Added
|
8
|
+
|
9
|
+
- Prometheus metrics for the internal API
|
10
|
+
([#236](https://github.com/3scale/apisonator/pull/236)).
|
11
|
+
- Docs with a detailed explanation about how counter updates are performed
|
12
|
+
([#239](https://github.com/3scale/apisonator/pull/239)).
|
13
|
+
|
14
|
+
### Changed
|
15
|
+
|
16
|
+
- NotifyJobs are run only when the service ID is explicitly defined
|
17
|
+
([#238](https://github.com/3scale/apisonator/pull/238)).
|
18
|
+
|
19
|
+
### Fixed
|
20
|
+
|
21
|
+
- Fixed corner case that raised "TransactionTimestampNotWithinRange" in notify
|
22
|
+
jobs ([#235](https://github.com/3scale/apisonator/pull/235)).
|
23
|
+
|
24
|
+
|
25
|
+
## 3.0.1.1 - 2020-07-28
|
26
|
+
|
27
|
+
### Changed
|
28
|
+
|
29
|
+
- Updated json gem to v2.3.1
|
30
|
+
([#232](https://github.com/3scale/apisonator/pull/232)).
|
31
|
+
|
32
|
+
## 3.0.1 - 2020-07-14
|
33
|
+
|
34
|
+
### Fixed
|
35
|
+
|
36
|
+
- The Prometheus counter of "5xx" errors has been fixed
|
37
|
+
([#230](https://github.com/3scale/apisonator/pull/230)).
|
38
|
+
|
39
|
+
### Changed
|
40
|
+
|
41
|
+
- Gem updates: rack to v2.1.4
|
42
|
+
([#228](https://github.com/3scale/apisonator/pull/228)) and async-redis to
|
43
|
+
v0.5.0 ([#229](https://github.com/3scale/apisonator/pull/229)).
|
44
|
+
|
45
|
+
|
46
|
+
## 3.0.0 - 2020-06-19
|
47
|
+
|
48
|
+
### Removed
|
49
|
+
|
50
|
+
- Apisonator no longer supports the native oauth authorization mode. This is a
|
51
|
+
feature that was deprecated a long time ago
|
52
|
+
([#226](https://github.com/3scale/apisonator/pull/226)).
|
53
|
+
|
54
|
+
|
5
55
|
## 2.101.1 - 2020-06-05
|
6
56
|
|
7
57
|
### Fixed
|
data/Gemfile.base
CHANGED
@@ -53,13 +53,13 @@ gem 'rake', '~> 13.0'
|
|
53
53
|
gem 'builder', '= 3.2.3'
|
54
54
|
# Use a patched resque to allow reusing their Airbrake Failure class
|
55
55
|
gem 'resque', git: 'https://github.com/3scale/resque', branch: '3scale'
|
56
|
-
gem 'rack', '~> 2.
|
56
|
+
gem 'rack', '~> 2.1.4'
|
57
57
|
gem 'sinatra', '~> 2.0.3'
|
58
58
|
gem 'sinatra-contrib', '~> 2.0.3'
|
59
59
|
# Optional external error logging services
|
60
60
|
gem 'bugsnag', '~> 6', require: nil
|
61
61
|
gem 'yabeda-prometheus', '~> 0.5.0'
|
62
|
-
gem 'async-redis', '~> 0.
|
62
|
+
gem 'async-redis', '~> 0.5'
|
63
63
|
gem 'falcon', '~> 0.35'
|
64
64
|
|
65
65
|
# Use a patched redis-rb that fixes an issue when trying to connect with
|
data/Gemfile.lock
CHANGED
@@ -35,7 +35,7 @@ GIT
|
|
35
35
|
PATH
|
36
36
|
remote: .
|
37
37
|
specs:
|
38
|
-
apisonator (
|
38
|
+
apisonator (3.1.0)
|
39
39
|
|
40
40
|
GEM
|
41
41
|
remote: https://rubygems.org/
|
@@ -66,14 +66,15 @@ GEM
|
|
66
66
|
async-http-cache (0.1.5)
|
67
67
|
async-http
|
68
68
|
protocol-http (~> 0.14)
|
69
|
-
async-io (1.27.
|
69
|
+
async-io (1.27.7)
|
70
70
|
async (~> 1.14)
|
71
71
|
async-pool (0.2.0)
|
72
72
|
async (~> 1.8)
|
73
|
-
async-redis (0.
|
73
|
+
async-redis (0.5.0)
|
74
74
|
async (~> 1.8)
|
75
75
|
async-io (~> 1.10)
|
76
|
-
|
76
|
+
async-pool (~> 0.2)
|
77
|
+
protocol-redis (~> 0.5.0)
|
77
78
|
async-rspec (1.13.0)
|
78
79
|
rspec (~> 3.0)
|
79
80
|
rspec-files (~> 1.0)
|
@@ -118,7 +119,7 @@ GEM
|
|
118
119
|
i18n (1.8.2)
|
119
120
|
concurrent-ruby (~> 1.0)
|
120
121
|
jmespath (1.3.1)
|
121
|
-
json (2.1
|
122
|
+
json (2.3.1)
|
122
123
|
license_finder (5.9.2)
|
123
124
|
bundler
|
124
125
|
rubyzip
|
@@ -161,7 +162,7 @@ GEM
|
|
161
162
|
protocol-http2 (0.11.6)
|
162
163
|
protocol-hpack (~> 1.4)
|
163
164
|
protocol-http (~> 0.15)
|
164
|
-
protocol-redis (0.
|
165
|
+
protocol-redis (0.5.0)
|
165
166
|
pry (0.11.3)
|
166
167
|
coderay (~> 1.1.0)
|
167
168
|
method_source (~> 0.9.0)
|
@@ -171,7 +172,7 @@ GEM
|
|
171
172
|
pry-doc (0.11.1)
|
172
173
|
pry (~> 0.9)
|
173
174
|
yard (~> 0.9)
|
174
|
-
rack (2.
|
175
|
+
rack (2.1.4)
|
175
176
|
rack-protection (2.0.3)
|
176
177
|
rack
|
177
178
|
rack-test (0.8.2)
|
@@ -266,7 +267,7 @@ PLATFORMS
|
|
266
267
|
DEPENDENCIES
|
267
268
|
airbrake (= 4.3.1)
|
268
269
|
apisonator!
|
269
|
-
async-redis (~> 0.
|
270
|
+
async-redis (~> 0.5)
|
270
271
|
async-rspec
|
271
272
|
aws-sdk (= 2.4.2)
|
272
273
|
benchmark-ips (~> 2.7.2)
|
@@ -286,7 +287,7 @@ DEPENDENCIES
|
|
286
287
|
pry-byebug (~> 3.5.1)
|
287
288
|
pry-doc (~> 0.11.1)
|
288
289
|
puma!
|
289
|
-
rack (~> 2.
|
290
|
+
rack (~> 2.1.4)
|
290
291
|
rack-test (~> 0.8.2)
|
291
292
|
rake (~> 13.0)
|
292
293
|
redis!
|
data/Gemfile.on_prem.lock
CHANGED
@@ -35,7 +35,7 @@ GIT
|
|
35
35
|
PATH
|
36
36
|
remote: .
|
37
37
|
specs:
|
38
|
-
apisonator (
|
38
|
+
apisonator (3.1.0)
|
39
39
|
|
40
40
|
GEM
|
41
41
|
remote: https://rubygems.org/
|
@@ -63,14 +63,15 @@ GEM
|
|
63
63
|
async-http-cache (0.1.5)
|
64
64
|
async-http
|
65
65
|
protocol-http (~> 0.14)
|
66
|
-
async-io (1.27.
|
66
|
+
async-io (1.27.7)
|
67
67
|
async (~> 1.14)
|
68
68
|
async-pool (0.2.0)
|
69
69
|
async (~> 1.8)
|
70
|
-
async-redis (0.
|
70
|
+
async-redis (0.5.0)
|
71
71
|
async (~> 1.8)
|
72
72
|
async-io (~> 1.10)
|
73
|
-
|
73
|
+
async-pool (~> 0.2)
|
74
|
+
protocol-redis (~> 0.5.0)
|
74
75
|
async-rspec (1.13.0)
|
75
76
|
rspec (~> 3.0)
|
76
77
|
rspec-files (~> 1.0)
|
@@ -107,7 +108,7 @@ GEM
|
|
107
108
|
hiredis (0.6.3)
|
108
109
|
i18n (1.8.2)
|
109
110
|
concurrent-ruby (~> 1.0)
|
110
|
-
json (2.1
|
111
|
+
json (2.3.1)
|
111
112
|
license_finder (5.9.2)
|
112
113
|
bundler
|
113
114
|
rubyzip
|
@@ -149,7 +150,7 @@ GEM
|
|
149
150
|
protocol-http2 (0.11.6)
|
150
151
|
protocol-hpack (~> 1.4)
|
151
152
|
protocol-http (~> 0.15)
|
152
|
-
protocol-redis (0.
|
153
|
+
protocol-redis (0.5.0)
|
153
154
|
pry (0.11.3)
|
154
155
|
coderay (~> 1.1.0)
|
155
156
|
method_source (~> 0.9.0)
|
@@ -159,7 +160,7 @@ GEM
|
|
159
160
|
pry-doc (0.11.1)
|
160
161
|
pry (~> 0.9)
|
161
162
|
yard (~> 0.9)
|
162
|
-
rack (2.
|
163
|
+
rack (2.1.4)
|
163
164
|
rack-protection (2.0.3)
|
164
165
|
rack
|
165
166
|
rack-test (0.8.2)
|
@@ -249,7 +250,7 @@ PLATFORMS
|
|
249
250
|
|
250
251
|
DEPENDENCIES
|
251
252
|
apisonator!
|
252
|
-
async-redis (~> 0.
|
253
|
+
async-redis (~> 0.5)
|
253
254
|
async-rspec
|
254
255
|
benchmark-ips (~> 2.7.2)
|
255
256
|
bugsnag (~> 6)
|
@@ -267,7 +268,7 @@ DEPENDENCIES
|
|
267
268
|
pry-byebug (~> 3.5.1)
|
268
269
|
pry-doc (~> 0.11.1)
|
269
270
|
puma!
|
270
|
-
rack (~> 2.
|
271
|
+
rack (~> 2.1.4)
|
271
272
|
rack-test (~> 0.8.2)
|
272
273
|
rake (~> 13.0)
|
273
274
|
redis!
|
data/lib/3scale/backend.rb
CHANGED
@@ -35,7 +35,6 @@ require '3scale/backend/rack'
|
|
35
35
|
require '3scale/backend/extensions'
|
36
36
|
require '3scale/backend/background_job'
|
37
37
|
require '3scale/backend/storage'
|
38
|
-
require '3scale/backend/oauth'
|
39
38
|
require '3scale/backend/memoizer'
|
40
39
|
require '3scale/backend/application'
|
41
40
|
require '3scale/backend/error_storage'
|
@@ -91,8 +91,8 @@ module ThreeScale
|
|
91
91
|
end
|
92
92
|
end
|
93
93
|
|
94
|
-
def extract_id!(service_id, app_id, user_key
|
95
|
-
with_app_id_from_params service_id, app_id, user_key
|
94
|
+
def extract_id!(service_id, app_id, user_key)
|
95
|
+
with_app_id_from_params service_id, app_id, user_key do |appid|
|
96
96
|
exists? service_id, appid and appid
|
97
97
|
end
|
98
98
|
end
|
@@ -106,7 +106,6 @@ module ThreeScale
|
|
106
106
|
raise ApplicationNotFound, id unless exists?(service_id, id)
|
107
107
|
delete_data service_id, id
|
108
108
|
clear_cache service_id, id
|
109
|
-
OAuth::Token::Storage.remove_tokens(service_id, id)
|
110
109
|
end
|
111
110
|
|
112
111
|
def delete_data(service_id, id)
|
@@ -157,14 +156,12 @@ module ThreeScale
|
|
157
156
|
)
|
158
157
|
end
|
159
158
|
|
160
|
-
def with_app_id_from_params(service_id, app_id, user_key
|
159
|
+
def with_app_id_from_params(service_id, app_id, user_key)
|
161
160
|
if app_id
|
162
161
|
raise AuthenticationError unless user_key.nil?
|
163
162
|
elsif user_key
|
164
163
|
app_id = load_id_by_key(service_id, user_key)
|
165
164
|
raise UserKeyInvalid, user_key if app_id.nil?
|
166
|
-
elsif access_token
|
167
|
-
app_id, * = OAuth::Token::Storage.get_credentials access_token, service_id
|
168
165
|
else
|
169
166
|
raise ApplicationNotFound
|
170
167
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require '3scale/backend/configuration/loader'
|
2
2
|
require '3scale/backend/environment'
|
3
3
|
require '3scale/backend/configurable'
|
4
|
+
require '3scale/backend/errors'
|
4
5
|
|
5
6
|
module ThreeScale
|
6
7
|
module Backend
|
@@ -57,7 +58,6 @@ module ThreeScale
|
|
57
58
|
config.add_section(:redshift, :host, :port, :dbname, :user, :password)
|
58
59
|
config.add_section(:statsd, :host, :port)
|
59
60
|
config.add_section(:internal_api, :user, :password)
|
60
|
-
config.add_section(:oauth, :max_token_size)
|
61
61
|
config.add_section(:master, :metrics)
|
62
62
|
config.add_section(:worker_prometheus_metrics, :enabled, :port)
|
63
63
|
config.add_section(:listener_prometheus_metrics, :enabled, :port)
|
@@ -78,9 +78,6 @@ module ThreeScale
|
|
78
78
|
master_metrics = [:transactions, :transactions_authorize]
|
79
79
|
config.master.metrics = Struct.new(*master_metrics).new
|
80
80
|
|
81
|
-
# Default config
|
82
|
-
config.master_service_id = 1
|
83
|
-
|
84
81
|
# This setting controls whether the listener can create event buckets in
|
85
82
|
# Redis. We do not want all the listeners creating buckets yet, as we do
|
86
83
|
# not know exactly the rate at which we can send events to Kinesis
|
@@ -73,36 +73,6 @@ module ThreeScale
|
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
76
|
-
class AccessTokenInvalid < NotFound
|
77
|
-
def initialize(id = nil)
|
78
|
-
super %(token "#{id}" is invalid: expired or never defined)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
class AccessTokenAlreadyExists < Error
|
83
|
-
def initialize(id = nil)
|
84
|
-
super %(token "#{id}" already exists)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
class AccessTokenStorageError < Error
|
89
|
-
def initialize(id = nil)
|
90
|
-
super %(storage error when saving token "#{id}")
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
class AccessTokenFormatInvalid < Invalid
|
95
|
-
def initialize
|
96
|
-
super 'token is either too big or has an invalid format'.freeze
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
class AccessTokenInvalidTTL < Invalid
|
101
|
-
def initialize
|
102
|
-
super 'the specified TTL should be a positive integer'.freeze
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
76
|
class ServiceNotActive < Error
|
107
77
|
def initialize
|
108
78
|
super 'service is not active'.freeze
|
@@ -182,12 +152,6 @@ module ThreeScale
|
|
182
152
|
end
|
183
153
|
end
|
184
154
|
|
185
|
-
class RequiredParamsMissing < Invalid
|
186
|
-
def initialize
|
187
|
-
super 'missing required parameters'.freeze
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
155
|
class UsageValueInvalid < Error
|
192
156
|
def initialize(metric_name, value)
|
193
157
|
if !value.is_a?(String) || value.blank?
|
@@ -30,9 +30,6 @@ module ThreeScale
|
|
30
30
|
##~ @parameter_app_id_inline = @parameter_app_id.clone
|
31
31
|
##~ @parameter_app_id_inline["description_inline"] = true
|
32
32
|
##
|
33
|
-
##~ @parameter_access_token = {"name" => "access_token", "dataType" => "string", "required" => false, "paramType" => "query", "threescale_name" => "access_tokens"}
|
34
|
-
##~ @parameter_access_token["description"] = "OAuth token used for authorizing if you don't use client_id with client_secret."
|
35
|
-
##
|
36
33
|
##~ @parameter_client_id = {"name" => "app_id", "dataType" => "string", "required" => false, "paramType" => "query", "threescale_name" => "app_ids"}
|
37
34
|
##~ @parameter_client_id["description"] = "Client Id (identifier of the application if the auth. pattern is OAuth, note that client_id == app_id)"
|
38
35
|
##~ @parameter_client_id_inline = @parameter_client_id.clone
|
@@ -114,8 +111,7 @@ module ThreeScale
|
|
114
111
|
|
115
112
|
|
116
113
|
AUTH_AUTHREP_COMMON_PARAMS = ['service_id'.freeze, 'app_id'.freeze, 'app_key'.freeze,
|
117
|
-
'user_key'.freeze, 'provider_key'.freeze
|
118
|
-
'access_token'.freeze].freeze
|
114
|
+
'user_key'.freeze, 'provider_key'.freeze].freeze
|
119
115
|
private_constant :AUTH_AUTHREP_COMMON_PARAMS
|
120
116
|
|
121
117
|
REPORT_EXPECTED_PARAMS = ['provider_key'.freeze,
|
@@ -128,8 +124,6 @@ module ThreeScale
|
|
128
124
|
disable :dump_errors
|
129
125
|
end
|
130
126
|
|
131
|
-
set :views, File.dirname(__FILE__) + '/views'
|
132
|
-
|
133
127
|
use Backend::Rack::ExceptionCatcher
|
134
128
|
|
135
129
|
before do
|
@@ -252,7 +246,7 @@ module ThreeScale
|
|
252
246
|
##~ op.summary = "Authorize (OAuth authentication mode pattern)"
|
253
247
|
##
|
254
248
|
##~ op.description = "<p>Read-only operation to authorize an application in the OAuth authentication pattern."
|
255
|
-
##~ @oauth_security = "<p>When using this endpoint please pay attention at your handling of app_id and app_key parameters. If you don't specify an app_key, the endpoint assumes the app_id specified has already been authenticated by other means. If you specify the app_key parameter, even if it is empty, it will be checked against the application's keys. If you don't trust the app_id value you have,
|
249
|
+
##~ @oauth_security = "<p>When using this endpoint please pay attention at your handling of app_id and app_key parameters. If you don't specify an app_key, the endpoint assumes the app_id specified has already been authenticated by other means. If you specify the app_key parameter, even if it is empty, it will be checked against the application's keys. If you don't trust the app_id value you have, use app keys and specify one."
|
256
250
|
##~ @oauth_desc_response = "<p>This call returns extra data (secret and redirect_url) needed to power OAuth APIs. It's only available for users with OAuth enabled APIs."
|
257
251
|
##~ op.description = op.description + @oauth_security + @oauth_desc_response
|
258
252
|
##~ op.description = op.description + " " + @authorize_desc + " " + @authorize_desc_response
|
@@ -263,7 +257,6 @@ module ThreeScale
|
|
263
257
|
##
|
264
258
|
##~ op.parameters.add @parameter_service_token
|
265
259
|
##~ op.parameters.add @parameter_service_id
|
266
|
-
##~ op.parameters.add @parameter_access_token
|
267
260
|
##~ op.parameters.add @parameter_client_id
|
268
261
|
##~ op.parameters.add @parameter_app_key_oauth
|
269
262
|
##~ op.parameters.add @parameter_referrer
|
@@ -337,7 +330,6 @@ module ThreeScale
|
|
337
330
|
##
|
338
331
|
##~ op.parameters.add @parameter_service_token
|
339
332
|
##~ op.parameters.add @parameter_service_id
|
340
|
-
##~ op.parameters.add @parameter_access_token
|
341
333
|
##~ op.parameters.add @parameter_client_id
|
342
334
|
##~ op.parameters.add @parameter_app_key_oauth
|
343
335
|
##~ op.parameters.add @parameter_referrer
|
@@ -430,62 +422,6 @@ module ThreeScale
|
|
430
422
|
202
|
431
423
|
end
|
432
424
|
|
433
|
-
## OAUTH ACCESS TOKENS
|
434
|
-
|
435
|
-
# These endpoints are deprecated and are going to be removed. For now,
|
436
|
-
# let's disable them.
|
437
|
-
if Backend.test?
|
438
|
-
post '/services/:service_id/oauth_access_tokens.xml' do
|
439
|
-
check_post_content_type!
|
440
|
-
require_params! :service_id, :token
|
441
|
-
|
442
|
-
service_id = params[:service_id]
|
443
|
-
ensure_authenticated!(params[:provider_key], params[:service_token], service_id)
|
444
|
-
|
445
|
-
app_id = params[:app_id]
|
446
|
-
raise ApplicationNotFound, app_id unless Application.exists?(service_id, app_id)
|
447
|
-
|
448
|
-
OAuth::Token::Storage.create(params[:token], service_id, app_id, params[:ttl])
|
449
|
-
end
|
450
|
-
|
451
|
-
delete '/services/:service_id/oauth_access_tokens/:token.xml' do
|
452
|
-
require_params! :service_id, :token
|
453
|
-
|
454
|
-
service_id = params[:service_id]
|
455
|
-
ensure_authenticated!(params[:provider_key], params[:service_token], service_id)
|
456
|
-
|
457
|
-
token = params[:token]
|
458
|
-
|
459
|
-
# TODO: perhaps improve this to list the deleted tokens?
|
460
|
-
raise AccessTokenInvalid, token unless OAuth::Token::Storage.delete(token, service_id)
|
461
|
-
end
|
462
|
-
|
463
|
-
get '/services/:service_id/applications/:app_id/oauth_access_tokens.xml' do
|
464
|
-
require_params! :service_id, :app_id
|
465
|
-
|
466
|
-
service_id = params[:service_id]
|
467
|
-
ensure_authenticated!(params[:provider_key], params[:service_token], service_id)
|
468
|
-
|
469
|
-
app_id = params[:app_id]
|
470
|
-
|
471
|
-
raise ApplicationNotFound, app_id unless Application.exists?(service_id, app_id)
|
472
|
-
|
473
|
-
@tokens = OAuth::Token::Storage.all_by_service_and_app service_id, app_id
|
474
|
-
builder :oauth_access_tokens
|
475
|
-
end
|
476
|
-
|
477
|
-
get '/services/:service_id/oauth_access_tokens/:token.xml' do
|
478
|
-
require_params! :service_id, :token
|
479
|
-
|
480
|
-
service_id = params[:service_id]
|
481
|
-
ensure_authenticated!(params[:provider_key], params[:service_token], service_id)
|
482
|
-
|
483
|
-
@token_to_app_id = OAuth::Token::Storage.get_credentials(params[:token], service_id)
|
484
|
-
|
485
|
-
builder :oauth_app_id_by_token
|
486
|
-
end
|
487
|
-
end
|
488
|
-
|
489
425
|
get '/check.txt' do
|
490
426
|
content_type 'text/plain'
|
491
427
|
body 'ok'
|
@@ -518,10 +454,6 @@ module ThreeScale
|
|
518
454
|
params[:usage].nil? || params[:usage].is_a?(Hash)
|
519
455
|
end
|
520
456
|
|
521
|
-
def require_params!(*keys)
|
522
|
-
raise RequiredParamsMissing unless params && keys.all? { |key| !blank?(params[key]) }
|
523
|
-
end
|
524
|
-
|
525
457
|
def check_params_value_encoding!(input_params, params_to_validate)
|
526
458
|
params_to_validate.each do |p|
|
527
459
|
param_value = input_params[p]
|
@@ -651,15 +583,6 @@ module ThreeScale
|
|
651
583
|
raise ServiceTokenInvalid.new(token, id)
|
652
584
|
end
|
653
585
|
|
654
|
-
def ensure_authenticated!(provider_key, service_token, service_id)
|
655
|
-
if blank?(provider_key)
|
656
|
-
key = provider_key_from(service_token, service_id)
|
657
|
-
raise_provider_key_error(params) if blank?(key)
|
658
|
-
elsif !Service.authenticate_service_id(service_id, provider_key)
|
659
|
-
raise ProviderKeyInvalid, provider_key
|
660
|
-
end
|
661
|
-
end
|
662
|
-
|
663
586
|
def response_auth_call(auth_status)
|
664
587
|
status(auth_status.authorized? ? 200 : 409)
|
665
588
|
optionally_set_headers(auth_status)
|
@@ -4,14 +4,34 @@ require 'rack'
|
|
4
4
|
module ThreeScale
|
5
5
|
module Backend
|
6
6
|
class ListenerMetrics
|
7
|
-
|
7
|
+
AUTH_AND_REPORT_REQUEST_TYPES = {
|
8
8
|
'/transactions/authorize.xml' => 'authorize',
|
9
9
|
'/transactions/oauth_authorize.xml' => 'authorize_oauth',
|
10
10
|
'/transactions/authrep.xml' => 'authrep',
|
11
11
|
'/transactions/oauth_authrep.xml' => 'authrep_oauth',
|
12
12
|
'/transactions.xml' => 'report'
|
13
13
|
}
|
14
|
-
private_constant :
|
14
|
+
private_constant :AUTH_AND_REPORT_REQUEST_TYPES
|
15
|
+
|
16
|
+
INTERNAL_API_PATHS = [
|
17
|
+
[/\/services\/.*\/alert_limits/, 'alerts'],
|
18
|
+
[/\/services\/.*\/applications\/.*\/keys/, 'application_keys'],
|
19
|
+
[/\/services\/.*\/applications\/.*\/referrer_filters/, 'application_referrer_filters'],
|
20
|
+
[/\/services\/.*\/applications/, 'applications'],
|
21
|
+
[/\/services\/.*\/errors/, 'errors'],
|
22
|
+
[/\/events/, 'events'],
|
23
|
+
[/\/services\/.*\/metrics/, 'metrics'],
|
24
|
+
[/\/service_tokens/, 'service_tokens'],
|
25
|
+
[/\/services/, 'services'],
|
26
|
+
[/\/services\/.*\/stats/, 'stats'],
|
27
|
+
[/\/services\/.*\/plans\/.*\/usagelimits/, 'usage_limits'],
|
28
|
+
[/\/services\/.*\/applications\/.*\/utilization/, 'utilization'],
|
29
|
+
].freeze
|
30
|
+
private_constant :INTERNAL_API_PATHS
|
31
|
+
|
32
|
+
# Most requests will be under 100ms, so use a higher granularity from there
|
33
|
+
TIME_BUCKETS = [0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.1, 0.25, 0.5, 0.75, 1]
|
34
|
+
private_constant :TIME_BUCKETS
|
15
35
|
|
16
36
|
class << self
|
17
37
|
ERRORS_4XX_TO_TRACK = Set[403, 404, 409].freeze
|
@@ -27,9 +47,12 @@ module ThreeScale
|
|
27
47
|
end
|
28
48
|
|
29
49
|
def report_resp_code(path, resp_code)
|
30
|
-
|
50
|
+
req_type = req_type(path)
|
51
|
+
prometheus_group = prometheus_group(req_type)
|
52
|
+
|
53
|
+
Yabeda.send(prometheus_group).response_codes.increment(
|
31
54
|
{
|
32
|
-
request_type:
|
55
|
+
request_type: req_type,
|
33
56
|
resp_code: code_group(resp_code)
|
34
57
|
},
|
35
58
|
by: 1
|
@@ -37,8 +60,11 @@ module ThreeScale
|
|
37
60
|
end
|
38
61
|
|
39
62
|
def report_response_time(path, request_time)
|
40
|
-
|
41
|
-
|
63
|
+
req_type = req_type(path)
|
64
|
+
prometheus_group = prometheus_group(req_type)
|
65
|
+
|
66
|
+
Yabeda.send(prometheus_group).response_times.measure(
|
67
|
+
{ request_type: req_type },
|
42
68
|
request_time
|
43
69
|
)
|
44
70
|
end
|
@@ -69,8 +95,21 @@ module ThreeScale
|
|
69
95
|
comment 'Response times'
|
70
96
|
unit :seconds
|
71
97
|
tags %i[request_type]
|
72
|
-
|
73
|
-
|
98
|
+
buckets TIME_BUCKETS
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
group :apisonator_listener_internal_api do
|
103
|
+
counter :response_codes do
|
104
|
+
comment 'Response codes'
|
105
|
+
tags %i[request_type resp_code]
|
106
|
+
end
|
107
|
+
|
108
|
+
histogram :response_times do
|
109
|
+
comment 'Response times'
|
110
|
+
unit :seconds
|
111
|
+
tags %i[request_type]
|
112
|
+
buckets TIME_BUCKETS
|
74
113
|
end
|
75
114
|
end
|
76
115
|
end
|
@@ -93,6 +132,24 @@ module ThreeScale
|
|
93
132
|
'unknown'.freeze
|
94
133
|
end
|
95
134
|
end
|
135
|
+
|
136
|
+
def req_type(path)
|
137
|
+
AUTH_AND_REPORT_REQUEST_TYPES[path] || internal_api_req_type(path)
|
138
|
+
end
|
139
|
+
|
140
|
+
def internal_api_req_type(path)
|
141
|
+
(_regex, type) = INTERNAL_API_PATHS.find { |(regex, _)| regex.match path }
|
142
|
+
type
|
143
|
+
end
|
144
|
+
|
145
|
+
# Returns the group as defined in .define_metrics
|
146
|
+
def prometheus_group(request_type)
|
147
|
+
if AUTH_AND_REPORT_REQUEST_TYPES.values.include? request_type
|
148
|
+
:apisonator_listener
|
149
|
+
else
|
150
|
+
:apisonator_listener_internal_api
|
151
|
+
end
|
152
|
+
end
|
96
153
|
end
|
97
154
|
end
|
98
155
|
end
|
@@ -8,7 +8,15 @@ module ThreeScale
|
|
8
8
|
|
9
9
|
def call(env)
|
10
10
|
began_at = Time.now.getutc
|
11
|
-
|
11
|
+
|
12
|
+
begin
|
13
|
+
status, header, body = @app.call(env)
|
14
|
+
rescue Exception => e
|
15
|
+
ListenerMetrics.report_resp_code(env['REQUEST_PATH'], 500)
|
16
|
+
ListenerMetrics.report_response_time(env['REQUEST_PATH'], Time.now - began_at)
|
17
|
+
raise e
|
18
|
+
end
|
19
|
+
|
12
20
|
ListenerMetrics.report_resp_code(env['REQUEST_PATH'], status)
|
13
21
|
ListenerMetrics.report_response_time(env['REQUEST_PATH'], Time.now - began_at)
|
14
22
|
[status, header, body]
|
@@ -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
|
@@ -30,9 +30,13 @@ module ThreeScale
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def notify(provider_key, usage)
|
33
|
-
#
|
33
|
+
# We need the master service ID to report its metrics. If it's not
|
34
|
+
# set, we don't need to notify anything.
|
35
|
+
# Batch several notifications together so that we can process just one
|
34
36
|
# job for a group of them.
|
35
|
-
|
37
|
+
unless configuration.master_service_id.to_s.empty?
|
38
|
+
notify_batch(provider_key, usage)
|
39
|
+
end
|
36
40
|
end
|
37
41
|
|
38
42
|
def notify_batch(provider_key, usage)
|
@@ -7,8 +7,6 @@ module ThreeScale
|
|
7
7
|
extend Configurable
|
8
8
|
@queue = :main
|
9
9
|
|
10
|
-
InvalidMasterServiceId = Class.new(ThreeScale::Backend::Error)
|
11
|
-
|
12
10
|
class << self
|
13
11
|
def perform_logged(provider_key, usage, timestamp, _enqueue_time)
|
14
12
|
application_id = Application.load_id_by_key(master_service_id, provider_key)
|
@@ -16,12 +14,42 @@ module ThreeScale
|
|
16
14
|
if application_id && Application.exists?(master_service_id, application_id)
|
17
15
|
master_metrics = Metric.load_all(master_service_id)
|
18
16
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
17
|
+
begin
|
18
|
+
ProcessJob.perform([{
|
19
|
+
service_id: master_service_id,
|
20
|
+
application_id: application_id,
|
21
|
+
timestamp: timestamp,
|
22
|
+
usage: master_metrics.process_usage(usage)
|
23
|
+
}])
|
24
|
+
rescue MetricInvalid => e
|
25
|
+
# This happens when the master account in Porta does not have
|
26
|
+
# the notify metrics defined (by default "transactions" and
|
27
|
+
# "transactions/authorize"). These metrics need to be created in
|
28
|
+
# Porta, Apisonator does not have a way to guarantee that
|
29
|
+
# they're defined.
|
30
|
+
# Notice that this rescue prevents the job from being retried.
|
31
|
+
# Apisonator can't know when the metrics will be created (if
|
32
|
+
# ever) so it's better to log the error rather than retrying
|
33
|
+
# these jobs for an undefined period of time.
|
34
|
+
Worker.logger.notify(e)
|
35
|
+
return [false, "#{e}"]
|
36
|
+
rescue TransactionTimestampNotWithinRange => e
|
37
|
+
# This is very unlikely to happen. The timestamps in a notify
|
38
|
+
# job are not set by users, they are set by the listeners. If
|
39
|
+
# this error happens it might mean that:
|
40
|
+
# a) The worker started processing this job way after the
|
41
|
+
# listener produced it. This can happen for example if we make
|
42
|
+
# some requests to a listener with no workers. The listeners
|
43
|
+
# will enqueue some notify jobs. If we start a worker hours
|
44
|
+
# later, we might see this error.
|
45
|
+
# b) There's some kind of clock skew issue.
|
46
|
+
# c) There's a bug.
|
47
|
+
#
|
48
|
+
# We can't raise here, because then, the job will be retried,
|
49
|
+
# but it's going to fail always if it has an old timestamp.
|
50
|
+
Worker.logger.notify(e)
|
51
|
+
return [false, "#{provider_key} #{application_id} #{e}"]
|
52
|
+
end
|
25
53
|
end
|
26
54
|
[true, "#{provider_key} #{application_id || '--'}"]
|
27
55
|
end
|
@@ -29,15 +57,7 @@ module ThreeScale
|
|
29
57
|
private
|
30
58
|
|
31
59
|
def master_service_id
|
32
|
-
|
33
|
-
|
34
|
-
unless value
|
35
|
-
raise InvalidMasterServiceId,
|
36
|
-
"Can't find master service id. Make sure the \"master_service_id\" "\
|
37
|
-
'configuration value is set correctly'
|
38
|
-
end
|
39
|
-
|
40
|
-
value.to_s
|
60
|
+
configuration.master_service_id.to_s
|
41
61
|
end
|
42
62
|
end
|
43
63
|
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
|
|
data/licenses.xml
CHANGED
@@ -23,7 +23,7 @@
|
|
23
23
|
</dependency>
|
24
24
|
<dependency>
|
25
25
|
<packageName>apisonator</packageName>
|
26
|
-
<version>
|
26
|
+
<version>3.1.0</version>
|
27
27
|
<licenses>
|
28
28
|
<license>
|
29
29
|
<name>Apache 2.0</name>
|
@@ -73,7 +73,7 @@
|
|
73
73
|
</dependency>
|
74
74
|
<dependency>
|
75
75
|
<packageName>async-io</packageName>
|
76
|
-
<version>1.27.
|
76
|
+
<version>1.27.7</version>
|
77
77
|
<licenses>
|
78
78
|
<license>
|
79
79
|
<name>MIT</name>
|
@@ -93,7 +93,7 @@
|
|
93
93
|
</dependency>
|
94
94
|
<dependency>
|
95
95
|
<packageName>async-redis</packageName>
|
96
|
-
<version>0.
|
96
|
+
<version>0.5.0</version>
|
97
97
|
<licenses>
|
98
98
|
<license>
|
99
99
|
<name>MIT</name>
|
@@ -371,7 +371,7 @@
|
|
371
371
|
</dependency>
|
372
372
|
<dependency>
|
373
373
|
<packageName>json</packageName>
|
374
|
-
<version>2.1
|
374
|
+
<version>2.3.1</version>
|
375
375
|
<licenses>
|
376
376
|
<license>
|
377
377
|
<name>ruby</name>
|
@@ -669,7 +669,7 @@
|
|
669
669
|
</dependency>
|
670
670
|
<dependency>
|
671
671
|
<packageName>protocol-redis</packageName>
|
672
|
-
<version>0.
|
672
|
+
<version>0.5.0</version>
|
673
673
|
<licenses>
|
674
674
|
<license>
|
675
675
|
<name>MIT</name>
|
@@ -719,7 +719,7 @@
|
|
719
719
|
</dependency>
|
720
720
|
<dependency>
|
721
721
|
<packageName>rack</packageName>
|
722
|
-
<version>2.
|
722
|
+
<version>2.1.4</version>
|
723
723
|
<licenses>
|
724
724
|
<license>
|
725
725
|
<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.1.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-10-14 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
|
@@ -1,25 +0,0 @@
|
|
1
|
-
# This module encodes values in Redis for our tokens
|
2
|
-
module ThreeScale
|
3
|
-
module Backend
|
4
|
-
module OAuth
|
5
|
-
class Token
|
6
|
-
module Value
|
7
|
-
# Note: this module made more sense when it also supported end-users.
|
8
|
-
# Given how simple the module is, we could get rid of it in a future
|
9
|
-
# refactor.
|
10
|
-
|
11
|
-
class << self
|
12
|
-
# this method is used when creating tokens
|
13
|
-
def for(app_id)
|
14
|
-
app_id
|
15
|
-
end
|
16
|
-
|
17
|
-
def from(value)
|
18
|
-
value
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
xml.instruct!
|
2
|
-
xml.oauth_access_tokens do
|
3
|
-
@tokens.each do |t|
|
4
|
-
# Some bright head leaked the -1 that Redis uses to indicate there is no TTL
|
5
|
-
# associated with a key. The behaviour is inconsistent because somehow we
|
6
|
-
# BOTH expect that a token with no TTL does not have a "ttl" attribute in
|
7
|
-
# the generated XML and at the same time we expect (in some tests) that such
|
8
|
-
# tokens have this field with a -1 value.
|
9
|
-
#
|
10
|
-
# Just enforce the ttl to be -1 when there is none.
|
11
|
-
attrs = { :ttl => t.ttl || -1 }
|
12
|
-
xml.oauth_access_token t.token, attrs
|
13
|
-
end
|
14
|
-
end
|