frodo 0.12.2 → 0.12.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 026c6e3af2cf841015fe7b99ab24b6025c989c39f4ecfdc34b7e2f6335be0d54
4
- data.tar.gz: cff43f6fd873888c0525a032410f2e0955e40e758d4ad23b28764e6b22c19d68
3
+ metadata.gz: 6171a906fa516145d1ea9bbff61f771cdbf9b936ceb815071ac93390ab9775c2
4
+ data.tar.gz: 0f2c9b9d8d3c0311edd87b8a624ca8a6c807662a2522b3acfeffcdb29abb8b8b
5
5
  SHA512:
6
- metadata.gz: 6660e988e7f76473c5e8eed25498cf4dd5fc7186da8017ae9ef7aa84a628f0f7378976c894fd292eb535a63e22d027986a4ea32858d87e6de757fc0e1ad55531
7
- data.tar.gz: 46db130f68dc67d7bb74418e535a7b2cb923fbbf3126d17ec0c2fd797949f254a73c31e47ca3b09b122a5f4c51ebd015bb5ac59469966e3319c6d94316e4add0
6
+ metadata.gz: be855e21a2371911d083c079059cd79473826f310334e11b75bda47d9cc59d883da3649587b308bec88b1bb2a0af779cec8bc29acc5bae3532ecf84049fc8be3
7
+ data.tar.gz: 1de688ddb8d1695c59d29d42f92d14e1a86572d4cc2e0fd16268fe7f1a7a4bd8e56e145265aeb3b000ca4053f5ff4ac74328b20db31740053e93f03375e2fa43
data/README.md CHANGED
@@ -52,7 +52,7 @@ building an application where many users from different orgs are authenticated
52
52
  through oauth and you need to interact with data in their org on their behalf,
53
53
  you should use the OAuth token authentication method.
54
54
 
55
- This is currently the only supported method. This may change overtime
55
+ The "client credentials", and "password" flows are also supported.
56
56
 
57
57
  It is also important to note that the client object should not be reused across different threads, otherwise you may encounter [thread-safety issues](https://www.youtube.com/watch?v=p5zQOkyCACc).
58
58
 
@@ -98,6 +98,37 @@ The proc is passed one argument, a `Hash` of the response, similar than the one
98
98
 
99
99
  The `id` field can be used to [uniquely identify](https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-protocols-oauth-code#refreshing-the-access-tokens) the user that the `access_token` and `refresh_token` belong to.
100
100
 
101
+ #### Client credentials authentication
102
+
103
+ This should be considered experimental. At the time of this writing, it was possible to obtain an access token using this method, but it was not possible to use the access token without receiving 401 from Dynamics. Configuration is as above, but you specify `client_id`, `client_secret`, and `tenant_id`:
104
+
105
+ ```ruby
106
+ client = Frodo.new({
107
+ instance_url: 'instance url',
108
+ client_id: 'client_id',
109
+ client_secret: 'client_secret',
110
+ tenant_id: 'tenant_id',
111
+ authentication_callback: Proc.new { |x| Rails.logger.debug x.to_s },
112
+ base_path: '/path/to/service'
113
+ })
114
+ ```
115
+
116
+ #### User password authentication
117
+
118
+ As above, but you specify `client_id`, `tenant_id`, `username`, and `password`:
119
+
120
+ ```ruby
121
+ client = Frodo.new({
122
+ instance_url: 'instance url',
123
+ client_id: 'client_id',
124
+ tenant_id: 'tenant_id',
125
+ username: 'username',
126
+ password: 'password',
127
+ authentication_callback: Proc.new { |x| Rails.logger.debug x.to_s },
128
+ base_path: '/path/to/service'
129
+ })
130
+ ```
131
+
101
132
  ### Proxy Support
102
133
 
103
134
  You can specify a HTTP proxy using the `proxy_uri` option, as follows, or by setting the `FRODATA_PROXY_URI` environment variable:
@@ -16,8 +16,31 @@ module Frodo
16
16
  # Internal: Determines what middleware will be used based on the options provided
17
17
  def authentication_middleware
18
18
  if oauth_refresh?
19
- Frodo::Middleware::Authentication::Token
19
+ return Frodo::Middleware::Authentication::Token
20
20
  end
21
+ if password?
22
+ return Frodo::Middleware::Authentication::Password
23
+ end
24
+ if client_credentials?
25
+ return Frodo::Middleware::Authentication::ClientCredentials
26
+ end
27
+ end
28
+
29
+ # Internal: Returns true if oauth password grant
30
+ # should be used for authentication.
31
+ def password?
32
+ options[:username] &&
33
+ options[:password] &&
34
+ options[:client_id] &&
35
+ options[:tenant_id]
36
+ end
37
+
38
+ # Internal: Returns true if oauth client_credentials flow should be used for
39
+ # authentication.
40
+ def client_credentials?
41
+ options[:tenant_id] &&
42
+ options[:client_id] &&
43
+ options[:client_secret]
21
44
  end
22
45
 
23
46
  # Internal: Returns true if oauth token refresh flow should be used for
@@ -24,7 +24,12 @@ module Frodo
24
24
  #
25
25
  # :client_id - The oauth client id to use. Needed for both
26
26
  # password and oauth authentication
27
- # :client_secret - The oauth client secret to use.
27
+ # :client_secret - The oauth client secret to use. Required for
28
+ # client_credentials auth flow
29
+ # :tenant_id - The Azure AD tenant id. Required for
30
+ # client_credentials and password auth flows
31
+ # :username - User's dynamics username for password auth flow
32
+ # :password - User's dynamics password for password auth flow
28
33
  #
29
34
  # :host - The String hostname to use during
30
35
  # authentication requests
@@ -0,0 +1,10 @@
1
+ module Frodo
2
+ # Authentication middleware used if client_id, client_secret, and client_credentials: true are set
3
+ class Middleware::Authentication::ClientCredentials < Frodo::Middleware::Authentication
4
+ def params
5
+ { grant_type: 'client_credentials',
6
+ client_id: @options[:client_id],
7
+ client_secret: @options[:client_secret] }
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ module Frodo
2
+ # Authentication middleware used if client_id, client_secret, and client_credentials: true are set
3
+ class Middleware::Authentication::Password < Frodo::Middleware::Authentication
4
+ def params
5
+ { grant_type: 'password',
6
+ client_id: @options[:client_id],
7
+ username: @options[:username],
8
+ password: @options[:password],
9
+ resource: @options[:instance_url],
10
+ }
11
+ end
12
+ end
13
+ end
@@ -6,7 +6,9 @@ module Frodo
6
6
  # will attempt to either reauthenticate (username and password) or refresh
7
7
  # the oauth access token (if a refresh token is present).
8
8
  class Middleware::Authentication < Frodo::Middleware
9
- autoload :Token, 'frodo/middleware/authentication/token'
9
+ autoload :Token, 'frodo/middleware/authentication/token'
10
+ autoload :ClientCredentials, 'frodo/middleware/authentication/client_credentials'
11
+ autoload :Password, 'frodo/middleware/authentication/password'
10
12
 
11
13
  # Rescue from 401's, authenticate then raise the error again so the client
12
14
  # can reissue the request.
@@ -19,7 +21,7 @@ module Frodo
19
21
 
20
22
  # Internal: Performs the authentication and returns the response body.
21
23
  def authenticate!
22
- response = connection.post '/common/oauth2/token' do |req|
24
+ response = connection.post token_endpoint do |req|
23
25
  req.body = encode_www_form(params)
24
26
  end
25
27
 
@@ -29,8 +31,8 @@ module Frodo
29
31
  raise Frodo::AuthenticationError, error_message(response)
30
32
  end
31
33
 
32
- @options[:oauth_token] = response.body['access_token']
33
- @options[:refresh_token] = response.body['refresh_token']
34
+ @options[:oauth_token] = response.body['access_token']
35
+ @options[:refresh_token] = response.body['refresh_token']
34
36
  @options[:authentication_callback]&.call(response.body)
35
37
 
36
38
  response.body
@@ -83,5 +85,9 @@ module Frodo
83
85
  proxy: @options[:proxy_uri],
84
86
  ssl: @options[:ssl] }
85
87
  end
88
+
89
+ def token_endpoint
90
+ "/#{@options[:tenant_id] || 'common'}/oauth2/token"
91
+ end
86
92
  end
87
93
  end
data/lib/frodo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Frodo
2
- VERSION = '0.12.2'
2
+ VERSION = '0.12.4'
3
3
  end
@@ -0,0 +1,9 @@
1
+ {
2
+ "token_type": "Bearer",
3
+ "expires_in": "3600",
4
+ "ext_expires_in": "3600",
5
+ "expires_on": "1573802322",
6
+ "not_before": "1573798422",
7
+ "resource": "00000002-0000-0000-c000-000000000000",
8
+ "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsI"
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "error":"invalid_client",
3
+ "error_description":"AADSTS7000215: Invalid client secret is provided.\r\nTrace ID: c95cc528-b2ca-4eef-9030-b4039aeb0500\r\nCorrelation ID: dee56428-96b1-471a-a666-2d45560df657\r\nTimestamp: 2019-11-15 06:26:46Z",
4
+ "error_codes":[7000215],
5
+ "timestamp":"2019-11-15 06:26:46Z",
6
+ "trace_id":"c95cc528-b2ca-4eef-9030-b4039aeb0500",
7
+ "correlation_id":"dee56428-96b1-471a-a666-2d45560df657",
8
+ "error_uri":"https://login.microsoftonline.com/error?code=7000215"
9
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "error": "invalid_grant",
3
+ "error_description": "AADSTS50126: Invalid username or password.\r\nTrace ID: 4f54f595-fd1f-4edf-8990-a08bb24e6600\r\nCorrelation ID: 54c414ed-ffc7-4187-871a-df2a6896bc8b\r\nTimestamp: 2019-11-15 18:43:57Z",
4
+ "error_codes": [
5
+ 50126
6
+ ],
7
+ "timestamp": "2019-11-15 18:43:57Z",
8
+ "trace_id": "4f54f595-fd1f-4edf-8990-a08bb24e6600",
9
+ "correlation_id": "54c414ed-ffc7-4187-871a-df2a6896bc8b",
10
+ "error_uri": "https://login.microsoftonline.com/error?code=50126"
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "token_type": "Bearer",
3
+ "scope": "user_impersonation",
4
+ "expires_in": "3599",
5
+ "ext_expires_in": "3599",
6
+ "expires_on": "1573846156",
7
+ "not_before": "1573842256",
8
+ "resource": "https://getoutreach.crm.dynamics.com",
9
+ "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOi",
10
+ "refresh_token": "AQABAAAAAACQN9QBRU3jT6bcBQLZ"
11
+ }
@@ -50,7 +50,36 @@ describe Frodo::Concerns::Authentication do
50
50
  expect(client).to receive(:oauth_refresh?).and_return(true)
51
51
  end
52
52
 
53
- it { should eq Frodo::Middleware::Authentication::Token }
53
+ it { should eq Frodo::Middleware::Authentication::Token }
54
+ end
55
+
56
+ context 'when client_credentials options are provided' do
57
+ before do
58
+ expect(client).to receive(:oauth_refresh?).and_return(false)
59
+ expect(client).to receive(:client_credentials?).and_return(true)
60
+ end
61
+
62
+ it { should eq Frodo::Middleware::Authentication::ClientCredentials }
63
+ end
64
+
65
+ context 'when password options are provided' do
66
+ before do
67
+ allow(client).to receive(:oauth_refresh?).and_return(false)
68
+ allow(client).to receive(:client_credentials?).and_return(false)
69
+ allow(client).to receive(:password?).and_return(true)
70
+ end
71
+
72
+ it { should eq Frodo::Middleware::Authentication::Password }
73
+ end
74
+
75
+ context 'when no auth options are provided' do
76
+ before do
77
+ expect(client).to receive(:oauth_refresh?).and_return(false)
78
+ expect(client).to receive(:password?).and_return(false)
79
+ expect(client).to receive(:client_credentials?).and_return(false)
80
+ end
81
+
82
+ it { should be_falsy }
54
83
  end
55
84
  end
56
85
 
@@ -76,4 +105,52 @@ describe Frodo::Concerns::Authentication do
76
105
  it { should_not be_truthy }
77
106
  end
78
107
  end
108
+
109
+ describe '.client_credentials?' do
110
+ subject { client.client_credentials? }
111
+ let(:options) { {} }
112
+
113
+ before do
114
+ expect(client).to receive(:options).and_return(options).at_least(1).times
115
+ end
116
+
117
+ context 'when oauth options are provided' do
118
+ let(:options) do
119
+ { tenant_id: 'tenant',
120
+ client_id: 'client',
121
+ client_secret: 'secret' }
122
+ end
123
+
124
+ it { should be_truthy}
125
+ end
126
+
127
+ context 'when oauth options are not provided' do
128
+ it { should_not be_truthy }
129
+ end
130
+ end
131
+
132
+ describe '.password?' do
133
+ subject { client.password? }
134
+ let(:options) { {} }
135
+
136
+ before do
137
+ expect(client).to receive(:options).and_return(options).at_least(1).times
138
+ end
139
+
140
+ context 'when oauth options are provided' do
141
+ let(:options) do
142
+ { tenant_id: 'tenant',
143
+ client_id: 'client',
144
+ username: 'username',
145
+ password: 'password',
146
+ }
147
+ end
148
+
149
+ it { should be_truthy}
150
+ end
151
+
152
+ context 'when oauth options are not provided' do
153
+ it { should_not be_truthy }
154
+ end
155
+ end
79
156
  end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'webmock/rspec'
5
+
6
+ describe Frodo::Middleware::Authentication::ClientCredentials do
7
+ include WebMock::API
8
+
9
+ describe 'authentication middleware' do
10
+ let(:options) do
11
+ { client_id: 'client_id',
12
+ client_secret: 'client_secret',
13
+ tenant_id: 'tenant_id',
14
+ adapter: :net_http,
15
+ host: 'login.window.net'
16
+ }
17
+ end
18
+
19
+ let(:success_request) do
20
+ stub_request(:post, "https://login.window.net/tenant_id/oauth2/token").with(
21
+ body: "grant_type=client_credentials&" \
22
+ "client_id=client_id&client_secret=client_secret"
23
+ ).to_return(status: 200, body: fixture("client_credentials_auth_success_response"))
24
+ end
25
+
26
+ let(:fail_request) do
27
+ stub_request(:post, "https://login.window.net/tenant_id/oauth2/token").with(
28
+ body: "grant_type=client_credentials&" \
29
+ "client_id=client_id&client_secret=client_secret"
30
+ ).to_return(status: 400, body: fixture("client_credentials_refresh_error_response"))
31
+ end
32
+
33
+ describe '.authenticate!' do
34
+ context 'when successful' do
35
+ let!(:request) { success_request }
36
+
37
+ describe '@options' do
38
+ subject { options }
39
+
40
+ before do
41
+ middleware.authenticate!
42
+ end
43
+
44
+ it { expect(subject[:host]).to eq 'login.window.net' }
45
+
46
+ it { expect(subject[:oauth_token]).to eq "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsI" }
47
+
48
+ it { expect(subject[:refresh_token]).to be_nil }
49
+ end
50
+
51
+ context 'when an authentication_callback is specified' do
52
+ before(:each) do
53
+ options.merge!(authentication_callback: auth_callback)
54
+ end
55
+
56
+ it 'calls the authentication callback with the response body' do
57
+ expect(auth_callback).to receive(:call)
58
+ middleware.authenticate!
59
+ end
60
+ end
61
+ end
62
+
63
+ context 'when unsuccessful' do
64
+ let!(:request) { fail_request }
65
+
66
+ it 'raises an exception' do
67
+ expect {
68
+ middleware.authenticate!
69
+ }.to raise_error Frodo::AuthenticationError
70
+ end
71
+
72
+ context 'when an authentication_callback is specified' do
73
+ before(:each) do
74
+ options.merge!(authentication_callback: auth_callback)
75
+ end
76
+
77
+ it 'does not call the authentication callback' do
78
+ expect(auth_callback).to_not receive(:call)
79
+ expect do
80
+ middleware.authenticate!
81
+ end.to raise_error(Frodo::AuthenticationError)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'webmock/rspec'
5
+
6
+ describe Frodo::Middleware::Authentication::Password do
7
+ include WebMock::API
8
+
9
+ describe 'authentication middleware' do
10
+ let(:options) do
11
+ { client_id: 'client_id',
12
+ tenant_id: 'tenant_foo_id',
13
+ username: 'username',
14
+ password: 'password',
15
+ adapter: :net_http,
16
+ host: 'login.window.net',
17
+ instance_url: "https://endpoint.example.com",
18
+ }
19
+ end
20
+
21
+ let(:success_request) do
22
+ stub_request(:post, "https://login.window.net/#{options[:tenant_id]}/oauth2/token").with(
23
+ body: {
24
+ "client_id"=>options[:client_id], "grant_type"=>"password", "password"=>options[:password],
25
+ "resource"=>options[:instance_url], "username"=>options[:username]
26
+ },
27
+ ).to_return(status: 200, body: fixture("password_auth_success_response"))
28
+ end
29
+
30
+ let(:fail_request) do
31
+ stub_request(:post, "https://login.window.net/#{options[:tenant_id]}/oauth2/token").with(
32
+ body: {
33
+ "client_id"=>options[:client_id], "grant_type"=>"password", "password"=>options[:password],
34
+ "resource"=>options[:instance_url], "username"=>options[:username]
35
+ },
36
+ ).to_return(status: 400, body: fixture("password_auth_failure_response"))
37
+ end
38
+
39
+ describe '.authenticate!' do
40
+ context 'when successful' do
41
+ let!(:request) { success_request }
42
+
43
+ describe '@options' do
44
+ subject { options }
45
+
46
+ before do
47
+ middleware.authenticate!
48
+ end
49
+
50
+ it { expect(subject[:host]).to eq 'login.window.net' }
51
+
52
+ it { expect(subject[:oauth_token]).to eq "eyJ0eXAiOiJKV1QiLCJhbGciOi" }
53
+
54
+ it { expect(subject[:refresh_token]).to eq 'AQABAAAAAACQN9QBRU3jT6bcBQLZ' }
55
+ end
56
+
57
+ context 'when an authentication_callback is specified' do
58
+ before(:each) do
59
+ options.merge!(authentication_callback: auth_callback)
60
+ end
61
+
62
+ it 'calls the authentication callback with the response body' do
63
+ expect(auth_callback).to receive(:call)
64
+ middleware.authenticate!
65
+ end
66
+ end
67
+ end
68
+
69
+ context 'when unsuccessful' do
70
+ let!(:request) { fail_request }
71
+
72
+ it 'raises an exception' do
73
+ expect {
74
+ middleware.authenticate!
75
+ }.to raise_error Frodo::AuthenticationError
76
+ end
77
+
78
+ context 'when an authentication_callback is specified' do
79
+ before(:each) do
80
+ options.merge!(authentication_callback: auth_callback)
81
+ end
82
+
83
+ it 'does not call the authentication callback' do
84
+ expect(auth_callback).to_not receive(:call)
85
+ expect do
86
+ middleware.authenticate!
87
+ end.to raise_error(Frodo::AuthenticationError)
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -78,7 +78,7 @@ describe Frodo::Middleware::Authentication::Token do
78
78
  expect(auth_callback).to_not receive(:call)
79
79
  expect do
80
80
  middleware.authenticate!
81
- end.to raise_error
81
+ end.to raise_error(Frodo::AuthenticationError)
82
82
  end
83
83
  end
84
84
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: frodo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.2
4
+ version: 0.12.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emmanuel Pinault
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-14 00:00:00.000000000 Z
11
+ date: 2019-11-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -250,6 +250,8 @@ files:
250
250
  - lib/frodo/entity_set.rb
251
251
  - lib/frodo/middleware.rb
252
252
  - lib/frodo/middleware/authentication.rb
253
+ - lib/frodo/middleware/authentication/client_credentials.rb
254
+ - lib/frodo/middleware/authentication/password.rb
253
255
  - lib/frodo/middleware/authentication/token.rb
254
256
  - lib/frodo/middleware/authorization.rb
255
257
  - lib/frodo/middleware/caching.rb
@@ -302,6 +304,8 @@ files:
302
304
  - lib/frodo/service_registry.rb
303
305
  - lib/frodo/version.rb
304
306
  - spec/fixtures/auth_success_response.json
307
+ - spec/fixtures/client_credentials_auth_success_response.json
308
+ - spec/fixtures/client_credentials_refresh_error_response.json
305
309
  - spec/fixtures/error.json
306
310
  - spec/fixtures/files/entity_to_xml.xml
307
311
  - spec/fixtures/files/error.xml
@@ -315,6 +319,8 @@ files:
315
319
  - spec/fixtures/files/supplier_0.json
316
320
  - spec/fixtures/files/supplier_0.xml
317
321
  - spec/fixtures/leads.json
322
+ - spec/fixtures/password_auth_failure_response.json
323
+ - spec/fixtures/password_auth_success_response.json
318
324
  - spec/fixtures/refresh_error_response.json
319
325
  - spec/frodo/abstract_client_spec.rb
320
326
  - spec/frodo/client_spec.rb
@@ -328,6 +334,8 @@ files:
328
334
  - spec/frodo/entity_container_spec.rb
329
335
  - spec/frodo/entity_set_spec.rb
330
336
  - spec/frodo/entity_spec.rb
337
+ - spec/frodo/middleware/authentication/client_credentials_spec.rb
338
+ - spec/frodo/middleware/authentication/password_spec.rb
331
339
  - spec/frodo/middleware/authentication/token_spec.rb
332
340
  - spec/frodo/middleware/authentication_spec.rb
333
341
  - spec/frodo/middleware/authorization_spec.rb
@@ -396,6 +404,8 @@ specification_version: 4
396
404
  summary: Simple OData library
397
405
  test_files:
398
406
  - spec/fixtures/auth_success_response.json
407
+ - spec/fixtures/client_credentials_auth_success_response.json
408
+ - spec/fixtures/client_credentials_refresh_error_response.json
399
409
  - spec/fixtures/error.json
400
410
  - spec/fixtures/files/entity_to_xml.xml
401
411
  - spec/fixtures/files/error.xml
@@ -409,6 +419,8 @@ test_files:
409
419
  - spec/fixtures/files/supplier_0.json
410
420
  - spec/fixtures/files/supplier_0.xml
411
421
  - spec/fixtures/leads.json
422
+ - spec/fixtures/password_auth_failure_response.json
423
+ - spec/fixtures/password_auth_success_response.json
412
424
  - spec/fixtures/refresh_error_response.json
413
425
  - spec/frodo/abstract_client_spec.rb
414
426
  - spec/frodo/client_spec.rb
@@ -422,6 +434,8 @@ test_files:
422
434
  - spec/frodo/entity_container_spec.rb
423
435
  - spec/frodo/entity_set_spec.rb
424
436
  - spec/frodo/entity_spec.rb
437
+ - spec/frodo/middleware/authentication/client_credentials_spec.rb
438
+ - spec/frodo/middleware/authentication/password_spec.rb
425
439
  - spec/frodo/middleware/authentication/token_spec.rb
426
440
  - spec/frodo/middleware/authentication_spec.rb
427
441
  - spec/frodo/middleware/authorization_spec.rb