stitches 4.1.0RC2 → 4.2.0.RC1
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.
- checksums.yaml +4 -4
- data/.circleci/config.yml +0 -38
- data/.gitignore +1 -0
- data/README.md +36 -4
- data/lib/stitches/add_disabled_at_to_api_clients_generator.rb +18 -0
- data/lib/stitches/allowlist_middleware.rb +20 -6
- data/lib/stitches/api_client_access_wrapper.rb +42 -11
- data/lib/stitches/api_key.rb +5 -5
- data/lib/stitches/configuration.rb +4 -0
- data/lib/stitches/generator_files/db/migrate/add_disabled_at_to_api_clients.rb +9 -0
- data/lib/stitches/generator_files/db/migrate/create_api_clients.rb +1 -0
- data/lib/stitches/render_timestamps_in_iso8601_in_json.rb +2 -6
- data/lib/stitches/valid_mime_type.rb +1 -1
- data/lib/stitches/version.rb +1 -1
- data/lib/stitches_norailtie.rb +1 -0
- data/spec/api_key_middleware_spec.rb +368 -0
- data/spec/api_version_constraint_middleware_spec.rb +58 -0
- data/spec/configuration_spec.rb +1 -1
- data/spec/deprecation_spec.rb +1 -1
- data/spec/error_spec.rb +1 -1
- data/spec/errors_spec.rb +3 -3
- data/spec/fake_app/.rspec +1 -0
- data/spec/fake_app/.ruby-version +1 -0
- data/spec/fake_app/Gemfile +53 -0
- data/spec/fake_app/README.md +24 -0
- data/spec/fake_app/Rakefile +6 -0
- data/spec/fake_app/app/assets/config/manifest.js +2 -0
- data/spec/fake_app/app/assets/stylesheets/application.css +15 -0
- data/spec/fake_app/app/controllers/api.rb +2 -0
- data/spec/fake_app/app/controllers/api/api_controller.rb +31 -0
- data/spec/fake_app/app/controllers/api/v1.rb +2 -0
- data/spec/fake_app/app/controllers/api/v1/hellos_controller.rb +7 -0
- data/spec/fake_app/app/controllers/api/v1/pings_controller.rb +16 -0
- data/spec/fake_app/app/controllers/api/v2.rb +2 -0
- data/spec/fake_app/app/controllers/api/v2/hellos_controller.rb +7 -0
- data/spec/fake_app/app/controllers/api/v2/pings_controller.rb +16 -0
- data/spec/fake_app/app/controllers/application_controller.rb +2 -0
- data/spec/fake_app/app/helpers/application_helper.rb +2 -0
- data/spec/fake_app/app/models/api_client.rb +2 -0
- data/spec/fake_app/app/models/application_record.rb +3 -0
- data/spec/fake_app/bin/rails +4 -0
- data/spec/fake_app/bin/rake +4 -0
- data/spec/fake_app/bin/setup +33 -0
- data/spec/fake_app/config.ru +6 -0
- data/spec/fake_app/config/application.rb +35 -0
- data/spec/fake_app/config/boot.rb +3 -0
- data/spec/fake_app/config/credentials.yml.enc +1 -0
- data/spec/fake_app/config/database.yml +25 -0
- data/spec/fake_app/config/environment.rb +5 -0
- data/spec/fake_app/config/environments/development.rb +71 -0
- data/spec/fake_app/config/environments/production.rb +109 -0
- data/spec/fake_app/config/environments/test.rb +52 -0
- data/spec/fake_app/config/initializers/assets.rb +12 -0
- data/spec/fake_app/config/initializers/cookies_serializer.rb +5 -0
- data/spec/fake_app/config/initializers/filter_parameter_logging.rb +6 -0
- data/spec/fake_app/config/initializers/stitches.rb +24 -0
- data/spec/fake_app/config/locales/en.yml +33 -0
- data/spec/fake_app/config/master.key +1 -0
- data/spec/fake_app/config/puma.rb +43 -0
- data/spec/fake_app/config/routes.rb +17 -0
- data/spec/fake_app/config/storage.yml +34 -0
- data/spec/fake_app/db/development.sqlite3 +0 -0
- data/spec/fake_app/db/migrate/20210802153118_enable_uuid_ossp_extension.rb +7 -0
- data/spec/fake_app/db/migrate/20210802153119_create_api_clients.rb +14 -0
- data/spec/fake_app/db/schema_missing_disabled_at.rb +12 -0
- data/spec/fake_app/db/schema_missing_enabled.rb +11 -0
- data/spec/fake_app/db/schema_modern.rb +13 -0
- data/spec/fake_app/db/seeds.rb +7 -0
- data/spec/fake_app/db/test.sqlite3 +0 -0
- data/spec/fake_app/doc/api.md +4 -0
- data/spec/fake_app/lib/tasks/generate_api_key.rake +10 -0
- data/spec/fake_app/public/404.html +67 -0
- data/spec/fake_app/public/422.html +67 -0
- data/spec/fake_app/public/500.html +66 -0
- data/spec/fake_app/public/apple-touch-icon-precomposed.png +0 -0
- data/spec/fake_app/public/apple-touch-icon.png +0 -0
- data/spec/fake_app/public/favicon.ico +0 -0
- data/spec/fake_app/public/javascripts/apitome/application.js +31 -0
- data/spec/fake_app/public/robots.txt +1 -0
- data/spec/fake_app/public/stylesheets/apitome/application.css +269 -0
- data/spec/fake_app/test/application_system_test_case.rb +5 -0
- data/spec/fake_app/test/test_helper.rb +13 -0
- data/spec/fake_app/tmp/development_secret.txt +1 -0
- data/spec/integration/add_to_rails_app_spec.rb +9 -1
- data/spec/rails_helper.rb +64 -0
- data/spec/valid_mime_type_middleware_spec.rb +59 -0
- data/spec/valid_mime_type_spec.rb +6 -4
- data/stitches.gemspec +2 -0
- metadata +165 -9
- data/spec/api_client_access_wrapper_spec.rb +0 -52
- data/spec/api_key_spec.rb +0 -208
- data/spec/api_version_constraint_spec.rb +0 -33
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
require 'spec_helper.rb'
|
|
2
|
-
|
|
3
|
-
module MyApp
|
|
4
|
-
class Application
|
|
5
|
-
end
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
unless defined? ApiClient
|
|
9
|
-
class ApiClient
|
|
10
|
-
def self.column_names
|
|
11
|
-
["enabled"]
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
describe Stitches::ApiClientAccessWrapper do
|
|
17
|
-
let(:api_client) {
|
|
18
|
-
double(ApiClient, id: 42)
|
|
19
|
-
}
|
|
20
|
-
before do
|
|
21
|
-
Stitches.configuration.reset_to_defaults!
|
|
22
|
-
end
|
|
23
|
-
describe '#fetch_by_key' do
|
|
24
|
-
context "cache is disabled" do
|
|
25
|
-
before do
|
|
26
|
-
expect(ApiClient).to receive(:find_by).and_return(api_client).twice
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
it "fetchs object from db twice" do
|
|
30
|
-
expect(described_class.fetch_for_key("123").id).to eq(42)
|
|
31
|
-
expect(described_class.fetch_for_key("123").id).to eq(42)
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
context "cache is configured" do
|
|
36
|
-
before do
|
|
37
|
-
Stitches.configure do |config|
|
|
38
|
-
config.max_cache_ttl = 5
|
|
39
|
-
config.max_cache_size = 10
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
expect(ApiClient).to receive(:find_by).and_return(api_client).once
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
it "fetchs object from cache" do
|
|
46
|
-
expect(described_class.fetch_for_key("123").id).to eq(42)
|
|
47
|
-
# This should hit the cache
|
|
48
|
-
expect(described_class.fetch_for_key("123").id).to eq(42)
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
data/spec/api_key_spec.rb
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
require 'spec_helper.rb'
|
|
2
|
-
|
|
3
|
-
module MyApp
|
|
4
|
-
class Application
|
|
5
|
-
end
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
unless defined? ApiClient
|
|
9
|
-
class ApiClient
|
|
10
|
-
def self.column_names
|
|
11
|
-
["enabled"]
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
describe Stitches::ApiKey do
|
|
17
|
-
let(:app) { double("rack app") }
|
|
18
|
-
let(:api_client) {
|
|
19
|
-
double(ApiClient, id: 42)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
before do
|
|
23
|
-
Stitches.configuration.reset_to_defaults!
|
|
24
|
-
Stitches.configuration.custom_http_auth_scheme = 'MyAwesomeInternalScheme'
|
|
25
|
-
fake_rails_app = MyApp::Application.new
|
|
26
|
-
allow(Rails).to receive(:application).and_return(fake_rails_app)
|
|
27
|
-
allow(app).to receive(:call).with(env)
|
|
28
|
-
allow(ApiClient).to receive(:find_by).and_return(api_client)
|
|
29
|
-
Stitches::ApiClientAccessWrapper.clear_api_cache
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
subject(:middleware) { described_class.new(app, namespace: "/api") }
|
|
33
|
-
|
|
34
|
-
shared_examples "an unauthorized response" do
|
|
35
|
-
it "returns a 401" do
|
|
36
|
-
status, _headers, _body = @response
|
|
37
|
-
expect(status).to eq(401)
|
|
38
|
-
end
|
|
39
|
-
it "sets the proper header" do
|
|
40
|
-
_status, headers, _body = @response
|
|
41
|
-
expect(headers["WWW-Authenticate"]).to eq("MyAwesomeInternalScheme realm=MyApp")
|
|
42
|
-
end
|
|
43
|
-
it "stops the call chain preventing anything from happening" do
|
|
44
|
-
expect(app).not_to have_received(:call)
|
|
45
|
-
end
|
|
46
|
-
it "sends a reasonable message" do
|
|
47
|
-
_status, _headers, body = @response
|
|
48
|
-
expect(body).to eq([expected_body])
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
describe "#call" do
|
|
53
|
-
context "not in namespace" do
|
|
54
|
-
context "not allowlisted" do
|
|
55
|
-
let(:env) {
|
|
56
|
-
{
|
|
57
|
-
"PATH_INFO" => "/index/apifoolingyou/home",
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
before do
|
|
62
|
-
@response = middleware.call(env)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
it_behaves_like "an unauthorized response" do
|
|
66
|
-
let(:expected_body) { "Unauthorized - no authorization header" }
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
context "allowlisting" do
|
|
70
|
-
context "allowlist is explicit in middleware usage" do
|
|
71
|
-
before do
|
|
72
|
-
@response = middleware.call(env)
|
|
73
|
-
end
|
|
74
|
-
context "passes the allowlist" do
|
|
75
|
-
subject(:middleware) { described_class.new(app, except: %r{\A/resque\/.*\Z}) }
|
|
76
|
-
let(:env) {
|
|
77
|
-
{
|
|
78
|
-
"PATH_INFO" => "/resque/overview"
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
it "calls through to the rest of the chain" do
|
|
82
|
-
expect(app).to have_received(:call).with(env)
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
context "fails the allowlist" do
|
|
87
|
-
subject(:middleware) { described_class.new(app, except: %r{\A/resque\/.*\Z}) }
|
|
88
|
-
let(:env) {
|
|
89
|
-
{
|
|
90
|
-
"PATH_INFO" => "//resque/overview" # subtle
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
it_behaves_like "an unauthorized response" do
|
|
94
|
-
let(:expected_body) { "Unauthorized - no authorization header" }
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
context "except: is not given a regexp" do
|
|
98
|
-
let(:env) {
|
|
99
|
-
{
|
|
100
|
-
"PATH_INFO" => "//resque/overview" # subtle
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
it "blows up" do
|
|
104
|
-
expect {
|
|
105
|
-
described_class.new(app, except: "/resque")
|
|
106
|
-
}.to raise_error(/must be a Regexp/i)
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
context "allowlist is implicit from the configuration" do
|
|
111
|
-
|
|
112
|
-
before do
|
|
113
|
-
Stitches.configuration.allowlist_regexp = %r{\A/resque/.*\Z}
|
|
114
|
-
@response = middleware.call(env)
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
context "passes the allowlist" do
|
|
118
|
-
subject(:middleware) { described_class.new(app) }
|
|
119
|
-
let(:env) {
|
|
120
|
-
{
|
|
121
|
-
"PATH_INFO" => "/resque/overview"
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
it "calls through to the rest of the chain" do
|
|
125
|
-
expect(app).to have_received(:call).with(env)
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
context "fails the allowlist" do
|
|
130
|
-
subject(:middleware) { described_class.new(app) }
|
|
131
|
-
let(:env) {
|
|
132
|
-
{
|
|
133
|
-
"PATH_INFO" => "//resque/overview" # subtle
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
it_behaves_like "an unauthorized response" do
|
|
137
|
-
let(:expected_body) { "Unauthorized - no authorization header" }
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
context "valid key" do
|
|
145
|
-
let(:env) {
|
|
146
|
-
{
|
|
147
|
-
"PATH_INFO" => "/api/ping",
|
|
148
|
-
"HTTP_AUTHORIZATION" => "MyAwesomeInternalScheme key=foobar",
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
before do
|
|
153
|
-
@response = middleware.call(env)
|
|
154
|
-
end
|
|
155
|
-
it "calls through to the rest of the chain" do
|
|
156
|
-
expect(app).to have_received(:call).with(env)
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
it "sets the api_client's ID in the environment" do
|
|
160
|
-
expect(env[Stitches.configuration.env_var_to_hold_api_client_primary_key]).to eq(api_client.id)
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
it "sets the api_client itself in the environment" do
|
|
164
|
-
expect(env[Stitches.configuration.env_var_to_hold_api_client]).to eq(api_client)
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
context "unauthorized responses" do
|
|
169
|
-
before do
|
|
170
|
-
@response = middleware.call(env)
|
|
171
|
-
end
|
|
172
|
-
context "invalid key" do
|
|
173
|
-
let(:env) {
|
|
174
|
-
{
|
|
175
|
-
"PATH_INFO" => "/api/ping",
|
|
176
|
-
"HTTP_AUTHORIZATION" => "MyAwesomeInternalScheme key=foobar",
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
let(:api_client) { nil }
|
|
180
|
-
|
|
181
|
-
it_behaves_like "an unauthorized response" do
|
|
182
|
-
let(:expected_body) { "Unauthorized - key invalid" }
|
|
183
|
-
end
|
|
184
|
-
end
|
|
185
|
-
context "bad authorization type" do
|
|
186
|
-
let(:env) {
|
|
187
|
-
{
|
|
188
|
-
"PATH_INFO" => "/api/ping",
|
|
189
|
-
"HTTP_AUTHORIZATION" => "foobar",
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
it_behaves_like "an unauthorized response" do
|
|
193
|
-
let(:expected_body) { "Unauthorized - bad authorization type" }
|
|
194
|
-
end
|
|
195
|
-
end
|
|
196
|
-
context "no auth header" do
|
|
197
|
-
let(:env) {
|
|
198
|
-
{
|
|
199
|
-
"PATH_INFO" => "/api/ping",
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
it_behaves_like "an unauthorized response" do
|
|
203
|
-
let(:expected_body) { "Unauthorized - no authorization header" }
|
|
204
|
-
end
|
|
205
|
-
end
|
|
206
|
-
end
|
|
207
|
-
end
|
|
208
|
-
end
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
require 'spec_helper.rb'
|
|
2
|
-
|
|
3
|
-
describe Stitches::ApiVersionConstraint do
|
|
4
|
-
let(:version) { 2 }
|
|
5
|
-
let(:request) { double("request", headers: headers) }
|
|
6
|
-
|
|
7
|
-
subject(:constraint) { described_class.new(version) }
|
|
8
|
-
|
|
9
|
-
context "no accept header" do
|
|
10
|
-
let(:headers) { {} }
|
|
11
|
-
it "doesn't match" do
|
|
12
|
-
expect(constraint.matches?(request)).to eq(false)
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
context "accept header missing version" do
|
|
16
|
-
let(:headers) { { accept: "application/json" } }
|
|
17
|
-
it "doesn't match" do
|
|
18
|
-
expect(constraint.matches?(request)).to eq(false)
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
context "accept header has wrong version" do
|
|
22
|
-
let(:headers) { { accept: "application/json; version=1" } }
|
|
23
|
-
it "doesn't match" do
|
|
24
|
-
expect(constraint.matches?(request)).to eq(false)
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
context "accept header has correct version" do
|
|
28
|
-
let(:headers) { { accept: "application/json; version=2" } }
|
|
29
|
-
it "matcheds" do
|
|
30
|
-
expect(constraint.matches?(request)).to eq(true)
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|