nylas 6.7.0 → 6.8.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/lib/nylas/client.rb +36 -0
- data/lib/nylas/handler/api_operations.rb +38 -21
- data/lib/nylas/handler/http_client.rb +108 -23
- data/lib/nylas/handler/service_account_signer.rb +112 -0
- data/lib/nylas/resources/applications.rb +14 -0
- data/lib/nylas/resources/bookings.rb +4 -2
- data/lib/nylas/resources/domains.rb +269 -0
- data/lib/nylas/resources/lists.rb +36 -0
- data/lib/nylas/resources/policies.rb +124 -0
- data/lib/nylas/resources/redirect_uris.rb +2 -2
- data/lib/nylas/resources/rules.rb +161 -0
- data/lib/nylas/resources/webhooks.rb +4 -5
- data/lib/nylas/resources/workspaces.rb +111 -0
- data/lib/nylas/utils/file_utils.rb +6 -3
- data/lib/nylas/version.rb +1 -1
- data/lib/nylas.rb +6 -0
- metadata +24 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 39fb36891572d67e348da1b79d8d0f73c0c542f073252733a17c7a11ac5f041b
|
|
4
|
+
data.tar.gz: 234c99ad89acd1ef2069bea31a70f7f86e8acf8e5b6a0a38b6000e79e7a570e4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ae326a217e211366f66a2079cc98990c9ebad04bd0c48a88c6a1634adae55db6d9ee505060655eb4faee1a2deffb7d4447e007a5e5b09a3fdc44157743b20034
|
|
7
|
+
data.tar.gz: b5c494daa9bd4062765eb5182382f550d06abb20ee85d0c890dee54cede76af4ea5650765ebd9bc3bb151d404bb853b409e9695219a5bcf29156d27df6fd696d
|
data/lib/nylas/client.rb
CHANGED
|
@@ -8,6 +8,7 @@ require_relative "resources/auth"
|
|
|
8
8
|
require_relative "resources/webhooks"
|
|
9
9
|
require_relative "resources/applications"
|
|
10
10
|
require_relative "resources/folders"
|
|
11
|
+
require_relative "resources/lists"
|
|
11
12
|
require_relative "resources/notetakers"
|
|
12
13
|
require_relative "resources/scheduler"
|
|
13
14
|
|
|
@@ -99,6 +100,13 @@ module Nylas
|
|
|
99
100
|
Grants.new(self)
|
|
100
101
|
end
|
|
101
102
|
|
|
103
|
+
# The list resources for your Nylas application.
|
|
104
|
+
#
|
|
105
|
+
# @return [Nylas::Lists] List resources for your Nylas application
|
|
106
|
+
def lists
|
|
107
|
+
Lists.new(self)
|
|
108
|
+
end
|
|
109
|
+
|
|
102
110
|
# The message resources for your Nylas application.
|
|
103
111
|
#
|
|
104
112
|
# @return [Nylas::Messages] Message resources for your Nylas application
|
|
@@ -113,6 +121,34 @@ module Nylas
|
|
|
113
121
|
Threads.new(self)
|
|
114
122
|
end
|
|
115
123
|
|
|
124
|
+
# The policy resources for your Nylas application.
|
|
125
|
+
#
|
|
126
|
+
# @return [Nylas::Policies] Policy resources for your Nylas application.
|
|
127
|
+
def policies
|
|
128
|
+
Policies.new(self)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# The rule resources for your Nylas application.
|
|
132
|
+
#
|
|
133
|
+
# @return [Nylas::Rules] Rule resources for your Nylas application.
|
|
134
|
+
def rules
|
|
135
|
+
Rules.new(self)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# The workspace resources for your Nylas application.
|
|
139
|
+
#
|
|
140
|
+
# @return [Nylas::Workspaces] Workspace resources for your Nylas application.
|
|
141
|
+
def workspaces
|
|
142
|
+
Workspaces.new(self)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# The domain resources for your Nylas application.
|
|
146
|
+
#
|
|
147
|
+
# @return [Nylas::Domains] Domain resources for your Nylas application.
|
|
148
|
+
def domains
|
|
149
|
+
Domains.new(self)
|
|
150
|
+
end
|
|
151
|
+
|
|
116
152
|
# The webhook resources for your Nylas application.
|
|
117
153
|
#
|
|
118
154
|
# @return [Nylas::Webhooks] Webhook resources for your Nylas application.
|
|
@@ -15,9 +15,10 @@ module Nylas
|
|
|
15
15
|
#
|
|
16
16
|
# @param path [String] Destination path for the call.
|
|
17
17
|
# @param query_params [Hash, {}] Query params to pass to the call.
|
|
18
|
+
# @param headers [Hash, {}] Additional HTTP headers to include in the payload.
|
|
18
19
|
# @return [Array([Hash, Array], String, Hash)] Nylas data object, API Request ID, and response headers.
|
|
19
|
-
def get(path:, query_params: {})
|
|
20
|
-
response = get_raw(path: path, query_params: query_params)
|
|
20
|
+
def get(path:, query_params: {}, headers: {})
|
|
21
|
+
response = get_raw(path: path, query_params: query_params, headers: headers)
|
|
21
22
|
|
|
22
23
|
[response[:data], response[:request_id], response[:headers]]
|
|
23
24
|
end
|
|
@@ -26,10 +27,11 @@ module Nylas
|
|
|
26
27
|
#
|
|
27
28
|
# @param path [String] Destination path for the call.
|
|
28
29
|
# @param query_params [Hash, {}] Query params to pass to the call.
|
|
30
|
+
# @param headers [Hash, {}] Additional HTTP headers to include in the payload.
|
|
29
31
|
# @return [Array<Array<Hash>, String, String, Hash>]
|
|
30
32
|
# Nylas data array, API Request ID, next cursor, and response headers.response headers.
|
|
31
|
-
def get_list(path:, query_params: {})
|
|
32
|
-
response = get_raw(path: path, query_params: query_params)
|
|
33
|
+
def get_list(path:, query_params: {}, headers: {})
|
|
34
|
+
response = get_raw(path: path, query_params: query_params, headers: headers)
|
|
33
35
|
|
|
34
36
|
[response[:data], response[:request_id], response[:next_cursor], response[:headers]]
|
|
35
37
|
end
|
|
@@ -40,16 +42,20 @@ module Nylas
|
|
|
40
42
|
#
|
|
41
43
|
# @param path [String] Destination path for the call.
|
|
42
44
|
# @param query_params [Hash, {}] Query params to pass to the call.
|
|
45
|
+
# @param headers [Hash, {}] Additional HTTP headers to include in the payload.
|
|
43
46
|
# @return [Hash] The JSON response from the Nylas API.
|
|
44
|
-
def get_raw(path:, query_params: {})
|
|
45
|
-
|
|
47
|
+
def get_raw(path:, query_params: {}, headers: {})
|
|
48
|
+
request = {
|
|
46
49
|
method: :get,
|
|
47
50
|
path: path,
|
|
48
51
|
query: query_params,
|
|
49
52
|
payload: nil,
|
|
50
53
|
api_key: api_key,
|
|
51
54
|
timeout: timeout
|
|
52
|
-
|
|
55
|
+
}
|
|
56
|
+
request[:headers] = headers unless headers.empty?
|
|
57
|
+
|
|
58
|
+
execute(**request)
|
|
53
59
|
end
|
|
54
60
|
end
|
|
55
61
|
|
|
@@ -63,18 +69,21 @@ module Nylas
|
|
|
63
69
|
# @param path [String] Destination path for the call.
|
|
64
70
|
# @param query_params [Hash, {}] Query params to pass to the call.
|
|
65
71
|
# @param request_body [Hash, nil] Request body to pass to the call.
|
|
72
|
+
# Defaults to {} when nil to ensure Content-Type: application/json is sent.
|
|
66
73
|
# @param headers [Hash, {}] Additional HTTP headers to include in the payload.
|
|
67
74
|
# @return [Array(Hash, String, Hash)] Nylas data object, API Request ID, and response headers.
|
|
68
|
-
def post(path:, query_params: {}, request_body: nil, headers: {})
|
|
69
|
-
|
|
75
|
+
def post(path:, query_params: {}, request_body: nil, headers: {}, serialized_json_body: nil)
|
|
76
|
+
request = {
|
|
70
77
|
method: :post,
|
|
71
78
|
path: path,
|
|
72
79
|
query: query_params,
|
|
73
|
-
payload: request_body,
|
|
80
|
+
payload: request_body || {},
|
|
74
81
|
headers: headers,
|
|
75
82
|
api_key: api_key,
|
|
76
83
|
timeout: timeout
|
|
77
|
-
|
|
84
|
+
}
|
|
85
|
+
request[:serialized_json_body] = serialized_json_body unless serialized_json_body.nil?
|
|
86
|
+
response = execute(**request)
|
|
78
87
|
|
|
79
88
|
[response[:data], response[:request_id], response[:headers]]
|
|
80
89
|
end
|
|
@@ -90,18 +99,21 @@ module Nylas
|
|
|
90
99
|
# @param path [String] Destination path for the call.
|
|
91
100
|
# @param query_params [Hash, {}] Query params to pass to the call.
|
|
92
101
|
# @param request_body [Hash, nil] Request body to pass to the call.
|
|
102
|
+
# Defaults to {} when nil to ensure Content-Type: application/json is sent.
|
|
93
103
|
# @param headers [Hash, {}] Additional HTTP headers to include in the payload.
|
|
94
104
|
# @return Nylas data object and API Request ID.
|
|
95
|
-
def put(path:, query_params: {}, request_body: nil, headers: {})
|
|
96
|
-
|
|
105
|
+
def put(path:, query_params: {}, request_body: nil, headers: {}, serialized_json_body: nil)
|
|
106
|
+
request = {
|
|
97
107
|
method: :put,
|
|
98
108
|
path: path,
|
|
99
109
|
query: query_params,
|
|
100
|
-
payload: request_body,
|
|
110
|
+
payload: request_body || {},
|
|
101
111
|
headers: headers,
|
|
102
112
|
api_key: api_key,
|
|
103
113
|
timeout: timeout
|
|
104
|
-
|
|
114
|
+
}
|
|
115
|
+
request[:serialized_json_body] = serialized_json_body unless serialized_json_body.nil?
|
|
116
|
+
response = execute(**request)
|
|
105
117
|
|
|
106
118
|
[response[:data], response[:request_id]]
|
|
107
119
|
end
|
|
@@ -117,18 +129,21 @@ module Nylas
|
|
|
117
129
|
# @param path [String] Destination path for the call.
|
|
118
130
|
# @param query_params [Hash, {}] Query params to pass to the call.
|
|
119
131
|
# @param request_body [Hash, nil] Request body to pass to the call.
|
|
132
|
+
# Defaults to {} when nil to ensure Content-Type: application/json is sent.
|
|
120
133
|
# @param headers [Hash, {}] Additional HTTP headers to include in the payload.
|
|
121
134
|
# @return Nylas data object and API Request ID.
|
|
122
|
-
def patch(path:, query_params: {}, request_body: nil, headers: {})
|
|
123
|
-
|
|
135
|
+
def patch(path:, query_params: {}, request_body: nil, headers: {}, serialized_json_body: nil)
|
|
136
|
+
request = {
|
|
124
137
|
method: :patch,
|
|
125
138
|
path: path,
|
|
126
139
|
query: query_params,
|
|
127
|
-
payload: request_body,
|
|
140
|
+
payload: request_body || {},
|
|
128
141
|
headers: headers,
|
|
129
142
|
api_key: api_key,
|
|
130
143
|
timeout: timeout
|
|
131
|
-
|
|
144
|
+
}
|
|
145
|
+
request[:serialized_json_body] = serialized_json_body unless serialized_json_body.nil?
|
|
146
|
+
response = execute(**request)
|
|
132
147
|
|
|
133
148
|
[response[:data], response[:request_id]]
|
|
134
149
|
end
|
|
@@ -143,15 +158,17 @@ module Nylas
|
|
|
143
158
|
#
|
|
144
159
|
# @param path [String] Destination path for the call.
|
|
145
160
|
# @param query_params [Hash, {}] Query params to pass to the call.
|
|
161
|
+
# @param request_body [Hash, nil] Optional request body (e.g. cancellation_reason for bookings).
|
|
162
|
+
# Defaults to {} to ensure Content-Type: application/json is sent.
|
|
146
163
|
# @param headers [Hash, {}] Additional HTTP headers to include in the payload.
|
|
147
164
|
# @return Nylas data object and API Request ID.
|
|
148
|
-
def delete(path:, query_params: {}, headers: {})
|
|
165
|
+
def delete(path:, query_params: {}, request_body: nil, headers: {})
|
|
149
166
|
response = execute(
|
|
150
167
|
method: :delete,
|
|
151
168
|
path: path,
|
|
152
169
|
query: query_params,
|
|
153
170
|
headers: headers,
|
|
154
|
-
payload:
|
|
171
|
+
payload: request_body || {},
|
|
155
172
|
api_key: api_key,
|
|
156
173
|
timeout: timeout
|
|
157
174
|
)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "httparty"
|
|
4
4
|
require "net/http"
|
|
5
|
+
require "net/http/post/multipart"
|
|
5
6
|
|
|
6
7
|
require_relative "../errors"
|
|
7
8
|
require_relative "../version"
|
|
@@ -29,11 +30,14 @@ module Nylas
|
|
|
29
30
|
# @param query [Hash, {}] Hash of names and values to include in the query section of the URI
|
|
30
31
|
# fragment.
|
|
31
32
|
# @param payload [Hash, nil] Body to send with the request.
|
|
33
|
+
# @param serialized_json_body [String, nil] Pre-serialized JSON body to send as-is.
|
|
32
34
|
# @param api_key [Hash, nil] API key to send with the request.
|
|
33
35
|
# @return [Object] Parsed JSON response from the API.
|
|
34
|
-
def execute(method:, path:, timeout:, headers: {}, query: {}, payload: nil, api_key: nil
|
|
36
|
+
def execute(method:, path:, timeout:, headers: {}, query: {}, payload: nil, api_key: nil,
|
|
37
|
+
serialized_json_body: nil)
|
|
35
38
|
request = build_request(method: method, path: path, headers: headers,
|
|
36
|
-
query: query, payload: payload, api_key: api_key, timeout: timeout
|
|
39
|
+
query: query, payload: payload, api_key: api_key, timeout: timeout,
|
|
40
|
+
serialized_json_body: serialized_json_body)
|
|
37
41
|
begin
|
|
38
42
|
httparty_execute(**request) do |response, _request, result|
|
|
39
43
|
content_type = nil
|
|
@@ -90,12 +94,14 @@ module Nylas
|
|
|
90
94
|
# @param query [Hash, {}] Hash of names and values to include in the query section of the URI
|
|
91
95
|
# fragment.
|
|
92
96
|
# @param payload [Hash, nil] Body to send with the request.
|
|
97
|
+
# @param serialized_json_body [String, nil] Pre-serialized JSON body to send as-is.
|
|
93
98
|
# @param timeout [Integer, nil] Timeout value to send with the request.
|
|
94
99
|
# @param api_key [Hash, nil] API key to send with the request.
|
|
95
100
|
# @return [Object] The request information after processing. This includes an updated payload
|
|
96
101
|
# and headers.
|
|
97
102
|
def build_request(
|
|
98
|
-
method:, path: nil, headers: {}, query: {}, payload: nil, timeout: nil, api_key: nil
|
|
103
|
+
method:, path: nil, headers: {}, query: {}, payload: nil, timeout: nil, api_key: nil,
|
|
104
|
+
serialized_json_body: nil
|
|
99
105
|
)
|
|
100
106
|
url = build_url(path, query)
|
|
101
107
|
resulting_headers = default_headers.merge(headers).merge(auth_header(api_key))
|
|
@@ -103,7 +109,10 @@ module Nylas
|
|
|
103
109
|
# Check for multipart flag using both string and symbol keys for backwards compatibility
|
|
104
110
|
is_multipart = !payload.nil? && (payload["multipart"] || payload[:multipart])
|
|
105
111
|
|
|
106
|
-
if !
|
|
112
|
+
if !serialized_json_body.nil?
|
|
113
|
+
payload = serialized_json_body
|
|
114
|
+
resulting_headers["Content-type"] = "application/json"
|
|
115
|
+
elsif !payload.nil? && !is_multipart
|
|
107
116
|
normalize_json_encodings!(payload)
|
|
108
117
|
payload = payload&.to_json
|
|
109
118
|
resulting_headers["Content-type"] = "application/json"
|
|
@@ -135,7 +144,9 @@ module Nylas
|
|
|
135
144
|
|
|
136
145
|
private
|
|
137
146
|
|
|
138
|
-
# Sends a request to the Nylas REST API using HTTParty.
|
|
147
|
+
# Sends a request to the Nylas REST API using HTTParty or Net::HTTP for multipart.
|
|
148
|
+
# Multipart requests use Net::HTTP::Post::Multipart (multipart-post gem) because
|
|
149
|
+
# HTTParty's multipart handling produces malformed requests that the Nylas API rejects.
|
|
139
150
|
#
|
|
140
151
|
# @param method [Symbol] HTTP method for the API call. Either :get, :post, :delete, or :patch.
|
|
141
152
|
# @param url [String] URL for the API call.
|
|
@@ -143,25 +154,16 @@ module Nylas
|
|
|
143
154
|
# @param payload [String, Hash] Body to send with the request.
|
|
144
155
|
# @param timeout [Hash] Timeout value to send with the request.
|
|
145
156
|
def httparty_execute(method:, url:, headers:, payload:, timeout:)
|
|
146
|
-
|
|
147
|
-
headers: headers,
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if payload.is_a?(Hash) && file_upload?(payload)
|
|
153
|
-
options[:multipart] = true
|
|
154
|
-
options[:body] = prepare_multipart_payload(payload)
|
|
155
|
-
elsif payload
|
|
156
|
-
options[:body] = payload
|
|
157
|
+
if method == :post && payload.is_a?(Hash) && file_upload?(payload)
|
|
158
|
+
response = execute_multipart_request(url: url, headers: headers, payload: payload, timeout: timeout)
|
|
159
|
+
else
|
|
160
|
+
options = { headers: headers, timeout: timeout }
|
|
161
|
+
options[:body] = payload if payload
|
|
162
|
+
response = HTTParty.send(method, url, options)
|
|
157
163
|
end
|
|
158
164
|
|
|
159
|
-
response = HTTParty.send(method, url, options)
|
|
160
|
-
|
|
161
|
-
# Create a compatible response object that mimics RestClient::Response
|
|
162
165
|
result = create_response_wrapper(response)
|
|
163
166
|
|
|
164
|
-
# Call the block with the response in the same format as rest-client
|
|
165
167
|
if block_given?
|
|
166
168
|
yield response, nil, result
|
|
167
169
|
else
|
|
@@ -169,6 +171,77 @@ module Nylas
|
|
|
169
171
|
end
|
|
170
172
|
end
|
|
171
173
|
|
|
174
|
+
# Executes multipart POST using Net::HTTP::Post::Multipart (fixes issue #538).
|
|
175
|
+
# HTTParty's multipart produces malformed requests; multipart-post/UploadIO works correctly.
|
|
176
|
+
def execute_multipart_request(url:, headers:, payload:, timeout:)
|
|
177
|
+
uri = URI.parse(url)
|
|
178
|
+
params = build_multipart_params(payload)
|
|
179
|
+
|
|
180
|
+
req = Net::HTTP::Post::Multipart.new(uri.path, params)
|
|
181
|
+
headers.each { |key, value| req[key] = value }
|
|
182
|
+
|
|
183
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
184
|
+
http.use_ssl = (uri.scheme == "https")
|
|
185
|
+
http.read_timeout = timeout
|
|
186
|
+
http.open_timeout = timeout
|
|
187
|
+
|
|
188
|
+
response = http.request(req)
|
|
189
|
+
|
|
190
|
+
create_httparty_like_response(response)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Build params hash for Net::HTTP::Post::Multipart with UploadIO for file fields.
|
|
194
|
+
def build_multipart_params(payload)
|
|
195
|
+
params = {}
|
|
196
|
+
payload.each do |key, value|
|
|
197
|
+
params[key] = if key.is_a?(String) && key != "message" && file_like_value?(value)
|
|
198
|
+
value_to_upload_io(value)
|
|
199
|
+
else
|
|
200
|
+
value.to_s
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
params
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def file_like_value?(value)
|
|
207
|
+
return true if value.respond_to?(:read) && (value.is_a?(File) ? !value.closed? : true)
|
|
208
|
+
if value.is_a?(String) && (value.respond_to?(:original_filename) || value.respond_to?(:content_type))
|
|
209
|
+
return true
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
false
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Convert File, String, or StringIO to UploadIO for multipart-post.
|
|
216
|
+
def value_to_upload_io(value)
|
|
217
|
+
content_type = value.respond_to?(:content_type) ? value.content_type : "application/octet-stream"
|
|
218
|
+
filename = value.respond_to?(:original_filename) ? value.original_filename : "file.bin"
|
|
219
|
+
|
|
220
|
+
io = if value.respond_to?(:read) && value.respond_to?(:rewind)
|
|
221
|
+
value.rewind if value.respond_to?(:rewind)
|
|
222
|
+
value
|
|
223
|
+
else
|
|
224
|
+
require "stringio"
|
|
225
|
+
content = value.to_s
|
|
226
|
+
content = content.dup.force_encoding(Encoding::ASCII_8BIT) if content.is_a?(String)
|
|
227
|
+
StringIO.new(content)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
UploadIO.new(io, content_type, filename)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Create response object compatible with HTTParty::Response interface.
|
|
234
|
+
def create_httparty_like_response(net_http_response)
|
|
235
|
+
headers = net_http_response.to_hash
|
|
236
|
+
headers = headers.transform_values { |v| v.is_a?(Array) && v.one? ? v.first : v }
|
|
237
|
+
|
|
238
|
+
OpenStruct.new(
|
|
239
|
+
body: net_http_response.body,
|
|
240
|
+
code: net_http_response.code.to_i,
|
|
241
|
+
headers: headers
|
|
242
|
+
)
|
|
243
|
+
end
|
|
244
|
+
|
|
172
245
|
# Create a response wrapper that mimics RestClient::Response.code behavior
|
|
173
246
|
def create_response_wrapper(response)
|
|
174
247
|
OpenStruct.new(code: response.code)
|
|
@@ -188,10 +261,20 @@ module Nylas
|
|
|
188
261
|
# Check if payload was prepared by FileUtils.build_form_request for multipart uploads
|
|
189
262
|
# This handles binary content attachments that are strings with added singleton methods
|
|
190
263
|
has_message_field = payload.key?("message") && payload["message"].is_a?(String)
|
|
191
|
-
has_attachment_fields = payload.keys.any? { |key| key.is_a?(String) && key.match?(/^file\d+$/) }
|
|
192
264
|
|
|
193
|
-
#
|
|
194
|
-
#
|
|
265
|
+
# Check for attachment fields - these can have custom content_id values (not just "file{N}")
|
|
266
|
+
# FileUtils.build_form_request creates entries with string values that have singleton methods
|
|
267
|
+
# like original_filename and content_type defined on them
|
|
268
|
+
has_attachment_fields = payload.any? do |key, value|
|
|
269
|
+
next false unless key.is_a?(String) && key != "message"
|
|
270
|
+
|
|
271
|
+
# Check if the value is a string with attachment-like singleton methods
|
|
272
|
+
# (original_filename or content_type), which indicates it's a file content
|
|
273
|
+
value.is_a?(String) && (value.respond_to?(:original_filename) || value.respond_to?(:content_type))
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# If we have both a "message" field and attachment fields with file metadata,
|
|
277
|
+
# this indicates the payload was prepared by FileUtils.build_form_request
|
|
195
278
|
has_message_field && has_attachment_fields
|
|
196
279
|
end
|
|
197
280
|
|
|
@@ -414,6 +497,8 @@ module Nylas
|
|
|
414
497
|
|
|
415
498
|
# Set the authorization header for an API query.
|
|
416
499
|
def auth_header(api_key)
|
|
500
|
+
return {} if api_key.nil?
|
|
501
|
+
|
|
417
502
|
{ "Authorization" => "Bearer #{api_key}" }
|
|
418
503
|
end
|
|
419
504
|
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
require "json"
|
|
5
|
+
require "openssl"
|
|
6
|
+
require "securerandom"
|
|
7
|
+
|
|
8
|
+
module Nylas
|
|
9
|
+
# Builds Nylas Service Account request signing headers for organization admin APIs.
|
|
10
|
+
#
|
|
11
|
+
# @see https://developer.nylas.com/docs/v3/auth/nylas-service-account/
|
|
12
|
+
class ServiceAccountSigner
|
|
13
|
+
NONCE_ALPHABET = ("a".."z").to_a.concat(("A".."Z").to_a, ("0".."9").to_a).freeze
|
|
14
|
+
DEFAULT_NONCE_LENGTH = 20
|
|
15
|
+
SIGNED_BODY_METHODS = %w[post put patch].freeze
|
|
16
|
+
|
|
17
|
+
attr_reader :private_key_id
|
|
18
|
+
|
|
19
|
+
# @param private_key_pem [String] RSA private key in PEM format.
|
|
20
|
+
# @param private_key_id [String] Value for the X-Nylas-Kid header.
|
|
21
|
+
def initialize(private_key_pem:, private_key_id:)
|
|
22
|
+
@private_key = self.class.load_rsa_private_key(private_key_pem)
|
|
23
|
+
@private_key_id = private_key_id
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Returns deterministic JSON with keys sorted at every object level and no extra whitespace.
|
|
27
|
+
#
|
|
28
|
+
# @param data [Hash, Array, String, Numeric, true, false, nil] Data to serialize.
|
|
29
|
+
# @return [String] Canonical JSON string.
|
|
30
|
+
def self.canonical_json(data)
|
|
31
|
+
JSON.generate(canonicalize(data))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Loads an RSA private key from a PEM string.
|
|
35
|
+
#
|
|
36
|
+
# @param private_key_pem [String] RSA private key in PEM format.
|
|
37
|
+
# @return [OpenSSL::PKey::RSA]
|
|
38
|
+
def self.load_rsa_private_key(private_key_pem)
|
|
39
|
+
key = OpenSSL::PKey::RSA.new(private_key_pem)
|
|
40
|
+
raise ArgumentError, "Private key must be RSA private key" unless key.private?
|
|
41
|
+
raise ArgumentError, "Private key must be at least 2048 bits" if key.n.num_bits < 2048
|
|
42
|
+
|
|
43
|
+
key
|
|
44
|
+
rescue OpenSSL::PKey::PKeyError
|
|
45
|
+
raise ArgumentError, "Private key must be RSA PEM"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Generates a cryptographically secure alphanumeric nonce.
|
|
49
|
+
#
|
|
50
|
+
# @param length [Integer] Length of the nonce to generate.
|
|
51
|
+
# @return [String] Generated nonce.
|
|
52
|
+
def self.generate_nonce(length = DEFAULT_NONCE_LENGTH)
|
|
53
|
+
Array.new(length) { NONCE_ALPHABET[SecureRandom.random_number(NONCE_ALPHABET.length)] }.join
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Builds signed headers and, for JSON body methods, the exact canonical body to send.
|
|
57
|
+
#
|
|
58
|
+
# @param method [String, Symbol] HTTP method.
|
|
59
|
+
# @param path [String] Relative request path, for example "/v3/admin/domains".
|
|
60
|
+
# @param body [Hash, nil] Request body for POST/PUT/PATCH requests.
|
|
61
|
+
# @param timestamp [Integer, nil] Optional Unix timestamp in seconds, mainly for tests.
|
|
62
|
+
# @param nonce [String, nil] Optional nonce, mainly for tests.
|
|
63
|
+
# @return [Array(Hash, String)] Signed headers and optional serialized JSON body.
|
|
64
|
+
def build_headers(method:, path:, body: nil, timestamp: nil, nonce: nil)
|
|
65
|
+
timestamp ||= Time.now.to_i
|
|
66
|
+
nonce ||= self.class.generate_nonce
|
|
67
|
+
method_value = method.to_s.downcase
|
|
68
|
+
serialized_body = nil
|
|
69
|
+
|
|
70
|
+
if SIGNED_BODY_METHODS.include?(method_value) && !body.nil?
|
|
71
|
+
serialized_body = self.class.canonical_json(body)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
envelope = {
|
|
75
|
+
method: method_value,
|
|
76
|
+
nonce: nonce,
|
|
77
|
+
path: path,
|
|
78
|
+
timestamp: timestamp
|
|
79
|
+
}
|
|
80
|
+
envelope[:payload] = serialized_body if serialized_body
|
|
81
|
+
|
|
82
|
+
signature = @private_key.sign(OpenSSL::Digest.new("SHA256"), self.class.canonical_json(envelope))
|
|
83
|
+
|
|
84
|
+
[
|
|
85
|
+
{
|
|
86
|
+
"X-Nylas-Kid" => private_key_id,
|
|
87
|
+
"X-Nylas-Nonce" => nonce,
|
|
88
|
+
"X-Nylas-Timestamp" => timestamp.to_s,
|
|
89
|
+
"X-Nylas-Signature" => Base64.strict_encode64(signature)
|
|
90
|
+
},
|
|
91
|
+
serialized_body
|
|
92
|
+
]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
class << self
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def canonicalize(value)
|
|
99
|
+
case value
|
|
100
|
+
when Hash
|
|
101
|
+
value.keys.sort_by(&:to_s).each_with_object({}) do |key, result|
|
|
102
|
+
result[key.to_s] = canonicalize(value[key])
|
|
103
|
+
end
|
|
104
|
+
when Array
|
|
105
|
+
value.map { |item| canonicalize(item) }
|
|
106
|
+
else
|
|
107
|
+
value
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -8,6 +8,7 @@ module Nylas
|
|
|
8
8
|
# Application
|
|
9
9
|
class Applications < Resource
|
|
10
10
|
include ApiOperations::Get
|
|
11
|
+
include ApiOperations::Patch
|
|
11
12
|
|
|
12
13
|
attr_reader :redirect_uris
|
|
13
14
|
|
|
@@ -23,5 +24,18 @@ module Nylas
|
|
|
23
24
|
def get_details
|
|
24
25
|
get(path: "#{api_uri}/v3/applications")
|
|
25
26
|
end
|
|
27
|
+
|
|
28
|
+
# Update application details.
|
|
29
|
+
#
|
|
30
|
+
# @param request_body [Hash] The values to update the application with. Include
|
|
31
|
+
# +callback_uris+ entries with +id+ when preserving or updating existing
|
|
32
|
+
# callback URIs.
|
|
33
|
+
# @return [Array(Hash, String)] The updated application details and API Request ID.
|
|
34
|
+
def update(request_body:)
|
|
35
|
+
patch(
|
|
36
|
+
path: "#{api_uri}/v3/applications",
|
|
37
|
+
request_body: request_body
|
|
38
|
+
)
|
|
39
|
+
end
|
|
26
40
|
end
|
|
27
41
|
end
|
|
@@ -64,11 +64,13 @@ module Nylas
|
|
|
64
64
|
# Delete a booking.
|
|
65
65
|
# @param booking_id [String] The id of the booking to delete.
|
|
66
66
|
# @param query_params [Hash, nil] Query params to pass to the request.
|
|
67
|
+
# @param request_body [Hash, nil] Optional body params (e.g. cancellation_reason).
|
|
67
68
|
# @return [Array(TrueClass, String)] True and the API Request ID for the delete operation.
|
|
68
|
-
def destroy(booking_id:, query_params: nil)
|
|
69
|
+
def destroy(booking_id:, query_params: nil, request_body: nil)
|
|
69
70
|
_, request_id = delete(
|
|
70
71
|
path: "#{api_uri}/v3/scheduling/bookings/#{booking_id}",
|
|
71
|
-
query_params: query_params
|
|
72
|
+
query_params: query_params,
|
|
73
|
+
request_body: request_body
|
|
72
74
|
)
|
|
73
75
|
|
|
74
76
|
[true, request_id]
|