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 +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
|