boxr 1.17.0 → 1.18.0

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: e02b3471599db059d5a1e9f7ae8b06757c60fe66d576b3083fd1e3799fac49f1
4
- data.tar.gz: d0ce9eb18ef1464b14d48ce45872a6a3768e4be14ebe9d2c903cb3172e0e791c
3
+ metadata.gz: ecda6bc31119954e1441132e89cb3730c72832a9d27a4b50cef02631889ac16d
4
+ data.tar.gz: 78e5f729e9e50695560cc15b28286e865940e7222cf258794763ee8efa61c8ee
5
5
  SHA512:
6
- metadata.gz: 6d45931744e3c77a0a29ee071a5da104f91152fff14471421187435e186f62b1964361a81b97b697e38226da95af814d1d9cf3bc0a905aed1980090e47a55deb
7
- data.tar.gz: a3b3bad61ff4c59678e7d116e0f5250e6172b01d07572a96436e4b23af76dc72e6cca894233278e131610e91a62cb84ed63c1c520fa5ffb1e9a345b8f84da1be
6
+ metadata.gz: 4b35c744df6ef3a020d3d201ed8caae441b33595ebd4dbd22d510db478a39bd5c548dca20b01899ba38ded2d6a9d00e5df9659df62d535ba9442672a88395630
7
+ data.tar.gz: f83a2c3cdeec8795c57d22a3ae1e12850f62f68b984fe28fcc63b6885fb9b0ad4d009c7330821f8d449f15a7b430eb15262dc17ac74de896359e2616e5cd52e7
data/.env.example CHANGED
@@ -13,3 +13,5 @@ BOX_CLIENT_SECRET={client secret of your Box app}
13
13
  BOX_ENTERPRISE_ID={box enterprise id}
14
14
  JWT_PRIVATE_KEY_PATH={path to your JWT private key}
15
15
  JWT_PRIVATE_KEY_PASSWORD={JWT private key password}
16
+ BOX_PRIMARY_SIGNATURE_KEY={primary key for webhooks}
17
+ BOX_SECONDARY_SIGNATURE_KEY={secondary key for webhooks}
data/README.md CHANGED
@@ -476,6 +476,29 @@ apply_watermark_on_folder(folder)
476
476
 
477
477
  remove_watermark_on_folder(folder)
478
478
  ```
479
+
480
+ #### [Webhooks](https://developer.box.com/en/reference/resources/webhook/) & [Webhook Signatures](https://developer.box.com/guides/webhooks/handle/setup-signatures/)
481
+ ```ruby
482
+ create_webhook(target_id, target_type, triggers, address)
483
+
484
+ get_webhooks(marker: nil, limit: nil)
485
+
486
+ get_webhook(webhook_id)
487
+
488
+ update_webhook(webhook_id, attributes)
489
+
490
+ delete_webhook(webhook_id)
491
+
492
+ # When receiving a webhook, it is advised to verify that it was sent by Box
493
+ Boxr::WebhookValidator.new(
494
+ headers,
495
+ payload,
496
+ primary_signature_key: primary_signature_key,
497
+ secondary_signature_key: secondary_signature_key
498
+ ).valid_message?
499
+
500
+ ```
501
+
479
502
  ## Contributing
480
503
 
481
504
  1. Fork it ( https://github.com/cburnette/boxr/fork )
data/lib/boxr.rb CHANGED
@@ -24,6 +24,8 @@ require 'boxr/events'
24
24
  require 'boxr/auth'
25
25
  require 'boxr/web_links'
26
26
  require 'boxr/watermarking'
27
+ require 'boxr/webhooks'
28
+ require 'boxr/webhook_validator'
27
29
 
28
30
  class BoxrCollection < Array
29
31
  def files
data/lib/boxr/client.rb CHANGED
@@ -29,7 +29,7 @@ module Boxr
29
29
  METADATA_TEMPLATES_URI = "#{API_URI}/metadata_templates"
30
30
  EVENTS_URI = "#{API_URI}/events"
31
31
  WEB_LINKS_URI = "#{API_URI}/web_links"
32
-
32
+ WEBHOOKS_URI = "#{API_URI}/webhooks"
33
33
 
34
34
  DEFAULT_LIMIT = 100
35
35
  FOLDER_ITEMS_LIMIT = 1000
data/lib/boxr/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Boxr
2
- VERSION = "1.17.0".freeze
2
+ VERSION = "1.18.0".freeze
3
3
  end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Boxr
4
+ class WebhookValidator
5
+ attr_reader(
6
+ :payload,
7
+ :primary_signature,
8
+ :primary_signature_key,
9
+ :secondary_signature,
10
+ :secondary_signature_key,
11
+ :timestamp
12
+ )
13
+
14
+ MAXIMUM_MESSAGE_AGE = 600 # 10 minutes (in seconds)
15
+
16
+ def initialize(headers, payload, primary_signature_key: nil, secondary_signature_key: nil)
17
+ @payload = payload
18
+ @timestamp = headers['BOX-DELIVERY-TIMESTAMP'].to_s
19
+ @primary_signature_key = primary_signature_key.to_s
20
+ @secondary_signature_key = secondary_signature_key.to_s
21
+ @primary_signature = headers['BOX-SIGNATURE-PRIMARY']
22
+ @secondary_signature = headers['BOX-SIGNATURE-SECONDARY']
23
+ end
24
+
25
+ def valid_message?
26
+ verify_delivery_timestamp && verify_signature
27
+ end
28
+
29
+ def verify_delivery_timestamp
30
+ message_age < MAXIMUM_MESSAGE_AGE
31
+ end
32
+
33
+ def verify_signature
34
+ generate_signature(primary_signature_key) == primary_signature || generate_signature(secondary_signature_key) == secondary_signature
35
+ end
36
+
37
+ def generate_signature(key)
38
+ message_as_bytes = (payload.bytes + timestamp.bytes).pack('U')
39
+ digest = OpenSSL::HMAC.hexdigest('SHA256', key, message_as_bytes)
40
+ Base64.encode64(digest)
41
+ end
42
+
43
+ private
44
+
45
+ def current_time
46
+ Time.now.utc
47
+ end
48
+
49
+ def delivery_time
50
+ Time.parse(timestamp).utc
51
+ rescue ArgumentError
52
+ raise BoxrError.new(boxr_message: "Webhook authenticity not verified: invalid timestamp")
53
+ end
54
+
55
+ def message_age
56
+ current_time - delivery_time
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Boxr
4
+ class Client
5
+ def create_webhook(target_id, target_type, triggers, address)
6
+ attributes = { target: { id: target_id, type: target_type }, triggers: triggers, address: address }
7
+ new_webhook, response = post(WEBHOOKS_URI, attributes)
8
+ new_webhook
9
+ end
10
+
11
+ def get_webhooks(marker: nil, limit: nil)
12
+ query_params = { marker: marker, limit: limit }.compact
13
+ webhooks, response = get(WEBHOOKS_URI, query: query_params)
14
+ webhooks
15
+ end
16
+
17
+ def get_webhook(webhook)
18
+ webhook_id = ensure_id(webhook)
19
+ uri = "#{WEBHOOKS_URI}/#{webhook_id}"
20
+ webhook, response = get(uri)
21
+ webhook
22
+ end
23
+
24
+ def update_webhook(webhook, attributes = {})
25
+ webhook_id = ensure_id(webhook)
26
+ uri = "#{WEBHOOKS_URI}/#{webhook_id}"
27
+ updated_webhook, response = put(uri, attributes)
28
+ updated_webhook
29
+ end
30
+
31
+ def delete_webhook(webhook)
32
+ webhook_id = ensure_id(webhook)
33
+ uri = "#{WEBHOOKS_URI}/#{webhook_id}"
34
+ result, response = delete(uri)
35
+ result
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ def generate_signature(payload, timestamp, key)
4
+ message_as_bytes = (payload.bytes + timestamp.bytes).pack('U')
5
+ digest = OpenSSL::HMAC.hexdigest('SHA256', key, message_as_bytes)
6
+ Base64.encode64(digest)
7
+ end
8
+
9
+ # rake spec SPEC_OPTS="-e \"Boxr::WebhookValidator"\"
10
+ describe Boxr::WebhookValidator, :skip_reset do
11
+ describe '#verify_delivery_timestamp' do
12
+ let(:payload) { 'not relevant' }
13
+ subject { described_class.new(headers, payload).verify_delivery_timestamp }
14
+ context 'maximum age is under 10 minutes' do
15
+ let(:five_minutes_ago) { (Time.now.utc - 300).to_s } # 5 minutes (in seconds)
16
+ let(:headers) { { 'BOX-DELIVERY-TIMESTAMP' => five_minutes_ago} }
17
+ it 'returns true' do
18
+ expect(subject).to eq(true)
19
+ end
20
+ end
21
+
22
+ context 'maximum age is over 10 minute' do
23
+ let(:eleven_minutes_ago) { (Time.now.utc - 660).to_s } # 11 minutes (in seconds)
24
+ let(:headers) { { 'BOX-DELIVERY-TIMESTAMP' => eleven_minutes_ago } }
25
+ it 'returns false' do
26
+ expect(subject).to eq(false)
27
+ end
28
+ end
29
+
30
+ context 'no delivery timestamp is supplied' do
31
+ let(:headers) { { 'BOX-DELIVERY-TIMESTAMP' => nil } }
32
+ it 'raises an error' do
33
+ expect do
34
+ subject
35
+ end.to raise_error(RuntimeError, 'Webhook authenticity not verified: invalid timestamp')
36
+ end
37
+ end
38
+
39
+ context 'bogus timestamp is supplied' do
40
+ let(:headers) { { 'BOX-DELIVERY-TIMESTAMP' => 'foo' } }
41
+ it 'raises an error' do
42
+ expect do
43
+ subject
44
+ end.to raise_error(RuntimeError, 'Webhook authenticity not verified: invalid timestamp')
45
+ end
46
+ end
47
+ end
48
+
49
+ describe '#verify_signature' do
50
+ let(:payload) { 'some data' }
51
+ let(:timestamp) { (Time.now.utc - 300).to_s } # 5 minutes ago (in seconds)
52
+ let(:signature_primary) { generate_signature(payload, timestamp, ENV['BOX_PRIMARY_SIGNATURE_KEY'].to_s) }
53
+ let(:signature_secondary) { generate_signature(payload, timestamp, ENV['BOX_SECONDARY_SIGNATURE_KEY'].to_s) }
54
+ subject { described_class.new(headers,
55
+ payload,
56
+ primary_signature_key: ENV['BOX_PRIMARY_SIGNATURE_KEY'].to_s,
57
+ secondary_signature_key: ENV['BOX_SECONDARY_SIGNATURE_KEY'].to_s,
58
+ ).verify_signature }
59
+
60
+ context 'valid primary key' do
61
+ let(:headers) do
62
+ { 'BOX-DELIVERY-TIMESTAMP' => timestamp,
63
+ 'BOX-SIGNATURE-PRIMARY' => signature_primary }
64
+ end
65
+
66
+ it 'returns true' do
67
+ expect(subject).to eq(true)
68
+ end
69
+ end
70
+
71
+ context 'invalid primary key, valid secondary key' do
72
+ let(:headers) do
73
+ { 'BOX-DELIVERY-TIMESTAMP' => timestamp,
74
+ 'BOX-SIGNATURE-PRIMARY' => 'invalid',
75
+ 'BOX-SIGNATURE-SECONDARY' => signature_secondary }
76
+ end
77
+
78
+ it 'returns true' do
79
+ expect(subject).to eq(true)
80
+ end
81
+ end
82
+
83
+ context 'invalid primary key, invalid secondary key' do
84
+ let(:headers) do
85
+ { 'BOX-DELIVERY-TIMESTAMP' => timestamp,
86
+ 'BOX-SIGNATURE-PRIMARY' => 'invalid',
87
+ 'BOX-SIGNATURE-SECONDARY' => 'also invalid' }
88
+ end
89
+
90
+ it 'returns false' do
91
+ expect(subject).to eq(false)
92
+ end
93
+ end
94
+
95
+ context 'no signatures were supplied' do
96
+ let(:headers) do
97
+ { 'BOX-DELIVERY-TIMESTAMP' => timestamp,
98
+ 'BOX-SIGNATURE-PRIMARY' => nil,
99
+ 'BOX-SIGNATURE-SECONDARY' => nil }
100
+ end
101
+
102
+ it 'returns false' do
103
+ subject = described_class.new(headers, payload, primary_signature_key: nil, secondary_signature_key: nil).verify_signature
104
+ expect(subject).to eq(false)
105
+ end
106
+ end
107
+ end
108
+
109
+ describe '#valid_message?' do
110
+ let(:headers) { { 'doesnt' => 'matter' } }
111
+ let(:payload) { 'not relevant' }
112
+
113
+ it 'delegates to timestamp and signature verification' do
114
+ validator = described_class.new(headers, payload)
115
+ expect(validator).to receive(:verify_delivery_timestamp).and_return(true)
116
+ expect(validator).to receive(:verify_signature)
117
+ validator.valid_message?
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,30 @@
1
+ # rake spec SPEC_OPTS="-e \"invokes webhook operations"\"
2
+ describe "webhook operations" do
3
+ it 'invokes webhook operations' do
4
+ puts 'create webhook'
5
+ resource_id = @test_folder.id
6
+ type = 'folder'
7
+ triggers = ['FOLDER.RENAMED']
8
+ address = 'https://example.com'
9
+ new_webhook = BOX_CLIENT.create_webhook(resource_id, type, triggers, address)
10
+ new_webhook_id = new_webhook.id
11
+ expect(new_webhook.triggers.to_a).to eq(triggers)
12
+
13
+ puts 'get webhooks'
14
+ all_webhooks = BOX_CLIENT.get_webhooks
15
+ expect(all_webhooks.entries.first.id).to eq(new_webhook_id)
16
+
17
+ puts 'get webhook'
18
+ single_webhook = BOX_CLIENT.get_webhook(new_webhook_id)
19
+ expect(single_webhook.id).to eq(new_webhook_id)
20
+
21
+ puts 'update webhooks'
22
+ new_address = 'https://foo.com'
23
+ updated_webhook = BOX_CLIENT.update_webhook(new_webhook, { address: new_address })
24
+ expect(updated_webhook.address).to eq(new_address)
25
+
26
+ puts 'delete webhooks'
27
+ deleted_webhook = BOX_CLIENT.delete_webhook(updated_webhook)
28
+ expect(deleted_webhook).to be_empty
29
+ end
30
+ end
data/spec/spec_helper.rb CHANGED
@@ -5,6 +5,11 @@ require 'awesome_print'
5
5
 
6
6
  RSpec.configure do |config|
7
7
  config.before(:each) do
8
+ if test.metadata[:skip_reset]
9
+ puts "Skipping reset"
10
+ next
11
+ end
12
+
8
13
  puts "-----> Resetting Box Environment"
9
14
  sleep BOX_SERVER_SLEEP
10
15
  root_folders = BOX_CLIENT.root_folder_items.folders
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: boxr
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.17.0
4
+ version: 1.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chad Burnette
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-03-08 00:00:00.000000000 Z
12
+ date: 2021-03-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -227,6 +227,8 @@ files:
227
227
  - lib/boxr/version.rb
228
228
  - lib/boxr/watermarking.rb
229
229
  - lib/boxr/web_links.rb
230
+ - lib/boxr/webhook_validator.rb
231
+ - lib/boxr/webhooks.rb
230
232
  - spec/boxr/auth_spec.rb
231
233
  - spec/boxr/chunked_uploads_spec.rb
232
234
  - spec/boxr/collaborations_spec.rb
@@ -240,6 +242,8 @@ files:
240
242
  - spec/boxr/users_spec.rb
241
243
  - spec/boxr/watermarking_spec.rb
242
244
  - spec/boxr/web_links_spec.rb
245
+ - spec/boxr/webhook_validator_spec.rb
246
+ - spec/boxr/webhooks_spec.rb
243
247
  - spec/boxr_spec.rb
244
248
  - spec/spec_helper.rb
245
249
  - spec/test_files/large test file.txt
@@ -281,6 +285,8 @@ test_files:
281
285
  - spec/boxr/users_spec.rb
282
286
  - spec/boxr/watermarking_spec.rb
283
287
  - spec/boxr/web_links_spec.rb
288
+ - spec/boxr/webhook_validator_spec.rb
289
+ - spec/boxr/webhooks_spec.rb
284
290
  - spec/boxr_spec.rb
285
291
  - spec/spec_helper.rb
286
292
  - spec/test_files/large test file.txt