boxr 1.13.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: 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