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 +4 -4
- data/.env.example +2 -0
- data/README.md +37 -1
- data/boxr.gemspec +1 -1
- data/lib/boxr.rb +2 -0
- data/lib/boxr/client.rb +5 -1
- data/lib/boxr/collaborations.rb +23 -7
- data/lib/boxr/files.rb +24 -10
- data/lib/boxr/folders.rb +1 -1
- data/lib/boxr/groups.rb +0 -6
- data/lib/boxr/metadata.rb +7 -0
- data/lib/boxr/version.rb +1 -1
- data/lib/boxr/web_links.rb +25 -0
- data/lib/boxr/webhook_validator.rb +59 -0
- data/lib/boxr/webhooks.rb +38 -0
- data/spec/boxr/collaborations_spec.rb +29 -2
- data/spec/boxr/files_spec.rb +8 -3
- data/spec/boxr/groups_spec.rb +0 -4
- data/spec/boxr/metadata_spec.rb +4 -0
- data/spec/boxr/web_links_spec.rb +13 -0
- data/spec/boxr/webhook_validator_spec.rb +120 -0
- data/spec/boxr/webhooks_spec.rb +30 -0
- data/spec/spec_helper.rb +5 -0
- metadata +13 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ecda6bc31119954e1441132e89cb3730c72832a9d27a4b50cef02631889ac16d
|
4
|
+
data.tar.gz: 78e5f729e9e50695560cc15b28286e865940e7222cf258794763ee8efa61c8ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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 = '
|
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
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
|
|
data/lib/boxr/collaborations.rb
CHANGED
@@ -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
|
14
|
-
|
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:
|
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
|
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(
|
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
|
-
|
147
|
-
content_md5 =
|
148
|
-
|
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(
|
269
|
-
size = File.size(
|
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
|
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
data/lib/boxr/web_links.rb
CHANGED
@@ -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(
|
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(
|
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
|
data/spec/boxr/files_spec.rb
CHANGED
@@ -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(
|
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(
|
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(
|
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)
|
data/spec/boxr/groups_spec.rb
CHANGED
@@ -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({})
|
data/spec/boxr/metadata_spec.rb
CHANGED
@@ -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({})
|
data/spec/boxr/web_links_spec.rb
CHANGED
@@ -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.
|
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:
|
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.
|
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
|