kennethkalmer-postini 0.1.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.
@@ -0,0 +1,6 @@
1
+ require File.dirname(__FILE__) + "/../../lib/postini4r"
2
+
3
+ gem 'cucumber'
4
+ require 'cucumber'
5
+ gem 'rspec'
6
+ require 'spec'
@@ -0,0 +1,11 @@
1
+ module Matchers
2
+ def contain(expected)
3
+ simple_matcher("contain #{expected.inspect}") do |given, matcher|
4
+ matcher.failure_message = "expected #{given.inspect} to contain #{expected.inspect}"
5
+ matcher.negative_failure_message = "expected #{given.inspect} not to contain #{expected.inspect}"
6
+ given.index expected
7
+ end
8
+ end
9
+ end
10
+
11
+ World(Matchers)
@@ -0,0 +1,119 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ # requirements
5
+ require 'rubygems'
6
+ require 'handsoap'
7
+
8
+ require 'postini/exceptions'
9
+
10
+ # = Configuration
11
+ #
12
+ # This module just provides access to configuration details used by the rest of
13
+ # the gem.
14
+ #
15
+ # Current configuration values are:
16
+ # * api_key
17
+ # * system_number
18
+ # * username
19
+ # * password
20
+ #
21
+ # The +api_key+ is your unique API keys obtained from Postini via their Early
22
+ # Access program.
23
+ #
24
+ # Postini.api_key = "your48characterkey"
25
+ #
26
+ # The +system_number+ can be any known system number (known to your account).
27
+ # The gem uses the Postini Endpoint Resolver service, but in some cases needs a
28
+ # known system to work from. Refer to the "Locating Your System" section of the
29
+ # Early Access documentation for more information.
30
+ #
31
+ # Postini.system_number = 8
32
+ #
33
+ # The +username+ parameter is required if:
34
+ # 1. Your org is using POP authentication, and thus needs the admin user's
35
+ # authentication details, not the end-user's.
36
+ # 2. You omit the optional username and password string for the request
37
+ #
38
+ # Postini.username = 'administrat@jumboinc.com'
39
+ #
40
+ # The +password+ parameter is require if:
41
+ # 1. Your org is using POP authentication, and thus needs the admin
42
+ # user's authentication details, not the end-users's.
43
+ # 2. You can omit the optional username and password arguments for a
44
+ # specific request.
45
+ #
46
+ # Postini.password = 'secret'
47
+ #
48
+ # All of the above configurations can either be preloaded from a yaml
49
+ # file or environment variables. Create a yaml file with the following
50
+ # structure:
51
+ #
52
+ # api_key:
53
+ # system_number:
54
+ # username:
55
+ # password:
56
+ #
57
+ # And call +Postini.configure!('/path/to/file.yml')+ to load the
58
+ # values. Alternatively you can set the following environment variables
59
+ # and call +Postini.configure!+ without a path.
60
+ #
61
+ # POSTINI_API_KEY
62
+ # POSTINI_SYSTEM_NUMBER
63
+ # POSTINI_USERNAME
64
+ # POSTINI_PASSWORD
65
+ #
66
+ # = Debugging
67
+ #
68
+ # Lots to debug when an API is classified as "Early access", here are different
69
+ # controls for setting the debugging verbosity
70
+ #
71
+ # * logger = Ruby-logger compatibe logger
72
+ # * debug = true/false - Enable more verbose logging
73
+ #
74
+ # Specify a 'Logger' compatible logging facility in order to use the debug
75
+ # features of the gem. All the other settings increase the verbosity to varying
76
+ # degrees, and allow you to access some soap4r internals as well.
77
+ #
78
+ module Postini
79
+ VERSION = "0.1.0"
80
+
81
+ autoload :ConfigurationCheck, "postini/configuration_check"
82
+ autoload :Endpoints, "postini/endpoints"
83
+ autoload :EndpointResolverService, "postini/endpoint_resolver_service"
84
+ autoload :AutomatedBatchService, "postini/automated_batch_service"
85
+
86
+ # Don't debug by default
87
+ @debug = false
88
+
89
+ class << self
90
+
91
+ attr_accessor :api_key, :system_number, :username, :password, :debug, :logger
92
+
93
+ def configured? #:nodoc:
94
+ !self.api_key.nil? &&
95
+ !self.system_number.nil? &&
96
+ !self.username.nil? &&
97
+ !self.password.nil?
98
+ end
99
+
100
+ # Configure the library from yaml configuration file or
101
+ # environment variables
102
+ def configure!( yaml = nil )
103
+ data = if yaml
104
+ YAML.load_file( yaml )
105
+ else
106
+ {
107
+ 'api_key' => ENV['POSTINI_API_KEY'],
108
+ 'system_number' => ENV['POSTINI_SYSTEM_NUMBER'],
109
+ 'username' => ENV['POSTINI_USERNAME'],
110
+ 'password' => ENV['POSTINI_PASSWORD']
111
+ }
112
+ end
113
+
114
+ data.each do |k,v|
115
+ self.send( "#{k}=", v )
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,464 @@
1
+ # -*- coding: utf-8 -*-
2
+ module Postini
3
+ class AutomatedBatchService < Handsoap::Service
4
+
5
+ include ConfigurationCheck
6
+
7
+ endpoint Postini::Endpoints.automated_batch
8
+
9
+ on_create_document do |doc|
10
+ doc.alias 'aut', 'http://postini.com/PSTN/SOAPAPI/v2/automatedbatch'
11
+ end
12
+
13
+ def on_fault( fault )
14
+ exception = RemoteException.delegate( fault.reason )
15
+
16
+ # Handle specifics, translate to higher level exception
17
+
18
+ # or raise
19
+ raise exception
20
+ end
21
+
22
+ # public methods
23
+
24
+ # Associates an additional address with a user's primary email
25
+ # address. The alias receives the same filtering and shares the
26
+ # same User Ouarantine as the user’s primary email address.
27
+ #
28
+ # * confirm - If an existing user address is being overwritten to
29
+ # become an alias address, use confirm
30
+ #
31
+ # Raises a Postini::BatchError if something goes wrong
32
+ def add_alias( user_address, alias_address, confirm = true )
33
+ response = invoke("aut:addalias") do |message|
34
+ build_auth!( message )
35
+ message.add('userAddressOrId', user_address)
36
+ message.add('aliasAddress', alias_address)
37
+ message.add('confirm', 'confirm') if confirm
38
+ end
39
+
40
+ true
41
+ end
42
+ requires_configured :add_alias
43
+
44
+ # Adds the domain record +name+ to the organization
45
+ # +orgid+. Enclose +orgid+ in double quotes or preceded with a
46
+ # '\' symbol if it contains a quote ('), double quote ("),
47
+ # backslash (\), apostrophe, commas, #, = symbols.
48
+ #
49
+ # Raises a Postini::BatchException if something goes wrong
50
+ def add_domain( orgid, name )
51
+ response = invoke( "aut:adddomain" ) do |message|
52
+ build_auth!( message )
53
+ message.add('orgNameOrId', orgid )
54
+ message.add('args') do |args|
55
+ args.add('domain', name)
56
+ end
57
+ end
58
+
59
+ true
60
+ end
61
+ requires_configured :add_domain
62
+
63
+ # Adds a user (+address+) to an organization (+org+). Enclose
64
+ # +orgid+ in double quotes or preceded with a
65
+ # '\' symbol if it contains a quote ('), double quote ("),
66
+ # backslash (\), apostrophe, commas, #, = symbols.
67
+ #
68
+ # Raises a Postini::BatchException if something foes wrong
69
+ def add_user( orgid, address, welcome = false )
70
+ response = invoke("aut:adduser") do |message|
71
+ build_auth!( message )
72
+ message.add('userAddress', address)
73
+ message.add('args') do |args|
74
+ args.add('org', orgid)
75
+ args.add('welcome', welcome)
76
+ end
77
+ end
78
+
79
+ true
80
+ end
81
+ requires_configured :add_user
82
+
83
+ # This operation is an Service Management API’s AutomatedBatch
84
+ # utility. It checks the user’s email, API license key, and
85
+ # password authentication tokens used for connection and
86
+ # authorization validation between the web service and the client.
87
+ #
88
+ # * email - The email address entered by the user.
89
+ # * apiKey - A unique customer and product ID.
90
+ # * password -- The PMP password entered by the user.
91
+ #
92
+ # If the organization is configured for POP authorization, always
93
+ # use the administrator's email login and password which will be
94
+ # in the PMP format. An end user's POP password will fail.
95
+ def check_auth( email = nil, password = nil, api_key = nil )
96
+ response = invoke("aut:checkauth") do |message|
97
+ build_auth!( message, email, password, api_key )
98
+ end
99
+
100
+ return true
101
+ end
102
+ requires_configured :check_auth
103
+
104
+ # Removes the specified alias completely from the email security
105
+ # service.
106
+ def delete_alias( alias_address )
107
+ response = invoke("aut:deletealias") do |message|
108
+ build_auth!( message )
109
+ message.add('aliasAddress', alias_address)
110
+ end
111
+
112
+ return true
113
+ end
114
+ requires_configured :delete_alias
115
+
116
+ # Removes the domain from the organizational hierarchy. All the
117
+ # users for the domain needs to be removed first by #delete_user.
118
+ #
119
+ # Raises a Postini::BatchException if something goes wrong
120
+ def delete_domain( name )
121
+ response = invoke("aut:deletedomain") do |message|
122
+ build_auth!( message )
123
+ message.add('domainNameOrId', name)
124
+ end
125
+
126
+ true
127
+ end
128
+ requires_configured :delete_domain
129
+
130
+ # Delete a user record from the email security service.
131
+ #
132
+ # Raises a Postini::BatchExceptio if something goes wrong
133
+ def delete_user( address )
134
+ response = invoke("aut:deleteuser") do |message|
135
+ build_auth!( message )
136
+ message.add('userAddressOrId', address)
137
+ end
138
+
139
+ true
140
+ end
141
+ requires_configured :delete_user
142
+
143
+ # Retrieve the details of the domain from the email security
144
+ # service as a hash with the following keys:
145
+ #
146
+ # :org => string - Name of the parent org
147
+ # :substrip => true/false - Whether sub domain stripping in enabled
148
+ # :aliased_to => nil/string - Alias of another domain
149
+ # :aliased_as => array - List of domain aliases
150
+ # :id => integer - Domain id
151
+ # :name => string - Domain name
152
+ def display_domain( name )
153
+ response = invoke("aut:displaydomain") do |message|
154
+ build_auth!( message )
155
+ message.add('domainNameOrId', name )
156
+ end
157
+
158
+ parse_display_domain_results( response.document.xpath('//tns:displaydomainResponse', tns).first )
159
+ end
160
+ requires_configured :display_domain
161
+
162
+ # Retrieve the details of the user from the email securty service
163
+ # as a hash with following keys:
164
+ #
165
+ # :active => bool
166
+ # :address => string
167
+ # :approved_recipients => array
168
+ # :approved_senders => array
169
+ # :blocked_senders => array
170
+ # :create_method => integer
171
+ # :created_date => Time
172
+ # :filter_adult => string
173
+ # :filter_bulk => string
174
+ # :filter_getrich => string
175
+ # :filter_offers => string
176
+ # :filter_racial => string
177
+ # :initial_password => string
178
+ # :junkmail_filter => bool
179
+ # :lang_locale => string
180
+ # :lastmod_date => Time
181
+ # :message_encryption => string
182
+ # :message_limit => string
183
+ # :message_limited => bool
184
+ # :message_count => integer
185
+ # :notice_address => string
186
+ # :org => string
187
+ # :password => string
188
+ # :timezone => string
189
+ # :id => integer
190
+ # :virus_notify => string
191
+ # :virus_state => bool
192
+ # :weblocked => bool
193
+ # :welcome_count => integer
194
+ # :wireless_state => string
195
+ def display_user( address )
196
+ response = invoke("aut:displayuser") do |message|
197
+ build_auth!( message )
198
+ message.add('userAddressOrId', address)
199
+ end
200
+
201
+ parse_display_user_results( response.document.xpath('//tns:displayuserResponse', tns).first )
202
+ end
203
+ requires_configured :display_user
204
+
205
+ # Modify a domain by moving it to a new organization, setting or
206
+ # removing aliases, or enabling/disabling subdomain
207
+ # stripping. Changes are passed as a hash with the following keys:
208
+ #
209
+ # :org => string - New organization to move domain to
210
+ # :substrip => bool - New new value for substrip
211
+ # :aliases => array - Add new domain aliases to the array, to
212
+ # remove an existing domain alias prepend the name
213
+ # with a hyphen (-)
214
+ def modify_domain( name, changes = {} )
215
+ valid_changes = {}
216
+ valid_changes['neworg'] = changes[:org] if changes.has_key?(:org)
217
+ if changes.has_key?(:substrip)
218
+ valid_changes['substrip'] = ( changes[:substrip] == true ? 'yes' : 'no' )
219
+ end
220
+ valid_changes['alias'] = changes[:aliases].to_a.join(', ') if changes.has_key?(:aliases)
221
+
222
+ response = invoke("aut:modifydomain") do |message|
223
+ build_auth!( message )
224
+ message.add('domainNameOrId', name)
225
+ message.add('domainModifications') do |mods|
226
+ valid_changes.each do |k,v|
227
+ mods.add( k, v )
228
+ end
229
+ end
230
+ end
231
+
232
+ display_domain( name )
233
+ end
234
+ requires_configured :modify_domain
235
+
236
+ # Modify a user extensively by providing any of the following keys
237
+ # in the changes hash
238
+ #
239
+ # :active => bool
240
+ # :address => string (change the email address)
241
+ # :approved_recipients => array (full or changes)
242
+ # :approved_senders => array (full or changes)
243
+ # :blocked_senders => array (full or changes)
244
+ # :filter_adult => string
245
+ # :filter_bulk => string
246
+ # :filter_getrich => string
247
+ # :filter_offers => string
248
+ # :filter_racial => string
249
+ # :initial_password => string
250
+ # :junkemail_filter => bool
251
+ # :lang_locale => string
252
+ # :message_limit => integer
253
+ # :message_limited => bool
254
+ # :notice_address => string
255
+ # :org => string
256
+ # :password => string
257
+ # :virus_notify => bool
258
+ # :virus_state => bool
259
+ # :weblocked => bool
260
+ # :wireless_state => string
261
+ #
262
+ # Notes on array parameters: Provide the full set of values, or an
263
+ # array of diffs (new entries prepended with a plus (+) and
264
+ # entries to be removed prepended with a hyphen (-).
265
+ #
266
+ # Notes on *all* parameters: I don't use all of these, nor will I
267
+ # ever, so please test them well and report any bugs to me.
268
+ def modify_user( address, changes = {} )
269
+ valid_changes = {}
270
+ valid_changes[:orgid] = changes[:org] if changes.has_key?(:org)
271
+
272
+ # Vanilla string copies
273
+ [ :active, :address, :filter_adult, :filter_bulk, :filter_getrich,
274
+ :filter_offers, :filter_racial, :initial_password, :lang_locale,
275
+ :notice_address, :password, :wireless_state, :message_limit
276
+ ].each do |k|
277
+
278
+ valid_changes[k] = changes[k] if changes.has_key?(k)
279
+ end
280
+
281
+ # Booleans need some special handling
282
+ [ :active, :junkemail_filter, :message_limited, :virus_notify,
283
+ :virus_state, :weblocked
284
+ ].each do |k|
285
+
286
+ if changes.has_key?(k)
287
+ valid_changes[k] = ( changes[k] == true ? 'yes' : 'no' )
288
+ end
289
+ end
290
+
291
+ # Join the arrays
292
+ [ :approved_senders, :approved_recipients, :blocked_senders ].each do |k|
293
+
294
+ if changes.has_key?( k )
295
+ valid_changes[k] = changes[k].join(', ')
296
+ end
297
+ end
298
+
299
+ # pray
300
+ response = invoke("aut:modifyuser") do |message|
301
+ build_auth!( message )
302
+ message.add('userAddressOrId', address)
303
+ message.add('userModifications') do |mods|
304
+ valid_changes.each do |k,v|
305
+ mods.add( k.to_s, v )
306
+ end
307
+ end
308
+ end
309
+
310
+ address = valid_changes[:address] || address
311
+
312
+ display_user( address )
313
+ end
314
+ requires_configured :modify_user
315
+
316
+ # Determines if connections to the web service and the web service
317
+ # client are not blocked. It is a simple round trip test.
318
+ #
319
+ # * true - The connection is successful. If it fails, the
320
+ # development tool will throw either an error, or warning
321
+ # depending upon the type of failure.
322
+ # * false - To test the exception handling between the web service
323
+ # and the application, use test<false> which will
324
+ # complete the roundtrip between the servers and return
325
+ # a StatusException.
326
+ #
327
+ # The Endpoint Resolver web service is not used with the test command.
328
+ def test( pass = true )
329
+ response = invoke("aut:test") do |message|
330
+ message.add('should_work', pass)
331
+ end
332
+
333
+ parse_test_results( response.document.xpath('//tns:testResponse', tns).first )
334
+ end
335
+ requires_configured :test
336
+
337
+ private
338
+
339
+ def tns
340
+ { 'tns' => 'http://postini.com/PSTN/SOAPAPI/v2/automatedbatch' }
341
+ end
342
+
343
+ def build_auth!( message, email = nil, password = nil, api_key = nil )
344
+ message.add('authElem') do |auth|
345
+ auth.add('apiKey', api_key || Postini.api_key)
346
+ auth.add('email', email || Postini.username )
347
+ auth.add('pword', password || Postini.password )
348
+ end
349
+ end
350
+
351
+ def to_array( string )
352
+ array = string.split(/[,\s+]/)
353
+ array.delete_if { |e| e == 'empty' }
354
+ array
355
+ end
356
+
357
+ # helpers
358
+
359
+ def parse_test_results( node )
360
+ node.xpath('./confirmation_message/text()').to_s
361
+ end
362
+
363
+ def parse_display_domain_results( node )
364
+ # <tns:displaydomainResponse>
365
+ # <domainRecord>
366
+ # <aliasedto>postini4r1.co.za</aliasedto>
367
+ # <aliasedfrom>postini4rone.co.za</aliasedfrom>
368
+ # <domainid>10000</domainid>
369
+ # <domainname>postini4r1.co.za</domainname>
370
+ # <org>postini-0.stage.example.org</org>
371
+ # <substrip>0</substrip>
372
+ # </domainRecord>
373
+ # </tns:displaydomainResponse>
374
+
375
+ data = {
376
+ :id => node.xpath('./domainRecord/domainid/text()').to_s.to_i,
377
+ :name => node.xpath('./domainRecord/domainname/text()').to_s,
378
+ :org => node.xpath('./domainRecord/org/text()').to_s,
379
+ :substrip => node.xpath('./domainRecord/substrip/text()').to_s == '1'
380
+ }
381
+
382
+ unless node.xpath('./domainRecord/aliasedto/text()').empty?
383
+ data[:aliased_to] = node.xpath('./domainRecord/aliasedto/text()').to_s
384
+ end
385
+
386
+ unless node.xpath('./domainRecord/aliasedfrom/text()').empty?
387
+ data[:aliased_from] = to_array( node.xpath('./domainRecord/aliasedfrom/text()').to_s )
388
+ end
389
+
390
+ data
391
+ end
392
+
393
+ def parse_display_user_results( node )
394
+ # <tns:displayuserResponse>
395
+ # <userRecord>
396
+ # <active>yes</active>
397
+ # <address>info@example.com</address>
398
+ # <approved_recipients>empty</approved_recipients>
399
+ # <approved_senders>empty</approved_senders>
400
+ # <blocked_senders>empty</blocked_senders>
401
+ # <create_method>3</create_method>
402
+ # <created_date>123456789</created_date>
403
+ # <filter_adult>moderately aggressive</filter_adult>
404
+ # <filter_bulk>leniently aggressive</filter_bulk>
405
+ # <filter_getrich>moderately aggressive</filter_getrich>
406
+ # <filter_offers>moderately aggressive</filter_offers>
407
+ # <filter_racial>moderately aggressive</filter_racial>
408
+ # <initial_password/>
409
+ # <junkmail_filter>on</junkmail_filter>
410
+ # <lang_locale/>
411
+ # <lastmod_date>123456789</lastmod_date>
412
+ # <message_count>0</message_count>
413
+ # <message_limit/>
414
+ # <message_limited>no</message_limited>
415
+ # <notice_address/>
416
+ # <orgid>Example Org Users</orgid>
417
+ # <password>randomhashrepresentation</password>
418
+ # <timezone>Europe/Lisbon</timezone>
419
+ # <user_id>1</user_id>
420
+ # <virus_notify>Organization default</virus_notify>
421
+ # <virus_state>on</virus_state>
422
+ # <weblocked>no</weblocked>
423
+ # <welcome_count>1</welcome_count>
424
+ # <wireless_state>unavailable</wireless_state>
425
+ # </userRecord>
426
+ # </tns:displayuserResponse>
427
+
428
+ data = {
429
+ :active => node.xpath('./userRecord/active/text()').to_s == 'yes',
430
+ :address => node.xpath('./userRecord/address/text()').to_s,
431
+ :approved_recipients => to_array( node.xpath('./userRecord/approved_recipients/text()').to_s ),
432
+ :approved_senders => to_array( node.xpath('./userRecord/approved_senders/text()').to_s ),
433
+ :blocked_senders => to_array( node.xpath('./userRecord/blocked_senders/text()').to_s ),
434
+ :create_method => node.xpath('./userRecord/create_method/text()').to_s.to_i,
435
+ :created_date => Time.at( node.xpath('./userRecord/created_date/text()').to_s.to_i ),
436
+ :filter_adult => node.xpath('./userRecord/filter_adult/text()').to_s,
437
+ :filter_bulk => node.xpath('./userRecord/filter_bulk/text()').to_s,
438
+ :filter_getrich => node.xpath('./userRecord/filter_getrich/text()').to_s,
439
+ :filter_offers => node.xpath('./userRecord/filter_offers/text()').to_s,
440
+ :filter_racial => node.xpath('./userRecord/filter_racial/text()').to_s,
441
+ :initial_password => node.xpath('./userRecord/initial_password/text()').to_s,
442
+ :junkmail_filter => node.xpath('./userRecord/junkmail_filter/text()').to_s == 'on',
443
+ :lang_locale => node.xpath('./userRecord/lang_local/text()').to_s,
444
+ :lastmod_date => Time.at( node.xpath('./userRecord/lastmod_date/text()').to_s.to_i ),
445
+ :message_encryption => node.xpath('./userRecord/message_encryption/text()').to_s,
446
+ :message_limit => node.xpath('./userRecord/message_limit/text()').to_s,
447
+ :message_limited => node.xpath('./userRecord/message_limited/text()').to_s == 'yes',
448
+ :message_count => node.xpath('./userRecord/message_count/text()').to_s.to_i,
449
+ :notice_address => node.xpath('./userRecord/notice_address/text()').to_s,
450
+ :org => node.xpath('./userRecord/orgid/text()').to_s,
451
+ :password => node.xpath('./userRecord/password/text()').to_s,
452
+ :timezone => node.xpath('./userRecord/timezone/text()').to_s,
453
+ :id => node.xpath('./userRecord/user_id/text()').to_s,
454
+ :virus_notify => node.xpath('./userRecord/virus_notify/text()').to_s,
455
+ :virus_state => node.xpath('./userRecord/virus_state/text()').to_s,
456
+ :weblocked => node.xpath('./userRecord/weblocked/text()').to_s == 'yes',
457
+ :welcome_count => node.xpath('./userRecord/welcome_count/text()').to_s.to_i,
458
+ :wireless_state => node.xpath('./userRecord/wireless_state/text()').to_s
459
+ }
460
+
461
+ data
462
+ end
463
+ end
464
+ end