stitches 5.1.0.RC2 → 5.1.0.RC4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 74c049e2d66d366adce255f0fd8d41b6eb0e121669225b1fbce4dbd8ccb19b10
4
- data.tar.gz: 8d8b6b8d5cc2e57b4e21a5a9097ccd14b462087c82ce6c30cfd2e6b4617c49c5
3
+ metadata.gz: 5e1a4aefc1d0249b7b5c8caf2eb06aa6e0866afef4f1d4564d63efa239ef80ce
4
+ data.tar.gz: 322ab581e74df9bb933d8a1e23d2dc3973c6e3494ebfac23424f16c25cbf589a
5
5
  SHA512:
6
- metadata.gz: 343c659a9e37f29caadad39b2c0d66496f164efea60e9a499daec8bb156fd5e90801af304d2bc0ac307c00ee4934b400ea4f22db4e7a996b9d7d114751a5a55c
7
- data.tar.gz: dfaa3c860b35fbbbd44ba3c99d4ac1a1ce820de42393ddaa9d93cb12fb0062ad03d14cfb2a7f0ab64dfe5fa83ecd72271c30aa222923ba9fc5cecb35501d8311
6
+ metadata.gz: 5da26d78de12b20c29f2cff453dae8b1b1712ab3f0bbcd373291082eeee1feae3f22dcfb841c512f6a351243f10894b1c91dbc6c5c58400c182be9c0e96ee93c
7
+ data.tar.gz: 1a2ef89f2e74dfd9478158dfa747c19989de5f32425363f77ae428058468e57d934e29decae0fb1b1bc39a60b1b18abb97c8c82bfd3dfde73ac0b449dc13c995
data/.circleci/config.yml CHANGED
@@ -26,6 +26,9 @@ parameters:
26
26
  type: string
27
27
  default: "8.1.2"
28
28
 
29
+ orbs:
30
+ stitchfix_build: stitchfix/build@dev:master
31
+
29
32
  jobs:
30
33
  generate-and-push-docs:
31
34
  docker:
@@ -45,29 +48,12 @@ jobs:
45
48
  docs:generate:custom ; elif [[ $(bundle exec rake -T docs:generate) ]];
46
49
  then echo "Generating docs using rake task docs:generate" ; bundle exec
47
50
  rake docs:generate ; else echo "Skipping doc generation" ; exit 0 ; fi '
51
+ - stitchfix_build/aws_cli_setup_eng
48
52
  - run:
49
53
  name: Push documentation to Unwritten
50
54
  command:
51
55
  if [[ $(bundle exec rake -T docs:push) ]]; then bundle exec rake
52
56
  docs:push; fi
53
- release:
54
- docker:
55
- - image: cimg/ruby:3.3.7
56
- auth:
57
- username: "$DOCKERHUB_USERNAME"
58
- password: "$DOCKERHUB_PASSWORD"
59
- steps:
60
- - checkout
61
- - run: bundle config stitchfix01.jfrog.io $ARTIFACTORY_USER:$ARTIFACTORY_TOKEN
62
- - run: bundle install
63
- - run:
64
- name: Artifactory login
65
- command:
66
- mkdir -p ~/.gem && curl -u$ARTIFACTORY_USER:$ARTIFACTORY_TOKEN https://stitchfix01.jfrog.io/stitchfix01/api/gems/eng-gems/api/v1/api_key.yaml
67
- > ~/.gem/credentials && chmod 0600 ~/.gem/credentials
68
- - run:
69
- name: Build/release gem to artifactory
70
- command: bundle exec rake push_artifactory
71
57
  test:
72
58
  parameters:
73
59
  ruby-version:
@@ -87,6 +73,7 @@ jobs:
87
73
  auth:
88
74
  username: "$DOCKERHUB_USERNAME"
89
75
  password: "$DOCKERHUB_PASSWORD"
76
+ resource_class: medium.gen2
90
77
  working_directory: "~/stitches"
91
78
  environment:
92
79
  DATABASE_URL: "postgres://postgres:@localhost:5432/stitches_fake_app_test"
@@ -136,19 +123,10 @@ workflows:
136
123
  unless:
137
124
  equal: ["schedule", << pipeline.parameters.GHA_Event >>]
138
125
  jobs:
139
- - release:
140
- context: org-global
141
- requires:
142
- - test
143
- filters:
144
- tags:
145
- only: "/^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:(-|\\.)(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$/"
146
- branches:
147
- ignore: /.*/
148
126
  - generate-and-push-docs:
149
127
  context: org-global
150
128
  requires:
151
- - release
129
+ - test
152
130
  filters:
153
131
  tags:
154
132
  only: "/^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:(-|\\.)(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$/"
@@ -0,0 +1,15 @@
1
+ module Stitches
2
+ # Lightweight stand-in for ApiClient when the caller is identified by
3
+ # the X-StitchFix-Calling-Service header rather than an API key lookup.
4
+ # Implements the same interface (.name, .id, .key) so existing code
5
+ # that reads api_client.name continues to work.
6
+ CallingServiceClient = Struct.new(:name) do
7
+ def id
8
+ nil
9
+ end
10
+
11
+ def key
12
+ nil
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,29 @@
1
+ require_relative 'calling_service_client'
2
+
3
+ module Stitches
4
+ # Rack middleware that populates the api_client env var from the
5
+ # X-StitchFix-Calling-Service header when no other auth middleware
6
+ # has already set it. This allows existing code that reads
7
+ # api_client.name to work transparently after API keys are disabled.
8
+ class CallingServiceMiddleware
9
+ def initialize(app)
10
+ @app = app
11
+ end
12
+
13
+ def call(env)
14
+ client_key = Stitches.configuration.env_var_to_hold_api_client
15
+
16
+ unless env[client_key]
17
+ header_name = Stitches.configuration.calling_service_header
18
+ header_name = CallingServiceName::DEFAULT_CALLING_SERVICE_HEADER unless header_name.present?
19
+ rack_key = "HTTP_#{header_name.upcase.tr('-', '_')}"
20
+
21
+ if (name = env[rack_key]).present?
22
+ env[client_key] = CallingServiceClient.new(name)
23
+ end
24
+ end
25
+
26
+ @app.call(env)
27
+ end
28
+ end
29
+ end
@@ -1,12 +1,17 @@
1
1
  module Stitches
2
2
  module CallingServiceName
3
- HEADER = "X-StitchFix-Calling-Service"
3
+ DEFAULT_CALLING_SERVICE_HEADER = "X-StitchFix-Calling-Service"
4
4
 
5
5
  def calling_service_name
6
6
  @calling_service_name ||=
7
- request.headers[HEADER].presence ||
8
- (respond_to?(:api_client, true) && api_client&.name) ||
9
- ""
7
+ request.headers[calling_service_header_name].presence || ""
8
+ end
9
+
10
+ private
11
+
12
+ def calling_service_header_name
13
+ configured = Stitches.configuration.calling_service_header
14
+ configured.present? ? configured : DEFAULT_CALLING_SERVICE_HEADER
10
15
  end
11
16
  end
12
17
  end
@@ -13,6 +13,7 @@ class Stitches::Configuration
13
13
  @custom_http_auth_scheme = UnsetString.new("custom_http_auth_scheme")
14
14
  @env_var_to_hold_api_client_primary_key = NonNullString.new("env_var_to_hold_api_client_primary_key","STITCHES_API_CLIENT_ID")
15
15
  @env_var_to_hold_api_client= NonNullString.new("env_var_to_hold_api_client","STITCHES_API_CLIENT")
16
+ @calling_service_header = NonNullString.new("calling_service_header", "X-StitchFix-Calling-Service")
16
17
  @max_cache_ttl = NonNullInteger.new("max_cache_ttl", 0)
17
18
  @max_cache_size = NonNullInteger.new("max_cache_size", 0)
18
19
  @disabled_key_leniency_in_seconds = ActiveSupport::Duration.days(3)
@@ -61,6 +62,16 @@ class Stitches::Configuration
61
62
  @env_var_to_hold_api_client= NonNullString.new("env_var_to_hold_api_client",new_env_var_to_hold_api_client)
62
63
  end
63
64
 
65
+ # The name of the HTTP header used to identify the calling service.
66
+ # Clients send this header; servers read it via CallingServiceName.
67
+ def calling_service_header
68
+ @calling_service_header.to_s
69
+ end
70
+
71
+ def calling_service_header=(new_calling_service_header)
72
+ @calling_service_header = NonNullString.new("calling_service_header", new_calling_service_header)
73
+ end
74
+
64
75
  def max_cache_ttl
65
76
  @max_cache_ttl.to_i
66
77
  end
@@ -1,10 +1,12 @@
1
1
  require 'stitches/api_key'
2
2
  require 'stitches/valid_mime_type'
3
3
  require 'stitches/api_client_access_wrapper'
4
+ require 'stitches/calling_service_middleware'
4
5
 
5
6
  module Stitches
6
7
  class Railtie < Rails::Railtie
7
8
  config.app_middleware.use Stitches::ApiKey
9
+ config.app_middleware.use Stitches::CallingServiceMiddleware
8
10
  config.app_middleware.use Stitches::ValidMimeType
9
11
  end
10
12
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Stitches
4
- VERSION = '5.1.0.RC2'
4
+ VERSION = '5.1.0.RC4'
5
5
  end
@@ -0,0 +1,72 @@
1
+ require 'rails_helper'
2
+
3
+ describe Stitches::CallingServiceMiddleware do
4
+ let(:app) { ->(env) { [200, env, "OK"] } }
5
+ let(:middleware) { described_class.new(app) }
6
+ let(:env) { Rack::MockRequest.env_for("/api/test", method: "POST") }
7
+
8
+ let(:client_key) { Stitches.configuration.env_var_to_hold_api_client }
9
+
10
+ describe "#call" do
11
+ context "when the header is present and env var is not set" do
12
+ before { env["HTTP_X_STITCHFIX_CALLING_SERVICE"] = "kingmob" }
13
+
14
+ it "populates the env var with a CallingServiceClient" do
15
+ _status, result_env, _body = middleware.call(env)
16
+ client = result_env[client_key]
17
+
18
+ expect(client).to be_a(Stitches::CallingServiceClient)
19
+ expect(client.name).to eq("kingmob")
20
+ expect(client.id).to be_nil
21
+ expect(client.key).to be_nil
22
+ end
23
+ end
24
+
25
+ context "when the env var is already set (API key or JWT auth ran)" do
26
+ let(:existing_client) { double("ApiClient", name: "existing-client", id: 42) }
27
+
28
+ before do
29
+ env[client_key] = existing_client
30
+ env["HTTP_X_STITCHFIX_CALLING_SERVICE"] = "some-service"
31
+ end
32
+
33
+ it "does not overwrite the existing value" do
34
+ _status, result_env, _body = middleware.call(env)
35
+ expect(result_env[client_key]).to eq(existing_client)
36
+ end
37
+ end
38
+
39
+ context "when the header is absent and env var is not set" do
40
+ it "leaves the env var nil" do
41
+ _status, result_env, _body = middleware.call(env)
42
+ expect(result_env[client_key]).to be_nil
43
+ end
44
+ end
45
+
46
+ context "when the header is blank" do
47
+ before { env["HTTP_X_STITCHFIX_CALLING_SERVICE"] = "" }
48
+
49
+ it "leaves the env var nil" do
50
+ _status, result_env, _body = middleware.call(env)
51
+ expect(result_env[client_key]).to be_nil
52
+ end
53
+ end
54
+
55
+ context "with a custom configured header" do
56
+ before do
57
+ Stitches.configuration.calling_service_header = "X-Custom-Caller"
58
+ env["HTTP_X_CUSTOM_CALLER"] = "custom-service"
59
+ end
60
+
61
+ after { Stitches.configuration.reset_to_defaults! }
62
+
63
+ it "reads from the configured header" do
64
+ _status, result_env, _body = middleware.call(env)
65
+ client = result_env[client_key]
66
+
67
+ expect(client).to be_a(Stitches::CallingServiceClient)
68
+ expect(client.name).to eq("custom-service")
69
+ end
70
+ end
71
+ end
72
+ end
@@ -3,91 +3,52 @@ require 'rails_helper'
3
3
  describe Stitches::CallingServiceName do
4
4
  let(:headers) { {} }
5
5
  let(:fake_request) { double("request", headers: headers) }
6
- let(:fake_api_client) { nil }
7
6
  let(:fake_controller) {
8
7
  req = fake_request
9
- client = fake_api_client
10
8
  Object.new.tap { |c|
11
9
  c.extend(described_class)
12
10
  c.define_singleton_method(:request) { req }
13
- c.define_singleton_method(:api_client) { client }
14
11
  }
15
12
  }
16
13
 
17
14
  describe "#calling_service_name" do
18
- context "when X-StitchFix-Calling-Service header is present" do
15
+ context "when the header is present" do
19
16
  let(:headers) { {"X-StitchFix-Calling-Service" => "kingmob"} }
20
17
 
21
18
  it "returns the header value" do
22
19
  expect(fake_controller.calling_service_name).to eq("kingmob")
23
20
  end
24
-
25
- context "and api_client is also present" do
26
- let(:fake_api_client) { double("ApiClient", name: "other-service") }
27
-
28
- it "prefers the header" do
29
- expect(fake_controller.calling_service_name).to eq("kingmob")
30
- end
31
- end
32
- end
33
-
34
- context "when header is absent but api_client is present" do
35
- let(:fake_api_client) { double("ApiClient", name: "mobile-service") }
36
-
37
- it "returns the api_client name" do
38
- expect(fake_controller.calling_service_name).to eq("mobile-service")
39
- end
40
- end
41
-
42
- context "when header is blank" do
43
- let(:headers) { {"X-StitchFix-Calling-Service" => ""} }
44
- let(:fake_api_client) { double("ApiClient", name: "fallback-service") }
45
-
46
- it "treats blank as absent and falls through to api_client" do
47
- expect(fake_controller.calling_service_name).to eq("fallback-service")
48
- end
49
21
  end
50
22
 
51
- context "when neither header nor api_client is present" do
52
- it "returns 'unknown'" do
23
+ context "when the header is absent" do
24
+ it "returns empty string" do
53
25
  expect(fake_controller.calling_service_name).to eq("")
54
26
  end
55
27
  end
56
28
 
57
- context "when api_client is nil" do
58
- let(:fake_api_client) { nil }
29
+ context "when the header is blank" do
30
+ let(:headers) { {"X-StitchFix-Calling-Service" => ""} }
59
31
 
60
32
  it "returns empty string" do
61
33
  expect(fake_controller.calling_service_name).to eq("")
62
34
  end
63
35
  end
36
+ end
64
37
 
65
- context "when api_client method is not defined" do
66
- let(:fake_controller) {
67
- req = fake_request
68
- Object.new.tap { |c|
69
- c.extend(described_class)
70
- c.define_singleton_method(:request) { req }
71
- }
72
- }
38
+ describe "configurable header" do
39
+ it "defaults to X-StitchFix-Calling-Service" do
40
+ expect(Stitches.configuration.calling_service_header).to eq("X-StitchFix-Calling-Service")
41
+ end
73
42
 
74
- it "returns empty string" do
75
- expect(fake_controller.calling_service_name).to eq("")
76
- end
43
+ context "when configured to a custom header" do
44
+ let(:headers) { {"X-Custom-Caller" => "my-service"} }
77
45
 
78
- context "and header is present" do
79
- let(:headers) { {"X-StitchFix-Calling-Service" => "fixops"} }
46
+ before { Stitches.configuration.calling_service_header = "X-Custom-Caller" }
47
+ after { Stitches.configuration.reset_to_defaults! }
80
48
 
81
- it "returns the header value" do
82
- expect(fake_controller.calling_service_name).to eq("fixops")
83
- end
49
+ it "reads from the configured header" do
50
+ expect(fake_controller.calling_service_name).to eq("my-service")
84
51
  end
85
52
  end
86
53
  end
87
-
88
- describe "::HEADER" do
89
- it "is the expected header name" do
90
- expect(described_class::HEADER).to eq("X-StitchFix-Calling-Service")
91
- end
92
- end
93
54
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stitches
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.1.0.RC2
4
+ version: 5.1.0.RC4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stitch Fix Engineering
@@ -174,6 +174,8 @@ files:
174
174
  - lib/stitches/api_key.rb
175
175
  - lib/stitches/api_migration_generator.rb
176
176
  - lib/stitches/api_version_constraint.rb
177
+ - lib/stitches/calling_service_client.rb
178
+ - lib/stitches/calling_service_middleware.rb
177
179
  - lib/stitches/calling_service_name.rb
178
180
  - lib/stitches/configuration.rb
179
181
  - lib/stitches/deprecation.rb
@@ -209,6 +211,7 @@ files:
209
211
  - owners.json
210
212
  - spec/api_key_middleware_spec.rb
211
213
  - spec/api_version_constraint_middleware_spec.rb
214
+ - spec/calling_service_middleware_spec.rb
212
215
  - spec/calling_service_name_spec.rb
213
216
  - spec/configuration_spec.rb
214
217
  - spec/deprecation_spec.rb
@@ -306,6 +309,7 @@ summary: You'll be in stitches at how easy it is to create a service at Stitch F
306
309
  test_files:
307
310
  - spec/api_key_middleware_spec.rb
308
311
  - spec/api_version_constraint_middleware_spec.rb
312
+ - spec/calling_service_middleware_spec.rb
309
313
  - spec/calling_service_name_spec.rb
310
314
  - spec/configuration_spec.rb
311
315
  - spec/deprecation_spec.rb