sfmc-fuelsdk-ruby 1.3.0 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +39 -39
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -20
  4. data/.gitignore +29 -29
  5. data/Gemfile +3 -3
  6. data/Gemfile.lock +104 -92
  7. data/Guardfile +8 -8
  8. data/LICENSE.md +13 -13
  9. data/README.md +200 -194
  10. data/Rakefile +1 -1
  11. data/lib/marketingcloudsdk.rb +74 -74
  12. data/lib/marketingcloudsdk/client.rb +395 -395
  13. data/lib/marketingcloudsdk/http_request.rb +118 -118
  14. data/lib/marketingcloudsdk/objects.rb +757 -757
  15. data/lib/marketingcloudsdk/rest.rb +118 -118
  16. data/lib/marketingcloudsdk/soap.rb +296 -296
  17. data/lib/marketingcloudsdk/targeting.rb +99 -99
  18. data/lib/marketingcloudsdk/utils.rb +47 -47
  19. data/lib/marketingcloudsdk/version.rb +39 -39
  20. data/lib/new.rb +1240 -1240
  21. data/marketingcloudsdk.gemspec +30 -30
  22. data/samples/sample-AddSubscriberToList.rb +56 -56
  23. data/samples/sample-CreateAndStartDataExtensionImport.rb +29 -29
  24. data/samples/sample-CreateAndStartListImport.rb +27 -27
  25. data/samples/sample-CreateContentAreas.rb +48 -48
  26. data/samples/sample-CreateDataExtensions.rb +54 -54
  27. data/samples/sample-CreateProfileAttributes.rb +48 -48
  28. data/samples/sample-SendEmailToDataExtension.rb +23 -23
  29. data/samples/sample-SendEmailToList.rb +23 -23
  30. data/samples/sample-SendTriggeredSends.rb +30 -30
  31. data/samples/sample-bounceevent.rb +70 -70
  32. data/samples/sample-campaign.rb +211 -211
  33. data/samples/sample-clickevent.rb +71 -71
  34. data/samples/sample-contentarea.rb +122 -122
  35. data/samples/sample-dataextension.rb +209 -209
  36. data/samples/sample-directverb.rb +54 -54
  37. data/samples/sample-email.rb +122 -122
  38. data/samples/sample-email.senddefinition.rb +134 -134
  39. data/samples/sample-folder.rb +143 -143
  40. data/samples/sample-import.rb +103 -103
  41. data/samples/sample-list.rb +105 -105
  42. data/samples/sample-list.subscriber.rb +97 -97
  43. data/samples/sample-openevent.rb +70 -70
  44. data/samples/sample-profileattribute.rb +56 -56
  45. data/samples/sample-sentevent.rb +70 -70
  46. data/samples/sample-subscriber.rb +135 -135
  47. data/samples/sample-triggeredsend.rb +129 -129
  48. data/samples/sample-unsubevent.rb +72 -72
  49. data/samples/sample_helper.rb.template +10 -10
  50. data/spec/client_spec.rb +416 -416
  51. data/spec/default_values_fallback_spec.rb +30 -30
  52. data/spec/helper_funcs_spec.rb +11 -11
  53. data/spec/http_request_spec.rb +61 -61
  54. data/spec/objects_helper_spec.rb +32 -32
  55. data/spec/objects_spec.rb +484 -484
  56. data/spec/public_or_web_integration_credentials.rb.template +11 -11
  57. data/spec/rest_spec.rb +48 -48
  58. data/spec/soap_spec.rb +140 -140
  59. data/spec/spec_helper.rb +14 -14
  60. data/spec/targeting_spec.rb +44 -44
  61. metadata +14 -27
data/Rakefile CHANGED
@@ -1 +1 @@
1
- require "bundler/gem_tasks"
1
+ require "bundler/gem_tasks"
@@ -1,74 +1,74 @@
1
- =begin
2
- Copyright (c) 2013 ExactTarget, Inc.
3
-
4
- All rights reserved.
5
-
6
- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
7
-
8
- following conditions are met:
9
-
10
- 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
11
-
12
- following disclaimer.
13
-
14
- 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
15
-
16
- following disclaimer in the documentation and/or other materials provided with the distribution.
17
-
18
- 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
19
-
20
- products derived from this software without specific prior written permission.
21
-
22
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
23
-
24
- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25
-
26
- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27
-
28
- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29
-
30
- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
31
-
32
- WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
33
-
34
- USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
- =end
36
-
37
- require "marketingcloudsdk/version"
38
-
39
- require 'rubygems'
40
- require 'date'
41
- require 'jwt'
42
-
43
- module MarketingCloudSDK
44
- require 'marketingcloudsdk/utils'
45
- autoload :HTTPRequest, 'marketingcloudsdk/http_request'
46
- autoload :Targeting, 'marketingcloudsdk/targeting'
47
- autoload :Soap, 'marketingcloudsdk/soap'
48
- autoload :Rest, 'marketingcloudsdk/rest'
49
- require 'marketingcloudsdk/client'
50
- require 'marketingcloudsdk/objects'
51
- end
52
-
53
- # backwards compatability
54
- ET_Client = MarketingCloudSDK::Client
55
- ET_BounceEvent = MarketingCloudSDK::BounceEvent
56
- ET_ClickEvent = MarketingCloudSDK::ClickEvent
57
- ET_ContentArea = MarketingCloudSDK::ContentArea
58
- ET_DataExtension = MarketingCloudSDK::DataExtension
59
- ET_DataFolder = MarketingCloudSDK::DataFolder
60
- ET_Folder = MarketingCloudSDK::Folder
61
- ET_Email = MarketingCloudSDK::Email
62
- ET_List = MarketingCloudSDK::List
63
- ET_OpenEvent = MarketingCloudSDK::OpenEvent
64
- ET_SentEvent = MarketingCloudSDK::SentEvent
65
- ET_Subscriber = MarketingCloudSDK::Subscriber
66
- ET_UnsubEvent = MarketingCloudSDK::UnsubEvent
67
- ET_TriggeredSend = MarketingCloudSDK::TriggeredSend
68
- ET_Campaign = MarketingCloudSDK::Campaign
69
- ET_Get = MarketingCloudSDK::Get
70
- ET_Post = MarketingCloudSDK::Post
71
- ET_Delete = MarketingCloudSDK::Delete
72
- ET_Patch = MarketingCloudSDK::Patch
73
- ET_ProfileAttribute = MarketingCloudSDK::ProfileAttribute
74
- ET_Import = MarketingCloudSDK::Import
1
+ =begin
2
+ Copyright (c) 2013 ExactTarget, Inc.
3
+
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
7
+
8
+ following conditions are met:
9
+
10
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
11
+
12
+ following disclaimer.
13
+
14
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
15
+
16
+ following disclaimer in the documentation and/or other materials provided with the distribution.
17
+
18
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
19
+
20
+ products derived from this software without specific prior written permission.
21
+
22
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
23
+
24
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25
+
26
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27
+
28
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29
+
30
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
31
+
32
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
33
+
34
+ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
+ =end
36
+
37
+ require "marketingcloudsdk/version"
38
+
39
+ require 'rubygems'
40
+ require 'date'
41
+ require 'jwt'
42
+
43
+ module MarketingCloudSDK
44
+ require 'marketingcloudsdk/utils'
45
+ autoload :HTTPRequest, 'marketingcloudsdk/http_request'
46
+ autoload :Targeting, 'marketingcloudsdk/targeting'
47
+ autoload :Soap, 'marketingcloudsdk/soap'
48
+ autoload :Rest, 'marketingcloudsdk/rest'
49
+ require 'marketingcloudsdk/client'
50
+ require 'marketingcloudsdk/objects'
51
+ end
52
+
53
+ # backwards compatability
54
+ ET_Client = MarketingCloudSDK::Client
55
+ ET_BounceEvent = MarketingCloudSDK::BounceEvent
56
+ ET_ClickEvent = MarketingCloudSDK::ClickEvent
57
+ ET_ContentArea = MarketingCloudSDK::ContentArea
58
+ ET_DataExtension = MarketingCloudSDK::DataExtension
59
+ ET_DataFolder = MarketingCloudSDK::DataFolder
60
+ ET_Folder = MarketingCloudSDK::Folder
61
+ ET_Email = MarketingCloudSDK::Email
62
+ ET_List = MarketingCloudSDK::List
63
+ ET_OpenEvent = MarketingCloudSDK::OpenEvent
64
+ ET_SentEvent = MarketingCloudSDK::SentEvent
65
+ ET_Subscriber = MarketingCloudSDK::Subscriber
66
+ ET_UnsubEvent = MarketingCloudSDK::UnsubEvent
67
+ ET_TriggeredSend = MarketingCloudSDK::TriggeredSend
68
+ ET_Campaign = MarketingCloudSDK::Campaign
69
+ ET_Get = MarketingCloudSDK::Get
70
+ ET_Post = MarketingCloudSDK::Post
71
+ ET_Delete = MarketingCloudSDK::Delete
72
+ ET_Patch = MarketingCloudSDK::Patch
73
+ ET_ProfileAttribute = MarketingCloudSDK::ProfileAttribute
74
+ ET_Import = MarketingCloudSDK::Import
@@ -1,395 +1,395 @@
1
- =begin
2
- Copyright (c) 2013 ExactTarget, Inc.
3
-
4
- All rights reserved.
5
-
6
- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
7
-
8
- following conditions are met:
9
-
10
- 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
11
-
12
- following disclaimer.
13
-
14
- 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
15
-
16
- following disclaimer in the documentation and/or other materials provided with the distribution.
17
-
18
- 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
19
-
20
- products derived from this software without specific prior written permission.
21
-
22
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
23
-
24
- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25
-
26
- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27
-
28
- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29
-
30
- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
31
-
32
- WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
33
-
34
- USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
-
36
- =end
37
-
38
- require 'securerandom'
39
- module MarketingCloudSDK
40
- class Response
41
- # not doing accessor so user, can't update these values from response.
42
- # You will see in the code some of these
43
- # items are being updated via back doors and such.
44
- attr_reader :code, :message, :results, :request_id, :body, :raw
45
-
46
- # some defaults
47
- def success
48
- @success ||= false
49
- end
50
- alias :success? :success
51
- alias :status :success # backward compatibility
52
-
53
- def more
54
- @more ||= false
55
- end
56
- alias :more? :more
57
-
58
- def initialize raw, client
59
- @client = client # keep connection with client in case we request more
60
- @results = []
61
- @raw = raw
62
- unpack raw
63
- rescue => ex # all else fails return raw
64
- puts ex.message
65
- raw
66
- end
67
-
68
- def continue
69
- raise NotImplementedError
70
- end
71
-
72
- private
73
- def unpack raw
74
- raise NotImplementedError
75
- end
76
- end
77
-
78
- class Client
79
- attr_accessor :debug, :access_token, :auth_token, :internal_token, :refresh_token,
80
- :id, :secret, :signature, :base_api_url, :package_name, :package_folders, :parent_folders, :auth_token_expiration,
81
- :request_token_url, :soap_endpoint, :use_oAuth2_authentication, :account_id, :scope, :application_type, :authorization_code, :redirect_URI
82
-
83
- include MarketingCloudSDK::Soap
84
- include MarketingCloudSDK::Rest
85
-
86
- def jwt= encoded_jwt
87
- raise 'Require app signature to decode JWT' unless self.signature
88
- decoded_jwt = JWT.decode(encoded_jwt, self.signature, true)
89
- decoded_jwt_first = decoded_jwt.first
90
-
91
- self.auth_token = decoded_jwt_first['request']['user']['oauthToken']
92
- self.internal_token = decoded_jwt_first['request']['user']['internalOauthToken']
93
- self.refresh_token = decoded_jwt_first['request']['user']['refreshToken']
94
- self.auth_token_expiration = Time.new + decoded_jwt_first['request']['user']['expiresIn']
95
- self.package_name = decoded_jwt_first['request']['application']['package']
96
- end
97
-
98
- def initialize(params={}, debug=false)
99
- @refresh_mutex = Mutex.new
100
- self.debug = debug
101
- client_config = params['client']
102
- if client_config
103
- self.id = client_config["id"]
104
- self.secret = client_config["secret"]
105
- self.signature = client_config["signature"]
106
- self.base_api_url = !(client_config["base_api_url"].to_s.strip.empty?) ? client_config["base_api_url"] : 'https://www.exacttargetapis.com'
107
- self.request_token_url = client_config["request_token_url"]
108
- self.soap_endpoint = client_config["soap_endpoint"]
109
- self.use_oAuth2_authentication = client_config["use_oAuth2_authentication"]
110
- self.account_id = client_config["account_id"]
111
- self.scope = client_config["scope"]
112
- self.application_type = client_config["application_type"]
113
- self.authorization_code = client_config["authorization_code"]
114
- self.redirect_URI = client_config["redirect_URI"]
115
- end
116
-
117
- # Set a default value in case no 'client' params is sent
118
- if (!self.base_api_url)
119
- self.base_api_url = 'https://www.exacttargetapis.com'
120
- end
121
-
122
- if (self.request_token_url.to_s.strip.empty?)
123
- if(use_oAuth2_authentication == true)
124
- raise 'request_token_url (Auth TSE) is mandatory when using OAuth2 authentication'
125
- else
126
- self.request_token_url = 'https://auth.exacttargetapis.com/v1/requestToken'
127
- end
128
- end
129
-
130
- if application_type.to_s.strip.empty?
131
- self.application_type = 'server'
132
- end
133
-
134
- if ['web', 'public'].include? application_type
135
- if authorization_code.to_s.strip.empty? or redirect_URI.to_s.strip.empty?
136
- raise 'authorization_code or redirect_URI is null: For Public/Web Apps, the authorization_code and redirect_URI must be passed when instantiating Client'
137
- end
138
- end
139
-
140
- if application_type == 'public'
141
- if id.to_s.strip.empty?
142
- raise 'id is null: id must be passed when instantiating Client'
143
- end
144
- else
145
- if id.to_s.strip.empty? or secret.to_s.strip.empty?
146
- raise 'id and secret must pe passed when instantiating Client'
147
- end
148
- end
149
-
150
- self.jwt = params['jwt'] if params['jwt']
151
- self.refresh_token = params['refresh_token'] if params['refresh_token']
152
-
153
- self.wsdl = params["defaultwsdl"] if params["defaultwsdl"]
154
-
155
- self.refresh
156
- end
157
-
158
- def refresh force=false
159
- @refresh_mutex.synchronize do
160
-
161
- if (self.use_oAuth2_authentication == true)
162
- self.refreshWithOAuth2(force)
163
- return
164
- end
165
-
166
- #If we don't already have a token or the token expires within 5 min(300 seconds)
167
- if (self.access_token.nil? || Time.new + 300 > self.auth_token_expiration || force) then
168
- payload = Hash.new.tap do |h|
169
- h['clientId']= id
170
- h['clientSecret'] = secret
171
- h['refreshToken'] = refresh_token if refresh_token
172
- h['accessType'] = 'offline'
173
- end
174
-
175
- options = Hash.new.tap do |h|
176
- h['data'] = payload
177
- h['content_type'] = 'application/json'
178
- h['params'] = {'legacy' => 1}
179
- end
180
- response = post(request_token_url, options)
181
- raise "Unable to refresh token: #{response['message']}" unless response.has_key?('accessToken')
182
-
183
- self.access_token = response['accessToken']
184
- self.internal_token = response['legacyToken']
185
- self.auth_token_expiration = Time.new + response['expiresIn']
186
- self.refresh_token = response['refreshToken'] if response.has_key?("refreshToken")
187
- return true
188
- else
189
- return false
190
- end
191
- end
192
- end
193
-
194
- def refreshWithOAuth2 force=false
195
- #If we don't already have a token or the token expires within 5 min(300 seconds)
196
- if (self.access_token.nil? || Time.new + 300 > self.auth_token_expiration || force) then
197
-
198
- payload = createPayload
199
-
200
- options = Hash.new.tap do |h|
201
- h['data'] = payload
202
- h['content_type'] = 'application/json'
203
- end
204
-
205
- auth_endpoint = request_token_url + '/v2/token'
206
-
207
- response = post(auth_endpoint, options)
208
- raise "Unable to refresh token: #{response['message']}" unless response.has_key?('access_token')
209
-
210
- self.access_token = response['access_token']
211
- self.auth_token_expiration = Time.new + response['expires_in']
212
- self.soap_endpoint = response['soap_instance_url'] + 'service.asmx'
213
- self.base_api_url = response['rest_instance_url']
214
-
215
- if response.has_key?('refresh_token')
216
- self.refresh_token = response['refresh_token']
217
- end
218
-
219
- return true
220
- else
221
- return false
222
- end
223
- end
224
-
225
- def createPayload
226
- payload = Hash.new.tap do |h|
227
- h['client_id'] = id
228
-
229
- if application_type != 'public'
230
- h['client_secret'] = secret
231
- end
232
-
233
- if !refresh_token.to_s.strip.empty?
234
- h['grant_type'] = 'refresh_token'
235
- h['refresh_token'] = refresh_token
236
- elsif ['web', 'public'].include? application_type
237
- h['grant_type'] = 'authorization_code'
238
- h['code'] = authorization_code
239
- h['redirect_uri'] = redirect_URI
240
- else
241
- h['grant_type'] = 'client_credentials'
242
- end
243
-
244
- unless account_id.to_s.strip.empty?
245
- h['account_id'] = account_id
246
- end
247
-
248
- unless scope.to_s.strip.empty?
249
- h['scope'] = scope
250
- end
251
- end
252
-
253
- payload
254
- end
255
-
256
- def refresh!
257
- refresh true
258
- end
259
-
260
- def AddSubscriberToList(email, ids, subscriber_key = nil)
261
- s = MarketingCloudSDK::Subscriber.new
262
- s.client = self
263
- lists = ids.collect{|id| {'ID' => id}}
264
- s.properties = {"EmailAddress" => email, "Lists" => lists}
265
- p s.properties
266
- s.properties['SubscriberKey'] = subscriber_key if subscriber_key
267
-
268
- # Try to add the subscriber
269
- if(rsp = s.post and rsp.results.first[:error_code] == '12014')
270
- # subscriber already exists we need to update.
271
- rsp = s.patch
272
- end
273
- rsp
274
- end
275
-
276
- def CreateDataExtensions(definitions)
277
- de = MarketingCloudSDK::DataExtension.new
278
- de.client = self
279
- de.properties = definitions
280
- de.post
281
- end
282
- def SendTriggeredSends(arrayOfTriggeredRecords)
283
- sendTS = ET_TriggeredSend.new
284
- sendTS.authStub = self
285
-
286
- sendTS.properties = arrayOfTriggeredRecords
287
- sendResponse = sendTS.send
288
-
289
- return sendResponse
290
- end
291
- def SendEmailToList(emailID, listID, sendClassificationCustomerKey)
292
- email = ET_Email::SendDefinition.new
293
- email.properties = {"Name"=>SecureRandom.uuid, "CustomerKey"=>SecureRandom.uuid, "Description"=>"Created with RubySDK"}
294
- email.properties["SendClassification"] = {"CustomerKey"=>sendClassificationCustomerKey}
295
- email.properties["SendDefinitionList"] = {"List"=> {"ID"=>listID}, "DataSourceTypeID"=>"List"}
296
- email.properties["Email"] = {"ID"=>emailID}
297
- email.authStub = self
298
- result = email.post
299
- if result.status then
300
- sendresult = email.send
301
- if sendresult.status then
302
- deleteresult = email.delete
303
- return sendresult
304
- else
305
- raise "Unable to send using send definition due to: #{result.results[0][:status_message]}"
306
- end
307
- else
308
- raise "Unable to create send definition due to: #{result.results[0][:status_message]}"
309
- end
310
- end
311
-
312
- def SendEmailToDataExtension(emailID, sendableDataExtensionCustomerKey, sendClassificationCustomerKey)
313
- email = ET_Email::SendDefinition.new
314
- email.properties = {"Name"=>SecureRandom.uuid, "CustomerKey"=>SecureRandom.uuid, "Description"=>"Created with RubySDK"}
315
- email.properties["SendClassification"] = {"CustomerKey"=> sendClassificationCustomerKey}
316
- email.properties["SendDefinitionList"] = {"CustomerKey"=> sendableDataExtensionCustomerKey, "DataSourceTypeID"=>"CustomObject"}
317
- email.properties["Email"] = {"ID"=>emailID}
318
- email.authStub = self
319
- result = email.post
320
- if result.status then
321
- sendresult = email.send
322
- if sendresult.status then
323
- deleteresult = email.delete
324
- return sendresult
325
- else
326
- raise "Unable to send using send definition due to: #{result.results[0][:status_message]}"
327
- end
328
- else
329
- raise "Unable to create send definition due to: #{result.results[0][:status_message]}"
330
- end
331
- end
332
- def CreateAndStartListImport(listId,fileName)
333
- import = ET_Import.new
334
- import.authStub = self
335
- import.properties = {"Name"=> "SDK Generated Import #{DateTime.now.to_s}"}
336
- import.properties["CustomerKey"] = SecureRandom.uuid
337
- import.properties["Description"] = "SDK Generated Import"
338
- import.properties["AllowErrors"] = "true"
339
- import.properties["DestinationObject"] = {"ID"=>listId}
340
- import.properties["FieldMappingType"] = "InferFromColumnHeadings"
341
- import.properties["FileSpec"] = fileName
342
- import.properties["FileType"] = "CSV"
343
- import.properties["RetrieveFileTransferLocation"] = {"CustomerKey"=>"ExactTarget Enhanced FTP"}
344
- import.properties["UpdateType"] = "AddAndUpdate"
345
- result = import.post
346
-
347
- if result.status then
348
- return import.start
349
- else
350
- raise "Unable to create import definition due to: #{result.results[0][:status_message]}"
351
- end
352
- end
353
-
354
- def CreateAndStartDataExtensionImport(dataExtensionCustomerKey, fileName, overwrite)
355
- import = ET_Import.new
356
- import.authStub = self
357
- import.properties = {"Name"=> "SDK Generated Import #{DateTime.now.to_s}"}
358
- import.properties["CustomerKey"] = SecureRandom.uuid
359
- import.properties["Description"] = "SDK Generated Import"
360
- import.properties["AllowErrors"] = "true"
361
- import.properties["DestinationObject"] = {"CustomerKey"=>dataExtensionCustomerKey}
362
- import.properties["FieldMappingType"] = "InferFromColumnHeadings"
363
- import.properties["FileSpec"] = fileName
364
- import.properties["FileType"] = "CSV"
365
- import.properties["RetrieveFileTransferLocation"] = {"CustomerKey"=>"ExactTarget Enhanced FTP"}
366
- if overwrite then
367
- import.properties["UpdateType"] = "Overwrite"
368
- else
369
- import.properties["UpdateType"] = "AddAndUpdate"
370
- end
371
- result = import.post
372
-
373
- if result.status then
374
- return import.start
375
- else
376
- raise "Unable to create import definition due to: #{result.results[0][:status_message]}"
377
- end
378
- end
379
-
380
- def CreateProfileAttributes(allAttributes)
381
- attrs = ET_ProfileAttribute.new
382
- attrs.authStub = self
383
- attrs.properties = allAttributes
384
- return attrs.post
385
- end
386
-
387
- def CreateContentAreas(arrayOfContentAreas)
388
- postC = ET_ContentArea.new
389
- postC.authStub = self
390
- postC.properties = arrayOfContentAreas
391
- sendResponse = postC.post
392
- return sendResponse
393
- end
394
- end
395
- end
1
+ =begin
2
+ Copyright (c) 2013 ExactTarget, Inc.
3
+
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
7
+
8
+ following conditions are met:
9
+
10
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
11
+
12
+ following disclaimer.
13
+
14
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
15
+
16
+ following disclaimer in the documentation and/or other materials provided with the distribution.
17
+
18
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
19
+
20
+ products derived from this software without specific prior written permission.
21
+
22
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
23
+
24
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25
+
26
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27
+
28
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29
+
30
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
31
+
32
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
33
+
34
+ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
+
36
+ =end
37
+
38
+ require 'securerandom'
39
+ module MarketingCloudSDK
40
+ class Response
41
+ # not doing accessor so user, can't update these values from response.
42
+ # You will see in the code some of these
43
+ # items are being updated via back doors and such.
44
+ attr_reader :code, :message, :results, :request_id, :body, :raw
45
+
46
+ # some defaults
47
+ def success
48
+ @success ||= false
49
+ end
50
+ alias :success? :success
51
+ alias :status :success # backward compatibility
52
+
53
+ def more
54
+ @more ||= false
55
+ end
56
+ alias :more? :more
57
+
58
+ def initialize raw, client
59
+ @client = client # keep connection with client in case we request more
60
+ @results = []
61
+ @raw = raw
62
+ unpack raw
63
+ rescue => ex # all else fails return raw
64
+ puts ex.message
65
+ raw
66
+ end
67
+
68
+ def continue
69
+ raise NotImplementedError
70
+ end
71
+
72
+ private
73
+ def unpack raw
74
+ raise NotImplementedError
75
+ end
76
+ end
77
+
78
+ class Client
79
+ attr_accessor :debug, :access_token, :auth_token, :internal_token, :refresh_token,
80
+ :id, :secret, :signature, :base_api_url, :package_name, :package_folders, :parent_folders, :auth_token_expiration,
81
+ :request_token_url, :soap_endpoint, :use_oAuth2_authentication, :account_id, :scope, :application_type, :authorization_code, :redirect_URI
82
+
83
+ include MarketingCloudSDK::Soap
84
+ include MarketingCloudSDK::Rest
85
+
86
+ def jwt= encoded_jwt
87
+ raise 'Require app signature to decode JWT' unless self.signature
88
+ decoded_jwt = JWT.decode(encoded_jwt, self.signature, true)
89
+ decoded_jwt_first = decoded_jwt.first
90
+
91
+ self.auth_token = decoded_jwt_first['request']['user']['oauthToken']
92
+ self.internal_token = decoded_jwt_first['request']['user']['internalOauthToken']
93
+ self.refresh_token = decoded_jwt_first['request']['user']['refreshToken']
94
+ self.auth_token_expiration = Time.new + decoded_jwt_first['request']['user']['expiresIn']
95
+ self.package_name = decoded_jwt_first['request']['application']['package']
96
+ end
97
+
98
+ def initialize(params={}, debug=false)
99
+ @refresh_mutex = Mutex.new
100
+ self.debug = debug
101
+ client_config = params['client']
102
+ if client_config
103
+ self.id = client_config["id"]
104
+ self.secret = client_config["secret"]
105
+ self.signature = client_config["signature"]
106
+ self.base_api_url = !(client_config["base_api_url"].to_s.strip.empty?) ? client_config["base_api_url"] : 'https://www.exacttargetapis.com'
107
+ self.request_token_url = client_config["request_token_url"]
108
+ self.soap_endpoint = client_config["soap_endpoint"]
109
+ self.use_oAuth2_authentication = client_config["use_oAuth2_authentication"]
110
+ self.account_id = client_config["account_id"]
111
+ self.scope = client_config["scope"]
112
+ self.application_type = client_config["application_type"]
113
+ self.authorization_code = client_config["authorization_code"]
114
+ self.redirect_URI = client_config["redirect_URI"]
115
+ end
116
+
117
+ # Set a default value in case no 'client' params is sent
118
+ if (!self.base_api_url)
119
+ self.base_api_url = 'https://www.exacttargetapis.com'
120
+ end
121
+
122
+ if (self.request_token_url.to_s.strip.empty?)
123
+ if(use_oAuth2_authentication == true)
124
+ raise 'request_token_url (Auth TSE) is mandatory when using OAuth2 authentication'
125
+ else
126
+ self.request_token_url = 'https://auth.exacttargetapis.com/v1/requestToken'
127
+ end
128
+ end
129
+
130
+ if application_type.to_s.strip.empty?
131
+ self.application_type = 'server'
132
+ end
133
+
134
+ if ['web', 'public'].include? application_type
135
+ if authorization_code.to_s.strip.empty? or redirect_URI.to_s.strip.empty?
136
+ raise 'authorization_code or redirect_URI is null: For Public/Web Apps, the authorization_code and redirect_URI must be passed when instantiating Client'
137
+ end
138
+ end
139
+
140
+ if application_type == 'public'
141
+ if id.to_s.strip.empty?
142
+ raise 'id is null: id must be passed when instantiating Client'
143
+ end
144
+ else
145
+ if id.to_s.strip.empty? or secret.to_s.strip.empty?
146
+ raise 'id and secret must pe passed when instantiating Client'
147
+ end
148
+ end
149
+
150
+ self.jwt = params['jwt'] if params['jwt']
151
+ self.refresh_token = params['refresh_token'] if params['refresh_token']
152
+
153
+ self.wsdl = params["defaultwsdl"] if params["defaultwsdl"]
154
+
155
+ self.refresh
156
+ end
157
+
158
+ def refresh force=false
159
+ @refresh_mutex.synchronize do
160
+
161
+ if (self.use_oAuth2_authentication == true)
162
+ self.refreshWithOAuth2(force)
163
+ return
164
+ end
165
+
166
+ #If we don't already have a token or the token expires within 5 min(300 seconds)
167
+ if (self.access_token.nil? || Time.new + 300 > self.auth_token_expiration || force) then
168
+ payload = Hash.new.tap do |h|
169
+ h['clientId']= id
170
+ h['clientSecret'] = secret
171
+ h['refreshToken'] = refresh_token if refresh_token
172
+ h['accessType'] = 'offline'
173
+ end
174
+
175
+ options = Hash.new.tap do |h|
176
+ h['data'] = payload
177
+ h['content_type'] = 'application/json'
178
+ h['params'] = {'legacy' => 1}
179
+ end
180
+ response = post(request_token_url, options)
181
+ raise "Unable to refresh token: #{response['message']}" unless response.has_key?('accessToken')
182
+
183
+ self.access_token = response['accessToken']
184
+ self.internal_token = response['legacyToken']
185
+ self.auth_token_expiration = Time.new + response['expiresIn']
186
+ self.refresh_token = response['refreshToken'] if response.has_key?("refreshToken")
187
+ return true
188
+ else
189
+ return false
190
+ end
191
+ end
192
+ end
193
+
194
+ def refreshWithOAuth2 force=false
195
+ #If we don't already have a token or the token expires within 5 min(300 seconds)
196
+ if (self.access_token.nil? || Time.new + 300 > self.auth_token_expiration || force) then
197
+
198
+ payload = createPayload
199
+
200
+ options = Hash.new.tap do |h|
201
+ h['data'] = payload
202
+ h['content_type'] = 'application/json'
203
+ end
204
+
205
+ auth_endpoint = request_token_url + '/v2/token'
206
+
207
+ response = post(auth_endpoint, options)
208
+ raise "Unable to refresh token: #{response['message']}" unless response.has_key?('access_token')
209
+
210
+ self.access_token = response['access_token']
211
+ self.auth_token_expiration = Time.new + response['expires_in']
212
+ self.soap_endpoint = response['soap_instance_url'] + 'service.asmx'
213
+ self.base_api_url = response['rest_instance_url']
214
+
215
+ if response.has_key?('refresh_token')
216
+ self.refresh_token = response['refresh_token']
217
+ end
218
+
219
+ return true
220
+ else
221
+ return false
222
+ end
223
+ end
224
+
225
+ def createPayload
226
+ payload = Hash.new.tap do |h|
227
+ h['client_id'] = id
228
+
229
+ if application_type != 'public'
230
+ h['client_secret'] = secret
231
+ end
232
+
233
+ if !refresh_token.to_s.strip.empty?
234
+ h['grant_type'] = 'refresh_token'
235
+ h['refresh_token'] = refresh_token
236
+ elsif ['web', 'public'].include? application_type
237
+ h['grant_type'] = 'authorization_code'
238
+ h['code'] = authorization_code
239
+ h['redirect_uri'] = redirect_URI
240
+ else
241
+ h['grant_type'] = 'client_credentials'
242
+ end
243
+
244
+ unless account_id.to_s.strip.empty?
245
+ h['account_id'] = account_id
246
+ end
247
+
248
+ unless scope.to_s.strip.empty?
249
+ h['scope'] = scope
250
+ end
251
+ end
252
+
253
+ payload
254
+ end
255
+
256
+ def refresh!
257
+ refresh true
258
+ end
259
+
260
+ def AddSubscriberToList(email, ids, subscriber_key = nil)
261
+ s = MarketingCloudSDK::Subscriber.new
262
+ s.client = self
263
+ lists = ids.collect{|id| {'ID' => id}}
264
+ s.properties = {"EmailAddress" => email, "Lists" => lists}
265
+ p s.properties
266
+ s.properties['SubscriberKey'] = subscriber_key if subscriber_key
267
+
268
+ # Try to add the subscriber
269
+ if(rsp = s.post and rsp.results.first[:error_code] == '12014')
270
+ # subscriber already exists we need to update.
271
+ rsp = s.patch
272
+ end
273
+ rsp
274
+ end
275
+
276
+ def CreateDataExtensions(definitions)
277
+ de = MarketingCloudSDK::DataExtension.new
278
+ de.client = self
279
+ de.properties = definitions
280
+ de.post
281
+ end
282
+ def SendTriggeredSends(arrayOfTriggeredRecords)
283
+ sendTS = ET_TriggeredSend.new
284
+ sendTS.authStub = self
285
+
286
+ sendTS.properties = arrayOfTriggeredRecords
287
+ sendResponse = sendTS.send
288
+
289
+ return sendResponse
290
+ end
291
+ def SendEmailToList(emailID, listID, sendClassificationCustomerKey)
292
+ email = ET_Email::SendDefinition.new
293
+ email.properties = {"Name"=>SecureRandom.uuid, "CustomerKey"=>SecureRandom.uuid, "Description"=>"Created with RubySDK"}
294
+ email.properties["SendClassification"] = {"CustomerKey"=>sendClassificationCustomerKey}
295
+ email.properties["SendDefinitionList"] = {"List"=> {"ID"=>listID}, "DataSourceTypeID"=>"List"}
296
+ email.properties["Email"] = {"ID"=>emailID}
297
+ email.authStub = self
298
+ result = email.post
299
+ if result.status then
300
+ sendresult = email.send
301
+ if sendresult.status then
302
+ deleteresult = email.delete
303
+ return sendresult
304
+ else
305
+ raise "Unable to send using send definition due to: #{result.results[0][:status_message]}"
306
+ end
307
+ else
308
+ raise "Unable to create send definition due to: #{result.results[0][:status_message]}"
309
+ end
310
+ end
311
+
312
+ def SendEmailToDataExtension(emailID, sendableDataExtensionCustomerKey, sendClassificationCustomerKey)
313
+ email = ET_Email::SendDefinition.new
314
+ email.properties = {"Name"=>SecureRandom.uuid, "CustomerKey"=>SecureRandom.uuid, "Description"=>"Created with RubySDK"}
315
+ email.properties["SendClassification"] = {"CustomerKey"=> sendClassificationCustomerKey}
316
+ email.properties["SendDefinitionList"] = {"CustomerKey"=> sendableDataExtensionCustomerKey, "DataSourceTypeID"=>"CustomObject"}
317
+ email.properties["Email"] = {"ID"=>emailID}
318
+ email.authStub = self
319
+ result = email.post
320
+ if result.status then
321
+ sendresult = email.send
322
+ if sendresult.status then
323
+ deleteresult = email.delete
324
+ return sendresult
325
+ else
326
+ raise "Unable to send using send definition due to: #{result.results[0][:status_message]}"
327
+ end
328
+ else
329
+ raise "Unable to create send definition due to: #{result.results[0][:status_message]}"
330
+ end
331
+ end
332
+ def CreateAndStartListImport(listId,fileName)
333
+ import = ET_Import.new
334
+ import.authStub = self
335
+ import.properties = {"Name"=> "SDK Generated Import #{DateTime.now.to_s}"}
336
+ import.properties["CustomerKey"] = SecureRandom.uuid
337
+ import.properties["Description"] = "SDK Generated Import"
338
+ import.properties["AllowErrors"] = "true"
339
+ import.properties["DestinationObject"] = {"ID"=>listId}
340
+ import.properties["FieldMappingType"] = "InferFromColumnHeadings"
341
+ import.properties["FileSpec"] = fileName
342
+ import.properties["FileType"] = "CSV"
343
+ import.properties["RetrieveFileTransferLocation"] = {"CustomerKey"=>"ExactTarget Enhanced FTP"}
344
+ import.properties["UpdateType"] = "AddAndUpdate"
345
+ result = import.post
346
+
347
+ if result.status then
348
+ return import.start
349
+ else
350
+ raise "Unable to create import definition due to: #{result.results[0][:status_message]}"
351
+ end
352
+ end
353
+
354
+ def CreateAndStartDataExtensionImport(dataExtensionCustomerKey, fileName, overwrite)
355
+ import = ET_Import.new
356
+ import.authStub = self
357
+ import.properties = {"Name"=> "SDK Generated Import #{DateTime.now.to_s}"}
358
+ import.properties["CustomerKey"] = SecureRandom.uuid
359
+ import.properties["Description"] = "SDK Generated Import"
360
+ import.properties["AllowErrors"] = "true"
361
+ import.properties["DestinationObject"] = {"CustomerKey"=>dataExtensionCustomerKey}
362
+ import.properties["FieldMappingType"] = "InferFromColumnHeadings"
363
+ import.properties["FileSpec"] = fileName
364
+ import.properties["FileType"] = "CSV"
365
+ import.properties["RetrieveFileTransferLocation"] = {"CustomerKey"=>"ExactTarget Enhanced FTP"}
366
+ if overwrite then
367
+ import.properties["UpdateType"] = "Overwrite"
368
+ else
369
+ import.properties["UpdateType"] = "AddAndUpdate"
370
+ end
371
+ result = import.post
372
+
373
+ if result.status then
374
+ return import.start
375
+ else
376
+ raise "Unable to create import definition due to: #{result.results[0][:status_message]}"
377
+ end
378
+ end
379
+
380
+ def CreateProfileAttributes(allAttributes)
381
+ attrs = ET_ProfileAttribute.new
382
+ attrs.authStub = self
383
+ attrs.properties = allAttributes
384
+ return attrs.post
385
+ end
386
+
387
+ def CreateContentAreas(arrayOfContentAreas)
388
+ postC = ET_ContentArea.new
389
+ postC.authStub = self
390
+ postC.properties = arrayOfContentAreas
391
+ sendResponse = postC.post
392
+ return sendResponse
393
+ end
394
+ end
395
+ end