flipper-cloud 0.28.3 → 1.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,63 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "flipper/cloud/message_verifier"
4
-
5
- module Flipper
6
- module Cloud
7
- class Middleware
8
- # Internal: The path to match for webhook requests.
9
- WEBHOOK_PATH = %r{\A/webhooks\/?\Z}
10
- # Internal: The root path to match for requests.
11
- ROOT_PATH = %r{\A/\Z}
12
-
13
- def initialize(app, options = {})
14
- @app = app
15
- @env_key = options.fetch(:env_key, 'flipper')
16
- end
17
-
18
- def call(env)
19
- dup.call!(env)
20
- end
21
-
22
- def call!(env)
23
- request = Rack::Request.new(env)
24
- if request.post? && (request.path_info.match(ROOT_PATH) || request.path_info.match(WEBHOOK_PATH))
25
- status = 200
26
- headers = {
27
- "Content-Type" => "application/json",
28
- }
29
- body = "{}"
30
- payload = request.body.read
31
- signature = request.env["HTTP_FLIPPER_CLOUD_SIGNATURE"]
32
- flipper = env.fetch(@env_key)
33
-
34
- begin
35
- message_verifier = MessageVerifier.new(secret: flipper.sync_secret)
36
- if message_verifier.verify(payload, signature)
37
- begin
38
- flipper.sync
39
- body = JSON.generate({
40
- groups: Flipper.group_names.map { |name| {name: name}}
41
- })
42
- rescue Flipper::Adapters::Http::Error => error
43
- status = error.response.code.to_i == 402 ? 402 : 500
44
- headers["Flipper-Cloud-Response-Error-Class"] = error.class.name
45
- headers["Flipper-Cloud-Response-Error-Message"] = error.message
46
- rescue => error
47
- status = 500
48
- headers["Flipper-Cloud-Response-Error-Class"] = error.class.name
49
- headers["Flipper-Cloud-Response-Error-Message"] = error.message
50
- end
51
- end
52
- rescue MessageVerifier::InvalidSignature
53
- status = 400
54
- end
55
-
56
- [status, headers, [body]]
57
- else
58
- @app.call(env)
59
- end
60
- end
61
- end
62
- end
63
- end
@@ -1,13 +0,0 @@
1
- # Default routes loaded by Flipper::Cloud::Engine
2
- Rails.application.routes.draw do
3
- if ENV["FLIPPER_CLOUD_TOKEN"] && ENV["FLIPPER_CLOUD_SYNC_SECRET"]
4
- config = Rails.application.config.flipper
5
-
6
- cloud_app = Flipper::Cloud.app(nil,
7
- env_key: config.env_key,
8
- memoizer_options: { preload: config.preload }
9
- )
10
-
11
- mount cloud_app, at: config.cloud_path
12
- end
13
- end
data/lib/flipper/cloud.rb DELETED
@@ -1,57 +0,0 @@
1
- require "flipper"
2
- require "flipper/middleware/setup_env"
3
- require "flipper/middleware/memoizer"
4
- require "flipper/cloud/configuration"
5
- require "flipper/cloud/dsl"
6
- require "flipper/cloud/middleware"
7
- require "flipper/cloud/engine" if defined?(Rails::Engine)
8
-
9
- module Flipper
10
- module Cloud
11
- # Public: Returns a new Flipper instance with an http adapter correctly
12
- # configured for flipper cloud.
13
- #
14
- # token - The String token for the environment from the website.
15
- # options - The Hash of options. See Flipper::Cloud::Configuration.
16
- # block - The block that configuration will be yielded to allowing you to
17
- # customize this cloud instance and its adapter.
18
- def self.new(options = {})
19
- configuration = Configuration.new(options)
20
- yield configuration if block_given?
21
- DSL.new(configuration)
22
- end
23
-
24
- def self.app(flipper = nil, options = {})
25
- env_key = options.fetch(:env_key, 'flipper')
26
- memoizer_options = options.fetch(:memoizer_options, {})
27
-
28
- app = ->(_) { [404, { 'Content-Type'.freeze => 'application/json'.freeze }, ['{}'.freeze]] }
29
- builder = Rack::Builder.new
30
- yield builder if block_given?
31
- builder.use Flipper::Middleware::SetupEnv, flipper, env_key: env_key
32
- builder.use Flipper::Middleware::Memoizer, memoizer_options.merge(env_key: env_key)
33
- builder.use Flipper::Cloud::Middleware, env_key: env_key
34
- builder.run app
35
- klass = self
36
- app = builder.to_app
37
- app.define_singleton_method(:inspect) { klass.inspect } # pretty rake routes output
38
- app
39
- end
40
-
41
- # Private: Configure Flipper to use Cloud by default
42
- def self.set_default
43
- Flipper.configure do |config|
44
- config.default do
45
- if ENV["FLIPPER_CLOUD_TOKEN"]
46
- self.new(local_adapter: config.adapter)
47
- else
48
- warn "Missing FLIPPER_CLOUD_TOKEN environment variable. Disabling Flipper::Cloud."
49
- Flipper.new(config.adapter)
50
- end
51
- end
52
- end
53
- end
54
- end
55
- end
56
-
57
- Flipper::Cloud.set_default
@@ -1,261 +0,0 @@
1
- require 'flipper/cloud/configuration'
2
- require 'flipper/adapters/instrumented'
3
-
4
- RSpec.describe Flipper::Cloud::Configuration do
5
- let(:required_options) do
6
- { token: "asdf" }
7
- end
8
-
9
- it "can set token" do
10
- instance = described_class.new(required_options)
11
- expect(instance.token).to eq(required_options[:token])
12
- end
13
-
14
- it "can set token from ENV var" do
15
- ENV["FLIPPER_CLOUD_TOKEN"] = "from_env"
16
- instance = described_class.new(required_options.reject { |k, v| k == :token })
17
- expect(instance.token).to eq("from_env")
18
- end
19
-
20
- it "can set instrumenter" do
21
- instrumenter = Object.new
22
- instance = described_class.new(required_options.merge(instrumenter: instrumenter))
23
- expect(instance.instrumenter).to be(instrumenter)
24
- end
25
-
26
- it "can set read_timeout" do
27
- instance = described_class.new(required_options.merge(read_timeout: 5))
28
- expect(instance.read_timeout).to eq(5)
29
- end
30
-
31
- it "can set read_timeout from ENV var" do
32
- ENV["FLIPPER_CLOUD_READ_TIMEOUT"] = "9"
33
- instance = described_class.new(required_options.reject { |k, v| k == :read_timeout })
34
- expect(instance.read_timeout).to eq(9)
35
- end
36
-
37
- it "can set open_timeout" do
38
- instance = described_class.new(required_options.merge(open_timeout: 5))
39
- expect(instance.open_timeout).to eq(5)
40
- end
41
-
42
- it "can set open_timeout from ENV var" do
43
- ENV["FLIPPER_CLOUD_OPEN_TIMEOUT"] = "9"
44
- instance = described_class.new(required_options.reject { |k, v| k == :open_timeout })
45
- expect(instance.open_timeout).to eq(9)
46
- end
47
-
48
- it "can set write_timeout" do
49
- instance = described_class.new(required_options.merge(write_timeout: 5))
50
- expect(instance.write_timeout).to eq(5)
51
- end
52
-
53
- it "can set write_timeout from ENV var" do
54
- ENV["FLIPPER_CLOUD_WRITE_TIMEOUT"] = "9"
55
- instance = described_class.new(required_options.reject { |k, v| k == :write_timeout })
56
- expect(instance.write_timeout).to eq(9)
57
- end
58
-
59
- it "can set sync_interval" do
60
- instance = described_class.new(required_options.merge(sync_interval: 1))
61
- expect(instance.sync_interval).to eq(1)
62
- end
63
-
64
- it "can set sync_interval from ENV var" do
65
- ENV["FLIPPER_CLOUD_SYNC_INTERVAL"] = "5"
66
- instance = described_class.new(required_options.reject { |k, v| k == :sync_interval })
67
- expect(instance.sync_interval).to eq(5)
68
- end
69
-
70
- it "passes sync_interval into sync adapter" do
71
- # The initial sync of http to local invokes this web request.
72
- stub_request(:get, /flippercloud\.io/).to_return(status: 200, body: "{}")
73
-
74
- instance = described_class.new(required_options.merge(sync_interval: 1))
75
- poller = instance.send(:poller)
76
- expect(poller.interval).to eq(1)
77
- end
78
-
79
- it "can set debug_output" do
80
- instance = described_class.new(required_options.merge(debug_output: STDOUT))
81
- expect(instance.debug_output).to eq(STDOUT)
82
- end
83
-
84
- it "defaults adapter block" do
85
- # The initial sync of http to local invokes this web request.
86
- stub_request(:get, /flippercloud\.io/).to_return(status: 200, body: "{}")
87
-
88
- instance = described_class.new(required_options)
89
- expect(instance.adapter).to be_instance_of(Flipper::Adapters::DualWrite)
90
- end
91
-
92
- it "can override adapter block" do
93
- # The initial sync of http to local invokes this web request.
94
- stub_request(:get, /flippercloud\.io/).to_return(status: 200, body: "{}")
95
-
96
- instance = described_class.new(required_options)
97
- instance.adapter do |adapter|
98
- Flipper::Adapters::Instrumented.new(adapter)
99
- end
100
- expect(instance.adapter).to be_instance_of(Flipper::Adapters::Instrumented)
101
- end
102
-
103
- it "defaults url" do
104
- instance = described_class.new(required_options.reject { |k, v| k == :url })
105
- expect(instance.url).to eq("https://www.flippercloud.io/adapter")
106
- end
107
-
108
- it "can override url using options" do
109
- options = required_options.merge(url: "http://localhost:5000/adapter")
110
- instance = described_class.new(options)
111
- expect(instance.url).to eq("http://localhost:5000/adapter")
112
-
113
- instance = described_class.new(required_options)
114
- instance.url = "http://localhost:5000/adapter"
115
- expect(instance.url).to eq("http://localhost:5000/adapter")
116
- end
117
-
118
- it "can override URL using ENV var" do
119
- ENV["FLIPPER_CLOUD_URL"] = "https://example.com"
120
- instance = described_class.new(required_options.reject { |k, v| k == :url })
121
- expect(instance.url).to eq("https://example.com")
122
- end
123
-
124
- it "defaults sync_method to :poll" do
125
- instance = described_class.new(required_options)
126
-
127
- expect(instance.sync_method).to eq(:poll)
128
- end
129
-
130
- it "sets sync_method to :webhook if sync_secret provided" do
131
- instance = described_class.new(required_options.merge({
132
- sync_secret: "secret",
133
- }))
134
-
135
- expect(instance.sync_method).to eq(:webhook)
136
- expect(instance.adapter).to be_instance_of(Flipper::Adapters::DualWrite)
137
- end
138
-
139
- it "sets sync_method to :webhook if FLIPPER_CLOUD_SYNC_SECRET set" do
140
- ENV["FLIPPER_CLOUD_SYNC_SECRET"] = "abc"
141
- instance = described_class.new(required_options)
142
-
143
- expect(instance.sync_method).to eq(:webhook)
144
- expect(instance.adapter).to be_instance_of(Flipper::Adapters::DualWrite)
145
- end
146
-
147
- it "can set sync_secret" do
148
- instance = described_class.new(required_options.merge(sync_secret: "from_config"))
149
- expect(instance.sync_secret).to eq("from_config")
150
- end
151
-
152
- it "can override sync_secret using ENV var" do
153
- ENV["FLIPPER_CLOUD_SYNC_SECRET"] = "from_env"
154
- instance = described_class.new(required_options.reject { |k, v| k == :sync_secret })
155
- expect(instance.sync_secret).to eq("from_env")
156
- end
157
-
158
- it "can sync with cloud" do
159
- body = JSON.generate({
160
- "features": [
161
- {
162
- "key": "search",
163
- "state": "on",
164
- "gates": [
165
- {
166
- "key": "boolean",
167
- "name": "boolean",
168
- "value": true
169
- },
170
- {
171
- "key": "groups",
172
- "name": "group",
173
- "value": []
174
- },
175
- {
176
- "key": "actors",
177
- "name": "actor",
178
- "value": []
179
- },
180
- {
181
- "key": "percentage_of_actors",
182
- "name": "percentage_of_actors",
183
- "value": 0
184
- },
185
- {
186
- "key": "percentage_of_time",
187
- "name": "percentage_of_time",
188
- "value": 0
189
- }
190
- ]
191
- },
192
- {
193
- "key": "history",
194
- "state": "off",
195
- "gates": [
196
- {
197
- "key": "boolean",
198
- "name": "boolean",
199
- "value": false
200
- },
201
- {
202
- "key": "groups",
203
- "name": "group",
204
- "value": []
205
- },
206
- {
207
- "key": "actors",
208
- "name": "actor",
209
- "value": []
210
- },
211
- {
212
- "key": "percentage_of_actors",
213
- "name": "percentage_of_actors",
214
- "value": 0
215
- },
216
- {
217
- "key": "percentage_of_time",
218
- "name": "percentage_of_time",
219
- "value": 0
220
- }
221
- ]
222
- }
223
- ]
224
- })
225
- stub = stub_request(:get, "https://www.flippercloud.io/adapter/features?exclude_gate_names=true").
226
- with({
227
- headers: {
228
- 'Flipper-Cloud-Token'=>'asdf',
229
- },
230
- }).to_return(status: 200, body: body, headers: {})
231
- instance = described_class.new(required_options)
232
- instance.sync
233
-
234
- # Check that remote was fetched.
235
- expect(stub).to have_been_requested
236
-
237
- # Check that local adapter really did sync.
238
- local_adapter = instance.local_adapter
239
- all = local_adapter.get_all
240
- expect(all.keys).to eq(["search", "history"])
241
- expect(all["search"][:boolean]).to eq("true")
242
- expect(all["history"][:boolean]).to eq(nil)
243
- end
244
-
245
- it "can setup brow to report events to cloud" do
246
- # skip logging brow
247
- Brow.logger = Logger.new(File::NULL)
248
- brow = described_class.new(required_options).brow
249
-
250
- stub = stub_request(:post, "https://www.flippercloud.io/adapter/events")
251
- .with { |request|
252
- data = JSON.parse(request.body)
253
- data.keys == ["uuid", "messages"] && data["messages"] == [{"n" => 1}]
254
- }
255
- .to_return(status: 201, body: "{}", headers: {})
256
-
257
- brow.push({"n" => 1})
258
- brow.worker.stop
259
- expect(stub).to have_been_requested.times(1)
260
- end
261
- end
@@ -1,82 +0,0 @@
1
- require 'flipper/cloud/configuration'
2
- require 'flipper/cloud/dsl'
3
- require 'flipper/adapters/operation_logger'
4
- require 'flipper/adapters/instrumented'
5
-
6
- RSpec.describe Flipper::Cloud::DSL do
7
- it 'delegates everything to flipper instance' do
8
- cloud_configuration = Flipper::Cloud::Configuration.new({
9
- token: "asdf",
10
- sync_secret: "tasty",
11
- })
12
- dsl = described_class.new(cloud_configuration)
13
- expect(dsl.features).to eq(Set.new)
14
- expect(dsl.enabled?(:foo)).to be(false)
15
- end
16
-
17
- it 'delegates sync to cloud configuration' do
18
- stub = stub_request(:get, "https://www.flippercloud.io/adapter/features?exclude_gate_names=true").
19
- with({
20
- headers: {
21
- 'Flipper-Cloud-Token'=>'asdf',
22
- },
23
- }).to_return(status: 200, body: '{"features": {}}', headers: {})
24
- cloud_configuration = Flipper::Cloud::Configuration.new({
25
- token: "asdf",
26
- sync_secret: "tasty",
27
- })
28
- dsl = described_class.new(cloud_configuration)
29
- dsl.sync
30
- expect(stub).to have_been_requested
31
- end
32
-
33
- it 'delegates sync_secret to cloud configuration' do
34
- cloud_configuration = Flipper::Cloud::Configuration.new({
35
- token: "asdf",
36
- sync_secret: "tasty",
37
- })
38
- dsl = described_class.new(cloud_configuration)
39
- expect(dsl.sync_secret).to eq("tasty")
40
- end
41
-
42
- context "when sync_method is webhook" do
43
- let(:local_adapter) do
44
- Flipper::Adapters::OperationLogger.new Flipper::Adapters::Memory.new
45
- end
46
-
47
- let(:cloud_configuration) do
48
- cloud_configuration = Flipper::Cloud::Configuration.new({
49
- token: "asdf",
50
- sync_secret: "tasty",
51
- local_adapter: local_adapter
52
- })
53
- end
54
-
55
- subject do
56
- described_class.new(cloud_configuration)
57
- end
58
-
59
- it "sends reads to local adapter" do
60
- subject.features
61
- subject.enabled?(:foo)
62
- expect(local_adapter.count(:features)).to be(1)
63
- expect(local_adapter.count(:get)).to be(1)
64
- end
65
-
66
- it "sends writes to cloud and local" do
67
- add_stub = stub_request(:post, "https://www.flippercloud.io/adapter/features").
68
- with({headers: {'Flipper-Cloud-Token'=>'asdf'}}).
69
- to_return(status: 200, body: '{}', headers: {})
70
- enable_stub = stub_request(:post, "https://www.flippercloud.io/adapter/features/foo/boolean").
71
- with(headers: {'Flipper-Cloud-Token'=>'asdf'}).
72
- to_return(status: 200, body: '{}', headers: {})
73
-
74
- subject.enable(:foo)
75
-
76
- expect(local_adapter.count(:add)).to be(1)
77
- expect(local_adapter.count(:enable)).to be(1)
78
- expect(add_stub).to have_been_requested
79
- expect(enable_stub).to have_been_requested
80
- end
81
- end
82
- end
@@ -1,95 +0,0 @@
1
- require 'rails'
2
- require 'flipper/cloud'
3
-
4
- RSpec.describe Flipper::Cloud::Engine do
5
- let(:env) do
6
- { "FLIPPER_CLOUD_TOKEN" => "test-token" }
7
- end
8
-
9
- let(:application) do
10
- Class.new(Rails::Application) do
11
- config.eager_load = false
12
- config.logger = ActiveSupport::Logger.new($stdout)
13
- end
14
- end
15
-
16
- # App for Rack::Test
17
- let(:app) { application.routes }
18
-
19
- before do
20
- Rails.application = nil
21
- ActiveSupport::Dependencies.autoload_paths = ActiveSupport::Dependencies.autoload_paths.dup
22
- ActiveSupport::Dependencies.autoload_once_paths = ActiveSupport::Dependencies.autoload_once_paths.dup
23
-
24
- # Force loading of flipper to configure itself
25
- load 'flipper/cloud.rb'
26
- end
27
-
28
- it "initializes cloud configuration" do
29
- stub_request(:get, /flippercloud\.io/).to_return(status: 200, body: "{}")
30
-
31
- ENV.update(env)
32
- application.initialize!
33
-
34
- expect(Flipper.instance).to be_a(Flipper::Cloud::DSL)
35
- expect(Flipper.instance.instrumenter).to be(ActiveSupport::Notifications)
36
- end
37
-
38
- context "with CLOUD_SYNC_SECRET" do
39
- before do
40
- env.update "FLIPPER_CLOUD_SYNC_SECRET" => "test-secret"
41
- end
42
-
43
- let(:request_body) do
44
- JSON.generate({
45
- "environment_id" => 1,
46
- "webhook_id" => 1,
47
- "delivery_id" => SecureRandom.uuid,
48
- "action" => "sync",
49
- })
50
- end
51
- let(:timestamp) { Time.now }
52
- let(:signature) {
53
- Flipper::Cloud::MessageVerifier.new(secret: env["FLIPPER_CLOUD_SYNC_SECRET"]).generate(request_body, timestamp)
54
- }
55
- let(:signature_header_value) {
56
- Flipper::Cloud::MessageVerifier.new(secret: "").header(signature, timestamp)
57
- }
58
-
59
- it "configures webhook app" do
60
- ENV.update(env)
61
- application.initialize!
62
-
63
- stub = stub_request(:get, "https://www.flippercloud.io/adapter/features?exclude_gate_names=true").with({
64
- headers: { "Flipper-Cloud-Token" => ENV["FLIPPER_CLOUD_TOKEN"] },
65
- }).to_return(status: 200, body: JSON.generate({ features: {} }), headers: {})
66
-
67
- post "/_flipper", request_body, { "HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value }
68
-
69
- expect(last_response.status).to eq(200)
70
- expect(stub).to have_been_requested
71
- end
72
- end
73
-
74
- context "without CLOUD_SYNC_SECRET" do
75
- it "does not configure webhook app" do
76
- ENV.update(env)
77
- application.initialize!
78
-
79
- post "/_flipper"
80
- expect(last_response.status).to eq(404)
81
- end
82
- end
83
-
84
- context "without FLIPPER_CLOUD_TOKEN" do
85
- it "gracefully skips configuring webhook app" do
86
- ENV["FLIPPER_CLOUD_TOKEN"] = nil
87
- application.initialize!
88
- expect(silence { Flipper.instance }).to match(/Missing FLIPPER_CLOUD_TOKEN/)
89
- expect(Flipper.instance).to be_a(Flipper::DSL)
90
-
91
- post "/_flipper"
92
- expect(last_response.status).to eq(404)
93
- end
94
- end
95
- end
@@ -1,104 +0,0 @@
1
- require 'flipper/cloud/message_verifier'
2
-
3
- RSpec.describe Flipper::Cloud::MessageVerifier do
4
- let(:payload) { "some payload" }
5
- let(:secret) { "secret" }
6
- let(:timestamp) { Time.now }
7
-
8
- describe "#generate" do
9
- it "generates signature that can be verified" do
10
- message_verifier = Flipper::Cloud::MessageVerifier.new(secret: secret)
11
- signature = message_verifier.generate(payload, timestamp)
12
- header = generate_header(timestamp: timestamp, signature: signature)
13
- expect(message_verifier.verify(payload, header)).to be(true)
14
- end
15
- end
16
-
17
- describe "#header" do
18
- it "generates a header in valid format" do
19
- version = "v1"
20
- message_verifier = Flipper::Cloud::MessageVerifier.new(secret: secret, version: version)
21
- signature = message_verifier.generate(payload, timestamp)
22
- header = message_verifier.header(signature, timestamp)
23
- expect(header).to eq("t=#{timestamp.to_i},#{version}=#{signature}")
24
- end
25
- end
26
-
27
- describe ".header" do
28
- it "generates a header in valid format" do
29
- version = "v1"
30
- message_verifier = Flipper::Cloud::MessageVerifier.new(secret: secret, version: version)
31
- signature = message_verifier.generate(payload, timestamp)
32
-
33
- header = Flipper::Cloud::MessageVerifier.header(signature, timestamp, version)
34
- expect(header).to eq("t=#{timestamp.to_i},#{version}=#{signature}")
35
- end
36
- end
37
-
38
- describe "#verify" do
39
- it "raises a InvalidSignature when the header does not have the expected format" do
40
- header = "i'm not even a real signature header"
41
- expect {
42
- message_verifier = Flipper::Cloud::MessageVerifier.new(secret: "secret")
43
- message_verifier.verify(payload, header)
44
- }.to raise_error(Flipper::Cloud::MessageVerifier::InvalidSignature, "Unable to extract timestamp and signatures from header")
45
- end
46
-
47
- it "raises a InvalidSignature when there are no signatures with the expected version" do
48
- header = generate_header(version: "v0")
49
- expect {
50
- message_verifier = Flipper::Cloud::MessageVerifier.new(secret: "secret")
51
- message_verifier.verify(payload, header)
52
- }.to raise_error(Flipper::Cloud::MessageVerifier::InvalidSignature, /No signatures found with expected version/)
53
- end
54
-
55
- it "raises a InvalidSignature when there are no valid signatures for the payload" do
56
- header = generate_header(signature: "bad_signature")
57
- expect {
58
- message_verifier = Flipper::Cloud::MessageVerifier.new(secret: "secret")
59
- message_verifier.verify(payload, header)
60
- }.to raise_error(Flipper::Cloud::MessageVerifier::InvalidSignature, "No signatures found matching the expected signature for payload")
61
- end
62
-
63
- it "raises a InvalidSignature when the timestamp is not within the tolerance" do
64
- header = generate_header(timestamp: Time.now - 15)
65
- expect {
66
- message_verifier = Flipper::Cloud::MessageVerifier.new(secret: secret)
67
- message_verifier.verify(payload, header, tolerance: 10)
68
- }.to raise_error(Flipper::Cloud::MessageVerifier::InvalidSignature, /Timestamp outside the tolerance zone/)
69
- end
70
-
71
- it "returns true when the header contains a valid signature and the timestamp is within the tolerance" do
72
- header = generate_header
73
- message_verifier = Flipper::Cloud::MessageVerifier.new(secret: "secret")
74
- expect(message_verifier.verify(payload, header, tolerance: 10)).to be(true)
75
- end
76
-
77
- it "returns true when the header contains at least one valid signature" do
78
- header = generate_header + ",v1=bad_signature"
79
- message_verifier = Flipper::Cloud::MessageVerifier.new(secret: secret)
80
- expect(message_verifier.verify(payload, header, tolerance: 10)).to be(true)
81
- end
82
-
83
- it "returns true when the header contains a valid signature and the timestamp is off but no tolerance is provided" do
84
- header = generate_header(timestamp: Time.at(12_345))
85
- message_verifier = Flipper::Cloud::MessageVerifier.new(secret: secret)
86
- expect(message_verifier.verify(payload, header)).to be(true)
87
- end
88
- end
89
-
90
- private
91
-
92
- def generate_header(options = {})
93
- options[:secret] ||= secret
94
- options[:version] ||= "v1"
95
-
96
- message_verifier = Flipper::Cloud::MessageVerifier.new(secret: options[:secret], version: options[:version])
97
-
98
- options[:timestamp] ||= timestamp
99
- options[:payload] ||= payload
100
- options[:signature] ||= message_verifier.generate(options[:payload], options[:timestamp])
101
-
102
- Flipper::Cloud::MessageVerifier.header(options[:signature], options[:timestamp], options[:version])
103
- end
104
- end