boxr 1.13.0 → 1.18.0

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: 4a9682a76314abdb2ca543bc244885f87491e8f1e9585ccdaca573bf93e11495
4
- data.tar.gz: 9cdf7ddd389d95367db57dad7fd3b4cb1335f0aff4c1a7833bed709d33952aea
3
+ metadata.gz: ecda6bc31119954e1441132e89cb3730c72832a9d27a4b50cef02631889ac16d
4
+ data.tar.gz: 78e5f729e9e50695560cc15b28286e865940e7222cf258794763ee8efa61c8ee
5
5
  SHA512:
6
- metadata.gz: f560b285eb744a38ff2a6cc52628a21d67b9894de51c127bc9513150eec641bef1f996c01026c0e47b4d4a5b9b40aeda20c5e64328492d5fa334f16120661d0b
7
- data.tar.gz: 5f43467d364b6692f1d3b117d9905a9fb87c7b018a6d202d3b0358a14397ce568df16af7740d5ccd421df87cf8d71b239d24a495dbd4f5807b3ada81716b2869
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
@@ -4,7 +4,7 @@
4
4
 
5
5
  Boxr is a Ruby client library for the Box V2 Content API. Box employees affectionately refer to one another as Boxers, hence the name of this gem.
6
6
 
7
- The purpose of this gem is to provide a clear, efficient, and intentional method of interacting with the Box Content API. As with any SDK that wraps a REST API, it is important to fully understand the Box Content API at the REST endpoint level. You are strongly encouraged to read through the Box documentation located [here](https://box-content.readme.io/).
7
+ The purpose of this gem is to provide a clear, efficient, and intentional method of interacting with the Box Content API. As with any SDK that wraps a REST API, it is important to fully understand the Box Content API at the REST endpoint level. You are strongly encouraged to read through the Box documentation located [here](https://developer.box.com/en/reference/).
8
8
 
9
9
  The full RubyDocs for Boxr can be found [here](http://www.rubydoc.info/gems/boxr/Boxr/Client). You are also encouraged to rely heavily on the source code found in the [lib/boxr](https://github.com/cburnette/boxr/tree/master/lib/boxr) directory of this gem, as well as on the integration tests found [here](https://github.com/cburnette/boxr/blob/master/spec/boxr_spec.rb).
10
10
 
@@ -223,11 +223,17 @@ download_url(file, version: nil)
223
223
  upload_file(path_to_file, parent, content_created_at: nil, content_modified_at: nil,
224
224
  preflight_check: true, send_content_md5: true)
225
225
 
226
+ upload_file_from_io(io, parent, name:, content_created_at: nil, content_modified_at: nil,
227
+ preflight_check: true, send_content_md5: true)
228
+
226
229
  delete_file(file, if_match: nil)
227
230
 
228
231
  upload_new_version_of_file(path_to_file, file, content_modified_at: nil, send_content_md5: true,
229
232
  preflight_check: true, if_match: nil)
230
233
 
234
+ upload_new_version_of_file_from_io(io, file, name: nil, content_modified_at: nil, send_content_md5: true,
235
+ preflight_check: true, if_match: nil)
236
+
231
237
  versions_of_file(file)
232
238
 
233
239
  promote_old_version_of_file(file, file_version)
@@ -289,6 +295,12 @@ get_web_link(web_link)
289
295
  update_web_link(web_link, url: nil, parent: nil, name: nil, description: nil)
290
296
 
291
297
  delete_web_link(web_link)
298
+
299
+ trashed_web_link(web_link, fields: [])
300
+
301
+ delete_trashed_web_link(web_link)
302
+
303
+ restore_trashed_web_link(web_link, name: nil, parent: nil)
292
304
  ```
293
305
  #### [Comments](https://developer.box.com/en/reference/resources/comment/)
294
306
  ```ruby
@@ -444,6 +456,7 @@ delete_folder_metadata(folder, scope, template)
444
456
 
445
457
  get_enterprise_templates
446
458
  get_metadata_template_by_name(scope, template_key)
459
+ get_metadata_template_by_id(template_id)
447
460
 
448
461
  create_metadata_template(display_name, template_key: nil, fields: [], hidden: nil)
449
462
  delete_metadata_template(scope, template_key)
@@ -463,6 +476,29 @@ apply_watermark_on_folder(folder)
463
476
 
464
477
  remove_watermark_on_folder(folder)
465
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
+
466
502
  ## Contributing
467
503
 
468
504
  1. Fork it ( https://github.com/cburnette/boxr/fork )
data/boxr.gemspec CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.required_ruby_version = '~> 2.0'
21
+ spec.required_ruby_version = '>= 2.0'
22
22
 
23
23
  spec.add_development_dependency "bundler", "~> 1.6"
24
24
  spec.add_development_dependency "rake", "~> 10.0"
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
@@ -59,6 +59,10 @@ module Boxr
59
59
  GROUP_FIELDS = [:type, :id, :name, :created_at, :modified_at]
60
60
  GROUP_FIELDS_QUERY = GROUP_FIELDS.join(',')
61
61
 
62
+ WEB_LINK_FIELDS = [:type, :id, :created_at, :created_by, :description, :etag, :item_status, :modified_at, :modified_by,
63
+ :name, :owned_by, :parent, :path_collection, :purged_at, :sequence_id, :shared_link, :trashed_at, :url]
64
+ WEB_LINK_FIELDS_QUERY = WEB_LINK_FIELDS.join(',')
65
+
62
66
  VALID_COLLABORATION_ROLES = ['editor','viewer','previewer','uploader','previewer uploader','viewer uploader','co-owner','owner']
63
67
 
64
68
 
@@ -1,21 +1,38 @@
1
1
  module Boxr
2
2
  class Client
3
3
 
4
- def folder_collaborations(folder, fields: [])
4
+ def folder_collaborations(folder, fields: [], offset: 0, limit: DEFAULT_LIMIT)
5
5
  folder_id = ensure_id(folder)
6
6
  query = build_fields_query(fields, COLLABORATION_FIELDS_QUERY)
7
7
  uri = "#{FOLDERS_URI}/#{folder_id}/collaborations"
8
+ collaborations = get_all_with_pagination(uri, query: query, offset: offset, limit: limit)
9
+ end
10
+
11
+ def file_collaborations(file, fields: [], limit: DEFAULT_LIMIT, marker: nil)
12
+ file_id = ensure_id(file)
13
+ query = build_fields_query(fields, COLLABORATION_FIELDS_QUERY)
14
+ query[:limit] = limit
15
+ query[:marker] = marker unless marker.nil?
16
+
17
+ uri = "#{FILES_URI}/#{file_id}/collaborations"
8
18
 
9
19
  collaborations, response = get(uri, query: query)
10
20
  collaborations['entries']
11
21
  end
12
22
 
13
- def add_collaboration(folder, accessible_by, role, fields: [], notify: nil)
14
- folder_id = ensure_id(folder)
23
+ def group_collaborations(group, offset: 0, limit: DEFAULT_LIMIT)
24
+ group_id = ensure_id(group)
25
+ uri = "#{GROUPS_URI}/#{group_id}/collaborations"
26
+
27
+ collaborations = get_all_with_pagination(uri, offset: offset, limit: limit)
28
+ end
29
+
30
+ def add_collaboration(item, accessible_by, role, fields: [], notify: nil, type: :folder)
31
+ item_id = ensure_id(item)
15
32
  query = build_fields_query(fields, COLLABORATION_FIELDS_QUERY)
16
33
  query[:notify] = notify unless notify.nil?
17
34
 
18
- attributes = {item: {id: folder_id, type: :folder}}
35
+ attributes = {item: {id: item_id, type: type}}
19
36
  attributes[:accessible_by] = accessible_by
20
37
  attributes[:role] = validate_role(role)
21
38
 
@@ -60,9 +77,8 @@ module Boxr
60
77
  pending_collaborations['entries']
61
78
  end
62
79
 
63
-
64
80
  private
65
-
81
+
66
82
  def validate_role(role)
67
83
  case role
68
84
  when :previewer_uploader
@@ -75,7 +91,7 @@ module Boxr
75
91
 
76
92
  role = role.to_s
77
93
  raise BoxrError.new(boxr_message: "Invalid collaboration role: '#{role}'") unless VALID_COLLABORATION_ROLES.include?(role)
78
-
94
+
79
95
  role
80
96
  end
81
97
  end
data/lib/boxr/files.rb CHANGED
@@ -12,7 +12,7 @@ module Boxr
12
12
  folder = folder_from_path(path_items.join('/'))
13
13
 
14
14
  files = folder_items(folder, fields: [:id, :name]).files
15
- file = files.select{|f| f.name == file_name}.first
15
+ file = files.select{|f| f.name.casecmp?(file_name) }.first
16
16
  raise BoxrError.new(boxr_message: "File not found: '#{file_name}'") if file.nil?
17
17
  file
18
18
  end
@@ -136,21 +136,35 @@ module Boxr
136
136
  preflight_check: true, if_match: nil, name: nil)
137
137
  filename = name ? name : File.basename(path_to_file)
138
138
 
139
+ File.open(path_to_file) do |io|
140
+ upload_new_version_of_file_from_io(io, file, name: filename, content_modified_at: content_modified_at, preflight_check: preflight_check, send_content_md5: send_content_md5, if_match: if_match)
141
+ end
142
+ end
143
+
144
+ def upload_new_version_of_file_from_io(io, file, name: nil, content_modified_at: nil, send_content_md5: true,
145
+ preflight_check: true, if_match: nil)
146
+
147
+ filename = name ? name : file.name
148
+
139
149
  file_id = ensure_id(file)
140
- preflight_check_new_version_of_file(path_to_file, file_id) if preflight_check
150
+ preflight_check_new_version_of_file(io, file_id) if preflight_check
141
151
 
142
152
  uri = "#{UPLOAD_URI}/files/#{file_id}/content"
143
153
  file_info = nil
144
154
  response = nil
145
155
 
146
- File.open(path_to_file) do |file|
147
- content_md5 = send_content_md5 ? Digest::SHA1.file(file).hexdigest : nil
148
- attributes = {name: filename}
149
- attributes[:content_modified_at] = content_modified_at.to_datetime.rfc3339 unless content_modified_at.nil?
150
- body = {attributes: JSON.dump(attributes), file: file}
151
- file_info, response = post(uri, body, process_body: false, content_md5: content_md5, if_match: if_match)
156
+ if send_content_md5
157
+ content_md5 = Digest::SHA1.hexdigest(io.read)
158
+ io.rewind
152
159
  end
153
160
 
161
+ attributes = {name: name}
162
+ attributes[:content_modified_at] = content_modified_at.to_datetime.rfc3339 unless content_modified_at.nil?
163
+
164
+ body = {attributes: JSON.dump(attributes), file: io}
165
+
166
+ file_info, response = post(uri, body, process_body: false, content_md5: content_md5, if_match: if_match)
167
+
154
168
  file_info.entries[0]
155
169
  end
156
170
 
@@ -265,8 +279,8 @@ module Boxr
265
279
  body_json, res = options("#{FILES_URI}/content", attributes)
266
280
  end
267
281
 
268
- def preflight_check_new_version_of_file(path_to_file, file_id)
269
- size = File.size(path_to_file)
282
+ def preflight_check_new_version_of_file(io, file_id)
283
+ size = File.size(io)
270
284
  attributes = {size: size}
271
285
  body_json, res = options("#{FILES_URI}/#{file_id}/content", attributes)
272
286
  end
data/lib/boxr/folders.rb CHANGED
@@ -10,7 +10,7 @@ module Boxr
10
10
 
11
11
  folder = path_folders.inject(Boxr::ROOT) do |parent_folder, folder_name|
12
12
  folders = folder_items(parent_folder, fields: [:id, :name]).folders
13
- folder = folders.select{|f| f.name == folder_name}.first
13
+ folder = folders.select{|f| f.name.casecmp?(folder_name) }.first
14
14
  raise BoxrError.new(boxr_message: "Folder not found: '#{folder_name}'") if folder.nil?
15
15
  folder
16
16
  end
data/lib/boxr/groups.rb CHANGED
@@ -89,11 +89,5 @@ module Boxr
89
89
  result
90
90
  end
91
91
 
92
- def group_collaborations(group, offset: 0, limit: DEFAULT_LIMIT)
93
- group_id = ensure_id(group)
94
- uri = "#{GROUPS_URI}/#{group_id}/collaborations"
95
- collaborations = get_all_with_pagination(uri, offset: offset, limit: limit)
96
- end
97
-
98
92
  end
99
93
  end
data/lib/boxr/metadata.rb CHANGED
@@ -93,6 +93,13 @@ module Boxr
93
93
  end
94
94
  alias :get_metadata_template_by_name :metadata_schema
95
95
 
96
+ def get_metadata_template_by_id(template_id)
97
+ template_id = ensure_id(template_id)
98
+ uri = "#{METADATA_TEMPLATES_URI}/#{template_id}"
99
+ schema, response = get(uri)
100
+ schema
101
+ end
102
+
96
103
  def create_metadata_template(display_name, template_key: nil, fields: [], hidden: nil)
97
104
  uri = "#{METADATA_TEMPLATES_URI}/schema"
98
105
  schema = {
data/lib/boxr/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Boxr
2
- VERSION = "1.13.0".freeze
2
+ VERSION = "1.18.0".freeze
3
3
  end
@@ -51,6 +51,31 @@ module Boxr
51
51
  result
52
52
  end
53
53
 
54
+ def trashed_web_link(web_link, fields: [])
55
+ web_link_id = ensure_id(web_link)
56
+ uri = "#{WEB_LINKS_URI}/#{web_link_id}/trash"
57
+ query = build_fields_query(fields, WEB_LINK_FIELDS_QUERY)
58
+
59
+ web_link, response = get(uri, query: query)
60
+ web_link
61
+ end
62
+ alias :get_trashed_web_link :trashed_web_link
63
+
64
+ def delete_trashed_web_link(web_link)
65
+ web_link_id = ensure_id(web_link)
66
+ uri = "#{WEB_LINKS_URI}/#{web_link_id}/trash"
67
+ result, response = delete(uri)
68
+ result
69
+ end
70
+
71
+ def restore_trashed_web_link(web_link, name: nil, parent: nil)
72
+ web_link_id = ensure_id(web_link)
73
+ parent_id = ensure_id(parent)
74
+
75
+ uri = "#{WEB_LINKS_URI}/#{web_link_id}"
76
+ restore_trashed_item(uri, name, parent_id)
77
+ end
78
+
54
79
  private
55
80
 
56
81
  def verify_url(item)
@@ -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
@@ -1,10 +1,22 @@
1
1
  #rake spec SPEC_OPTS="-e \"invokes collaborations operations"\"
2
2
  describe 'collaborations operations' do
3
3
  it "invokes collaborations operations" do
4
+ puts "test setup"
5
+ new_file = BOX_CLIENT.upload_file("./spec/test_files/#{TEST_FILE_NAME}", @test_folder)
6
+ test_group = BOX_CLIENT.create_group(TEST_GROUP_NAME)
7
+ second_test_user = BOX_CLIENT.create_user("Second Test User", login: "second_test_user@#{('a'..'z').to_a.shuffle[0,10].join}.com", role: 'coadmin')
8
+
4
9
  puts "add collaboration"
5
10
  collaboration = BOX_CLIENT.add_collaboration(@test_folder, {id: @test_user.id, type: :user}, :viewer_uploader)
11
+ file_collaboration = BOX_CLIENT.add_collaboration(new_file, {id: second_test_user.id, type: :user}, :viewer, type: :file)
12
+ group_collaboration = BOX_CLIENT.add_collaboration(@test_folder, {id: test_group.id, type: :group}, :editor)
6
13
  expect(collaboration.accessible_by.id).to eq(@test_user.id)
14
+ expect(file_collaboration.accessible_by.id).to eq(second_test_user.id)
15
+ expect(group_collaboration.accessible_by.id).to eq(test_group.id)
16
+
7
17
  COLLABORATION = collaboration
18
+ FILE_COLLABORATION = file_collaboration
19
+ GROUP_COLLABORATION = group_collaboration
8
20
 
9
21
  puts "inspect collaboration"
10
22
  collaboration = BOX_CLIENT.collaboration(COLLABORATION)
@@ -16,14 +28,24 @@ describe 'collaborations operations' do
16
28
 
17
29
  puts "inspect folder collaborations"
18
30
  collaborations = BOX_CLIENT.folder_collaborations(@test_folder)
19
- expect(collaborations.count).to eq(1)
31
+ expect(collaborations.count).to eq(2)
20
32
  expect(collaborations[0].id).to eq(COLLABORATION.id)
21
33
 
34
+ puts "inspect file collaborations"
35
+ collaborations = BOX_CLIENT.file_collaborations(new_file)
36
+ expect(collaborations.count).to eq(3)
37
+ expect(collaborations[0].id).to eq(FILE_COLLABORATION.id)
38
+
39
+ puts "inspect group collaborations"
40
+ collaborations = BOX_CLIENT.group_collaborations(test_group)
41
+ expect(collaborations.count).to eq(1)
42
+ expect(collaborations[0].id).to eq(GROUP_COLLABORATION.id)
43
+
22
44
  puts "remove collaboration"
23
45
  result = BOX_CLIENT.remove_collaboration(COLLABORATION)
24
46
  expect(result).to eq({})
25
47
  collaborations = BOX_CLIENT.folder_collaborations(@test_folder)
26
- expect(collaborations.count).to eq(0)
48
+ expect(collaborations.count).to eq(1)
27
49
 
28
50
  puts "inspect pending collaborations"
29
51
  pending_collaborations = BOX_CLIENT.pending_collaborations
@@ -31,5 +53,10 @@ describe 'collaborations operations' do
31
53
 
32
54
  puts "add invalid collaboration"
33
55
  expect { BOX_CLIENT.add_collaboration(@test_folder, {id: @test_user.id, type: :user}, :invalid_role)}.to raise_error{Boxr::BoxrError}
56
+
57
+ puts "test teardown"
58
+ BOX_CLIENT.delete_file(new_file)
59
+ BOX_CLIENT.delete_group(test_group)
60
+ BOX_CLIENT.delete_user(second_test_user, force: true)
34
61
  end
35
62
  end
@@ -67,20 +67,25 @@ describe "file operations" do
67
67
  new_version = BOX_CLIENT.upload_new_version_of_file("./spec/test_files/#{TEST_FILE_NAME}", test_file)
68
68
  expect(new_version.id).to eq(test_file.id)
69
69
 
70
+ puts "upload new version of file from IO"
71
+ io = File.open("./spec/test_files/#{TEST_FILE_NAME}")
72
+ new_version = BOX_CLIENT.upload_new_version_of_file_from_io(io, test_file)
73
+ expect(new_version.id).to eq(test_file.id)
74
+
70
75
  puts "inspect versions of file"
71
76
  versions = BOX_CLIENT.versions_of_file(test_file)
72
- expect(versions.count).to eq(1) #the reason this is 1 instead of 2 is that Box considers 'versions' to be a versions other than 'current'
77
+ expect(versions.count).to eq(2) #the reason this is 2 instead of 3 is that Box considers 'versions' to be a versions other than 'current'
73
78
  v1 = versions.first
74
79
 
75
80
  puts "promote old version of file"
76
81
  newer_version = BOX_CLIENT.promote_old_version_of_file(test_file, v1)
77
82
  versions = BOX_CLIENT.versions_of_file(test_file)
78
- expect(versions.count).to eq(2)
83
+ expect(versions.count).to eq(3)
79
84
 
80
85
  puts "delete old version of file"
81
86
  result = BOX_CLIENT.delete_old_version_of_file(test_file,v1)
82
87
  versions = BOX_CLIENT.versions_of_file(test_file)
83
- expect(versions.count).to eq(2) #this is still 2 because with Box you can restore a trashed old version
88
+ expect(versions.count).to eq(3) #this is still 3 because with Box you can restore a trashed old version
84
89
 
85
90
  puts "get file thumbnail"
86
91
  thumb = BOX_CLIENT.thumbnail(test_file)
@@ -56,10 +56,6 @@ describe 'group operations' do
56
56
  group_memberships = BOX_CLIENT.group_memberships_for_user(@test_user)
57
57
  expect(group_memberships.count).to eq(0)
58
58
 
59
- puts "inspect group collaborations"
60
- group_collaboration = BOX_CLIENT.add_collaboration(@test_folder, {id: test_group.id, type: :group}, :editor)
61
- expect(group_collaboration.accessible_by.id).to eq(test_group.id)
62
-
63
59
  puts "delete group"
64
60
  response = BOX_CLIENT.delete_group(test_group)
65
61
  expect(response).to eq({})
@@ -76,6 +76,10 @@ describe 'file metadata operations' do
76
76
  template_key = metadata_template["templateKey"]
77
77
  scope = metadata_template["scope"]
78
78
 
79
+ puts "get metadata template by id"
80
+ metadata_template = BOX_CLIENT.get_metadata_template_by_id(metadata_template["id"])
81
+ expect(metadata_template["displayName"]).to eq("Test Template")
82
+
79
83
  puts "delete metadata template"
80
84
  result = BOX_CLIENT.delete_metadata_template(scope, template_key)
81
85
  expect(result).to eq({})
@@ -16,5 +16,18 @@ describe "web links operations" do
16
16
  puts "delete web link"
17
17
  result = BOX_CLIENT.delete_web_link(web_link)
18
18
  expect(result).to eq({})
19
+
20
+ puts "get trashed web link"
21
+ trashed_web_link = BOX_CLIENT.trashed_web_link(web_link)
22
+ expect(trashed_web_link.item_status).to eq("trashed")
23
+
24
+ puts "restore trashed web link"
25
+ restored_web_link = BOX_CLIENT.restore_trashed_web_link(web_link)
26
+ expect(restored_web_link.item_status).to eq("active")
27
+
28
+ puts "trash and permanently delete web link"
29
+ BOX_CLIENT.delete_web_link(web_link)
30
+ result = BOX_CLIENT.delete_trashed_web_link(web_link)
31
+ expect(result).to eq({})
19
32
  end
20
33
  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,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: boxr
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.13.0
4
+ version: 1.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chad Burnette
8
8
  - Xavier Hocquet
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-11-11 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
@@ -248,13 +252,13 @@ homepage: https://github.com/cburnette/boxr
248
252
  licenses:
249
253
  - MIT
250
254
  metadata: {}
251
- post_install_message:
255
+ post_install_message:
252
256
  rdoc_options: []
253
257
  require_paths:
254
258
  - lib
255
259
  required_ruby_version: !ruby/object:Gem::Requirement
256
260
  requirements:
257
- - - "~>"
261
+ - - ">="
258
262
  - !ruby/object:Gem::Version
259
263
  version: '2.0'
260
264
  required_rubygems_version: !ruby/object:Gem::Requirement
@@ -263,8 +267,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
263
267
  - !ruby/object:Gem::Version
264
268
  version: '0'
265
269
  requirements: []
266
- rubygems_version: 3.0.3
267
- signing_key:
270
+ rubygems_version: 3.2.3
271
+ signing_key:
268
272
  specification_version: 4
269
273
  summary: A Ruby client library for the Box V2 Content API.
270
274
  test_files:
@@ -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