registrar-client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. data/LICENSE +19 -0
  2. data/Readme.md +70 -0
  3. data/Spec.md +55 -0
  4. data/examples/enom.example.yml +4 -0
  5. data/examples/purchase_com.rb +7 -0
  6. data/lib/registrar-client.rb +1 -0
  7. data/lib/registrar.rb +14 -0
  8. data/lib/registrar/client.rb +224 -0
  9. data/lib/registrar/contact.rb +31 -0
  10. data/lib/registrar/domain.rb +26 -0
  11. data/lib/registrar/extended_attribute.rb +12 -0
  12. data/lib/registrar/extended_attribute_descriptor.rb +31 -0
  13. data/lib/registrar/extended_attribute_option_descriptor.rb +7 -0
  14. data/lib/registrar/name_server.rb +16 -0
  15. data/lib/registrar/order.rb +43 -0
  16. data/lib/registrar/provider/enom.rb +430 -0
  17. data/lib/registrar/provider/enom/contact.rb +68 -0
  18. data/lib/registrar/provider/enom/extended_attribute.rb +64 -0
  19. data/lib/registrar/provider/enom/extended_attribute_be.rb +0 -0
  20. data/lib/registrar/provider/enom/extended_attribute_ca.rb +40 -0
  21. data/lib/registrar/provider/enom/extended_attribute_io.rb +18 -0
  22. data/lib/registrar/provider/enom/extended_attribute_us.rb +29 -0
  23. data/lib/registrar/provider/enom/order.rb +41 -0
  24. data/lib/registrar/provider/opensrs.rb +133 -0
  25. data/lib/registrar/provider/opensrs/contact.rb +30 -0
  26. data/lib/registrar/provider/opensrs/contact_set.rb +24 -0
  27. data/lib/registrar/provider/opensrs/name_server_list.rb +26 -0
  28. data/lib/registrar/provider/opensrs/operation.rb +42 -0
  29. data/lib/registrar/provider/opensrs/order.rb +59 -0
  30. data/lib/registrar/provider/opensrs/tld_data.rb +29 -0
  31. data/lib/registrar/provider/opensrs/tld_data_us.rb +48 -0
  32. data/lib/registrar/purchase_options.rb +29 -0
  33. data/lib/registrar/renewal_options.rb +8 -0
  34. metadata +177 -0
@@ -0,0 +1,12 @@
1
+ module Registrar
2
+ class ExtendedAttribute
3
+ attr_accessor :tld
4
+ attr_accessor :name
5
+ attr_accessor :value
6
+ def initialize(tld, name, value)
7
+ @tld = tld
8
+ @name = name
9
+ @value = value
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,31 @@
1
+ module Registrar
2
+ class ExtendedAttributeDescriptor
3
+ attr_accessor :name
4
+ attr_accessor :description
5
+ attr_accessor :required
6
+ attr_accessor :child
7
+ attr_accessor :application
8
+ attr_accessor :user_defined
9
+ attr_accessor :options
10
+ attr_accessor :apply_to_registrar
11
+
12
+ alias :required? :required
13
+ alias :child? :child
14
+ alias :user_defined? :user_defined
15
+ alias :apply_to_registrar? :apply_to_registrar
16
+
17
+ def initialize
18
+ @options = []
19
+ end
20
+
21
+ def serializable_hash
22
+ {
23
+ 'name' => name,
24
+ 'description' => description,
25
+ 'required' => required,
26
+ 'options' => options
27
+ }
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,7 @@
1
+ module Registrar
2
+ class ExtendedAttributeOptionDescriptor
3
+ attr_accessor :title
4
+ attr_accessor :value
5
+ attr_accessor :description
6
+ end
7
+ end
@@ -0,0 +1,16 @@
1
+ module Registrar
2
+ class NameServer
3
+ include Comparable
4
+
5
+ attr_reader :name
6
+ attr_accessor :ip_address
7
+
8
+ def initialize(name)
9
+ @name = name
10
+ end
11
+
12
+ def <=> other
13
+ self.name <=> other.name
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,43 @@
1
+ module Registrar #:nodoc:
2
+ # Instances of this class contain details about the current state of a
3
+ # particular order with the registrar.
4
+ class Order
5
+ # The service-specific identifier for the order.
6
+ attr_reader :identifier
7
+
8
+ # The current status of the order
9
+ attr_accessor :status
10
+
11
+ # The date the order was created
12
+ attr_accessor :date
13
+
14
+ # Construct a new Order instance.
15
+ def initialize(identifier)
16
+ @identifier = identifier
17
+ @status = :unknown
18
+ @successful = false
19
+ end
20
+
21
+ # Get the domains associated with this order
22
+ def domains
23
+ @domains ||= []
24
+ end
25
+
26
+ # Return true if the order is complete.
27
+ def complete?
28
+ ![:open, :unknown].include?(@status)
29
+ end
30
+
31
+ def open?
32
+ @status == :open
33
+ end
34
+
35
+ def successful?
36
+ @successful
37
+ end
38
+
39
+ def successful=(successful)
40
+ @successful = successful
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,430 @@
1
+ require 'httparty'
2
+ require 'tzinfo'
3
+
4
+ require 'registrar/provider/enom/contact'
5
+ require 'registrar/provider/enom/extended_attribute'
6
+ require 'registrar/provider/enom/order'
7
+
8
+ module Registrar
9
+ module Provider
10
+ # Implementation of a registrar provider for Enom (http://www.enom.com/).
11
+ class Enom
12
+ include HTTParty
13
+
14
+ attr_accessor :url, :username, :password
15
+
16
+ def initialize(url, username, password)
17
+ @url = url
18
+ @username = username
19
+ @password = password
20
+ end
21
+
22
+ def parse(name)
23
+ query = base_query.merge('Command' => 'ParseDomain')
24
+ response = execute(query.merge('PassedDomain' => name))
25
+
26
+ [response['ParseDomain']['SLD'], response['ParseDomain']['TLD']]
27
+ end
28
+
29
+ def available?(name)
30
+ sld, tld = parse(name)
31
+
32
+ query = base_query.merge('Command' => 'Check')
33
+ response = execute(query.merge('SLD' => sld, 'TLD' => tld))
34
+
35
+ response['RRPCode'] == '210'
36
+ end
37
+
38
+ def find(name)
39
+ sld, tld = parse(name)
40
+ query = base_query.merge('Command' => 'GetDomainInfo')
41
+
42
+ response = execute(query.merge('SLD' => sld, 'TLD' => tld))
43
+
44
+ domain = Registrar::Domain.new(name)
45
+ domain.expiration = response['GetDomainInfo']['status']['expiration']
46
+ domain.registration_status = response['GetDomainInfo']['status']['registrationstatus']
47
+ domain.order = order_for_domain(name)
48
+ domain
49
+ end
50
+
51
+ def purchase(name, registrant, purchase_options=nil)
52
+ purchase_options ||= Registrar::PurchaseOptions.new
53
+
54
+ sld, tld = parse(name)
55
+ query = base_query.merge('Command' => 'Purchase', 'SLD' => sld, 'TLD' => tld)
56
+ registrant = Enom::Contact.new(registrant)
57
+
58
+ if registrant
59
+ query.merge!(registrant.to_query("Registrant"))
60
+ query.merge!(registrant.to_query("AuxBilling"))
61
+ query.merge!(registrant.to_query("Tech"))
62
+ query.merge!(registrant.to_query("Admin"))
63
+ end
64
+
65
+ if purchase_options.has_name_servers?
66
+ query['IgnoreNSFail'] = 'Yes'
67
+ purchase_options.name_servers.each_with_index do |name_server, i|
68
+ case name_server
69
+ when String
70
+ query["NS#{i+1}"] = name_server
71
+ else
72
+ query["NS#{i+1}"] = name_server.name
73
+ end
74
+
75
+ end
76
+ else
77
+ query['UseDNS'] = 'default'
78
+ end
79
+
80
+ if purchase_options.has_extended_attributes?
81
+ extended_attributes = purchase_options.extended_attributes.map { |a| Enom::ExtendedAttribute.new(a) }
82
+ extended_attributes.each do |extended_attribute|
83
+ query[extended_attribute.name] = extended_attribute.value
84
+ end
85
+ end
86
+
87
+ query['NumYears'] = purchase_options.number_of_years || minimum_number_of_years(tld)
88
+ query['IDNCode'] = purchase_options.language if purchase_options.language
89
+
90
+ response = execute(query)
91
+
92
+ registrant.identifier = response['RegistrantPartyID']
93
+
94
+ domain = Registrar::Domain.new(name)
95
+ domain.registrant = registrant
96
+ domain.lockable = response['IsLockable'].downcase == 'true'
97
+ domain.real_time = response['IsRealTimeTLD'].downcase == 'true'
98
+ order = order(response['OrderID'])
99
+ order.domains << domain
100
+ domain.order = order
101
+ order
102
+ end
103
+
104
+ def renew(name, renewal_options=nil)
105
+ renewal_options ||= Registrar::RenewalOptions.new
106
+ sld, tld = parse(name)
107
+ query = base_query.merge('Command' => 'Extend', 'SLD' => sld, 'TLD' => tld)
108
+ query = query.merge('NumYears' => renewal_options.number_of_years)
109
+ response = execute(query)
110
+ response['Extension'] && response['Extension'].downcase == 'successful'
111
+ end
112
+
113
+ def auto_renew?(name)
114
+ sld, tld = parse(name)
115
+ query = base_query.merge('Command' => 'GetRenew', 'TLD' => tld, 'SLD' => sld)
116
+ response = execute_command(query)
117
+ response['auto_renew'] == '1'
118
+ end
119
+
120
+ def enable_auto_renewal(name)
121
+ sld, tld = parse(name)
122
+ query = base_query.merge('Command' => 'SetRenew', 'TLD' => tld, 'SLD' => sld)
123
+ query = query.merge('RenewFlag' => '1')
124
+ response = execute_command(query)
125
+ response['RenewName'] == 'True'
126
+ end
127
+
128
+ def disable_auto_renewal(name)
129
+ sld, tld = parse(name)
130
+ query = base_query.merge('Command' => 'SetRenew', 'TLD' => tld, 'SLD' => sld)
131
+ query = query.merge('RenewFlag' => '0')
132
+ response = execute_command(query)
133
+ response['RenewName'] == 'False'
134
+ end
135
+
136
+ def order(id)
137
+ query = base_query.merge('Command' => 'GetOrderDetail', 'OrderID' => id.to_s)
138
+ response = execute(query)
139
+
140
+ order = Enom::Order.new(response['Order']['OrderID'])
141
+ order.order_date = response['Order']['OrderDate']
142
+ order.order_status = response['Order']['OrderDetail']['OrderStatus']
143
+ order.status = response['Order']['OrderDetail']['Status']
144
+ order.to_order
145
+ end
146
+
147
+ def order_for_domain(name)
148
+ sld, tld = parse(name)
149
+ query = base_query.merge('Command' => 'GetDomainStatus', 'SLD' => sld, 'TLD' => tld)
150
+ response = execute_command(query)
151
+ order(response['DomainStatus']['OrderID'])
152
+ end
153
+
154
+ def name_servers(name)
155
+ sld, tld = parse(name)
156
+ query = base_query.merge('Command' => 'GetDNS', 'TLD' => tld, 'SLD' => sld)
157
+ response = execute_command(query)
158
+ [response['dns']].flatten
159
+ end
160
+ alias :nameservers :name_servers
161
+
162
+ def set_name_servers(name, name_servers=[])
163
+ sld, tld = parse(name)
164
+ query = base_query.merge('Command' => 'ModifyNS', 'TLD' => tld, 'SLD' => sld)
165
+
166
+ name_server_hash = {}
167
+ if name_servers.length == 0
168
+ name_server_hash["NS1"] = ""
169
+ else
170
+ name_servers.each_with_index do |ns_name, index|
171
+ name_server_hash["NS#{index + 1}"] = ns_name
172
+ end
173
+ end
174
+ query = query.merge(name_server_hash)
175
+
176
+ response = execute_command(query)
177
+
178
+ name_servers
179
+ end
180
+
181
+ def find_name_server(name)
182
+ query = base_query.merge('Command' => 'CheckNSStatus', 'CheckNSName' => name)
183
+ response = execute_command(query)
184
+
185
+ if response['NsCheckSuccess'] == '1'
186
+ name_server = Registrar::NameServer.new(response['CheckNsStatus']['name'])
187
+ name_server.ip_address = response['CheckNsStatus']['ipaddress']
188
+ name_server
189
+ else
190
+ raise RuntimeError, "Name server not found for #{name}"
191
+ end
192
+ end
193
+
194
+ def register_name_server(name_server)
195
+ query = base_query.merge('Command' => 'RegisterNameServer', 'Add' => 'true', 'NSName' => name_server.name, 'IP' => name_server.ip_address)
196
+ response = execute_command(query)
197
+
198
+ if response['RRPCode'] == '200'
199
+ name_server = Registrar::NameServer.new(response['RegisterNameserver']['NS'])
200
+ name_server.ip_address = response['RegisterNameserver']['IP']
201
+ name_server
202
+ else
203
+ raise RuntimeError, "Unable to create name server: #{response['RRPText']}"
204
+ end
205
+ end
206
+
207
+ def extended_attributes(name)
208
+ sld, tld = parse(name)
209
+ query = base_query.merge('Command' => 'GetExtAttributes', 'TLD' => tld)
210
+ response = execute(query)
211
+ return nil unless response['Attributes']
212
+ [response['Attributes']['Attribute']].flatten.map do |enom_attribute|
213
+ extended_attribute = Registrar::ExtendedAttributeDescriptor.new
214
+ extended_attribute.name = enom_attribute['Name']
215
+ extended_attribute.description = enom_attribute['Description']
216
+ extended_attribute.child = enom_attribute['IsChild'] == '1'
217
+ extended_attribute.required = enom_attribute['Required'] == '1'
218
+ extended_attribute.application = enom_attribute['Application']
219
+ extended_attribute.user_defined = enom_attribute['UserDefined'] == 'True'
220
+ extended_attribute.apply_to_registrar = enom_attribute['Application'] == '2'
221
+
222
+ if enom_attribute['Options']
223
+ extended_attribute.options = [enom_attribute['Options']['Option']].flatten.map do |enom_option|
224
+ option = Registrar::ExtendedAttributeOptionDescriptor.new
225
+ option.title = enom_option['Title']
226
+ option.value = enom_option['Value']
227
+ option.description = enom_option['Description']
228
+ option
229
+ end
230
+ end
231
+
232
+ extended_attribute
233
+ end
234
+ end
235
+
236
+ def minimum_number_of_years(tld)
237
+ {
238
+ 'co.uk' => 2,
239
+ 'org.uk' => 2,
240
+ 'nu' => 2,
241
+ 'tm' => 10,
242
+ 'com.mx' => 2,
243
+ 'me.uk' => 2
244
+ }[tld] || 1
245
+ end
246
+
247
+ def tld_retail_transfer_price(tld)
248
+ Enom::PricingEngine.tld_retail_transfer_price(tld)
249
+ end
250
+
251
+ # Get a Hash of all of the contacts for the domain. The Hash will have the following
252
+ # key/value pairs:
253
+ #
254
+ # * :registrant => The domain registrant
255
+ # * :aux_billing => An customer specified billing contact
256
+ # * :tech => The technical contact for the domain
257
+ # * :admin => The administrative contact for the domain
258
+ # * :billing => The Enom billing contact (DNSimple)
259
+ def contacts(domain)
260
+ sld, tld = parse(domain.name)
261
+
262
+ query = base_query.merge('Command' => 'GetContacts', 'TLD' => tld, 'SLD' => sld)
263
+
264
+ response = execute(query)
265
+
266
+ contacts = {}
267
+ registrant_hash = response['GetContacts']['Registrant']
268
+ contacts[:registrant] = Enom::Contact.from_response('Registrant', registrant_hash)
269
+ aux_billing_hash = response['GetContacts']['AuxBilling']
270
+ contacts[:aux_billing] = Enom::Contact.from_response('AuxBilling', aux_billing_hash)
271
+
272
+ tech_hash = response['GetContacts']['Tech']
273
+ contacts[:tech] = Enom::Contact.from_response('Tech', tech_hash)
274
+
275
+ admin_hash = response['GetContacts']['Admin']
276
+ contacts[:admin] = Enom::Contact.from_response('Admin', admin_hash)
277
+
278
+ billing_hash = response['GetContacts']['Billing']
279
+ contacts[:billing] = Enom::Contact.from_response('Billing', billing_hash)
280
+
281
+ contacts
282
+ end
283
+
284
+ # Update the registrant information for a domain. For some TLDs this will include
285
+ # providing extended attributes. If the TLD does not require extended attributes
286
+ # then send nil or an empty Hash for the extended_attributes argument.
287
+ def update_registrant(domain, registrant, extended_attributes=nil)
288
+ registrant = Enom::Contact.new(registrant)
289
+
290
+ sld, tld = parse(domain.name)
291
+ query = base_query.merge(
292
+ 'Command' => 'Contacts',
293
+ 'TLD' => tld,
294
+ 'SLD' => sld
295
+ )
296
+
297
+ query = query.merge('ContactType' => 'Registrant')
298
+ query = query.merge(registrant.to_query('Registrant'))
299
+
300
+ if extended_attributes
301
+ extended_attributes.each do |name, value|
302
+ query[name] = value
303
+ end
304
+ end
305
+
306
+ response = execute_command(query) # TODO: should something else be returned here?
307
+ end
308
+
309
+ # Update the tech, aux billing and administrative contacts for the domain. Right
310
+ # now the same contact must be used for all of these contact types.
311
+ def update_contacts(domain, contact)
312
+ contact = Enom::Contact.new(contact)
313
+
314
+ sld, tld = parse(domain.name)
315
+ query = base_query.merge(
316
+ 'Command' => 'Contacts',
317
+ 'TLD' => tld,
318
+ 'SLD' => sld
319
+ )
320
+
321
+ tech_contact_query = query.merge('ContactType' => 'Tech')
322
+ tech_contact_query = tech_contact_query.merge(contact.to_query('Tech'))
323
+ response = execute_command(tech_contact_query)
324
+
325
+ admin_contact_query = query.merge('ContactType' => 'Admin')
326
+ admin_contact_query = admin_contact_query.merge(contact.to_query('Admin'))
327
+ response = execute_command(admin_contact_query)
328
+
329
+ billing_contact_query = query.merge('ContactType' => 'AuxBilling')
330
+ billing_contact_query = billing_contact_query.merge(contact.to_query('AuxBilling'))
331
+ response = execute(billing_contact_query)
332
+
333
+ contacts(domain)
334
+ end
335
+
336
+ # Update the tech contact for the domain.
337
+ def update_technical_contact(domain, contact)
338
+ contact = Enom::Contact.new(contact)
339
+
340
+ sld, tld = parse(domain.name)
341
+ query = base_query.merge(
342
+ 'Command' => 'Contacts',
343
+ 'TLD' => tld,
344
+ 'SLD' => sld
345
+ )
346
+
347
+ query = query.merge('ContactType' => 'Tech')
348
+ query = query.merge(contact.to_query('Tech'))
349
+ response = execute(query)
350
+ contacts(domain)[:tech]
351
+ end
352
+
353
+ # Update the admin contact for the domain.
354
+ def update_administrative_contact(domain, contact)
355
+ contact = Enom::Contact.new(contact)
356
+
357
+ sld, tld = parse(domain.name)
358
+ query = base_query.merge(
359
+ 'Command' => 'Contacts',
360
+ 'TLD' => tld,
361
+ 'SLD' => sld
362
+ )
363
+
364
+ query = query.merge('ContactType' => 'Admin')
365
+ query = query.merge(contact.to_query('Admin'))
366
+ response = execute(query)
367
+ contacts(domain)[:admin]
368
+ end
369
+
370
+ # Update the aux billing contact for the domain.
371
+ def update_aux_billing_contact(domain, contact)
372
+ contact = Enom::Contact.new(contact)
373
+
374
+ sld, tld = parse(domain.name)
375
+ query = base_query.merge(
376
+ 'Command' => 'Contacts',
377
+ 'TLD' => tld,
378
+ 'SLD' => sld
379
+ )
380
+
381
+ query = query.merge('ContactType' => 'AuxBilling')
382
+ query = query.merge(contact.to_query('AuxBilling'))
383
+ response = execute(query)
384
+ contacts(domain)[:aux_billing]
385
+ end
386
+
387
+ private
388
+ def execute(query)
389
+ Encoding.default_internal = Encoding.default_external = "UTF-8"
390
+ options = {:query => query, :parser => EnomParser}
391
+ response = self.class.get(url, options)['interface_response']
392
+ raise Registrar::RegistrarError.new("Response from Enom was nil") if response.nil?
393
+ raise EnomError.new(response) if response['ErrCount'] != '0'
394
+ response
395
+ end
396
+ alias :execute_command :execute
397
+
398
+ def base_query
399
+ {
400
+ 'UID' => username,
401
+ 'PW' => password,
402
+ 'ResponseType' => 'XML'
403
+ }
404
+ end
405
+
406
+ end
407
+
408
+ class EnomError < Registrar::RegistrarError
409
+ attr_reader :response
410
+ attr_reader :errors
411
+
412
+ def initialize(response)
413
+ @response = response
414
+ @errors = []
415
+
416
+ response['errors'].each do |k, err|
417
+ @errors << err
418
+ end
419
+
420
+ super response['errors'].values.join(", ")
421
+ end
422
+ end
423
+
424
+ class EnomParser < HTTParty::Parser
425
+ def body
426
+ @body.force_encoding('UTF-8')
427
+ end
428
+ end
429
+ end
430
+ end