epp-client-base 0.11.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,394 @@
1
+ module EPPClient
2
+ module Domain
3
+ EPPClient::Poll::PARSERS['domain:panData'] = :domain_pending_action_process
4
+
5
+ def domain_check_xml(*domains) # :nodoc:
6
+ command do |xml|
7
+ xml.check do
8
+ xml.check('xmlns' => EPPClient::SCHEMAS_URL['domain-1.0']) do
9
+ domains.each do |dom|
10
+ xml.name(dom)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ # Check the availability of domains
18
+ #
19
+ # takes a list of domains as arguments
20
+ #
21
+ # returns an array of hashes containing three fields :
22
+ # [<tt>:name</tt>] The domain name
23
+ # [<tt>:avail</tt>] Wether the domain is available or not.
24
+ # [<tt>:reason</tt>] The reason for non availability, if given.
25
+ def domain_check(*domains)
26
+ domains.flatten!
27
+ response = send_request(domain_check_xml(*domains))
28
+
29
+ get_result(:xml => response, :callback => :domain_check_process)
30
+ end
31
+
32
+ def domain_check_process(xml) # :nodoc:
33
+ xml.xpath('epp:resData/domain:chkData/domain:cd', EPPClient::SCHEMAS_URL).map do |dom|
34
+ ret = {
35
+ :name => dom.xpath('domain:name', EPPClient::SCHEMAS_URL).text,
36
+ :avail => dom.xpath('domain:name', EPPClient::SCHEMAS_URL).attr('avail').value == '1',
37
+ }
38
+ unless (reason = dom.xpath('domain:reason', EPPClient::SCHEMAS_URL).text).empty?
39
+ ret[:reason] = reason
40
+ end
41
+ ret
42
+ end
43
+ end
44
+
45
+ def domain_info_xml(args) # :nodoc:
46
+ command do |xml|
47
+ xml.info do
48
+ xml.info('xmlns' => EPPClient::SCHEMAS_URL['domain-1.0']) do
49
+ xml.name(args[:name])
50
+ if args.key?(:authInfo)
51
+ xml.authInfo do
52
+ if args.key?(:roid)
53
+ xml.pw({:roid => args[:roid]}, args[:authInfo])
54
+ else
55
+ xml.pw(args[:authInfo])
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ # Returns the informations about a domain
65
+ #
66
+ # Takes either a unique argument, a string, representing the domain, or a
67
+ # hash with : <tt>:name</tt> the domain name, and optionnaly
68
+ # <tt>:authInfo</tt> the authentication information and possibly
69
+ # <tt>:roid</tt> the contact the authInfo is about.
70
+ #
71
+ # Returned is a hash mapping as closely as possible the result expected
72
+ # from the command as per Section 3.1.2 of RFC 5731 :
73
+ # [<tt>:name</tt>] The fully qualified name of the domain object.
74
+ # [<tt>:roid</tt>]
75
+ # The Repository Object IDentifier assigned to the domain object when
76
+ # the object was created.
77
+ # [<tt>:status</tt>]
78
+ # an optionnal array of elements that contain the current status
79
+ # descriptors associated with the domain.
80
+ # [<tt>:registrant</tt>] one optionnal registrant nic handle.
81
+ # [<tt>:contacts</tt>]
82
+ # an optionnal hash which keys are choosen between +admin+, +billing+ and
83
+ # +tech+ and which values are arrays of nic handles for the corresponding
84
+ # contact types.
85
+ # [<tt>:ns</tt>]
86
+ # an optional array containing nameservers informations, which can either
87
+ # be an array of strings containing the the fully qualified name of a
88
+ # host, or an array of hashes containing the following keys :
89
+ # [<tt>:hostName</tt>] the fully qualified name of a host.
90
+ # [<tt>:hostAddrv4</tt>]
91
+ # an optionnal array of ipv4 addresses to be associated with the host.
92
+ # [<tt>:hostAddrv6</tt>]
93
+ # an optionnal array of ipv6 addresses to be associated with the host.
94
+ # [<tt>:host</tt>]
95
+ # an optionnal array of fully qualified names of the subordinate host
96
+ # objects that exist under this superordinate domain object.
97
+ # [<tt>:clID</tt>] the identifier of the sponsoring client.
98
+ # [<tt>:crID</tt>]
99
+ # an optional identifier of the client that created the domain object.
100
+ # [<tt>:crDate</tt>] an optional date and time of domain object creation.
101
+ # [<tt>:exDate</tt>]
102
+ # the date and time identifying the end of the domain object's
103
+ # registration period.
104
+ # [<tt>:upID</tt>]
105
+ # the identifier of the client that last updated the domain object.
106
+ # [<tt>:upDate</tt>]
107
+ # the date and time of the most recent domain-object modification.
108
+ # [<tt>:trDate</tt>]
109
+ # the date and time of the most recent successful domain-object transfer.
110
+ # [<tt>:authInfo</tt>]
111
+ # authorization information associated with the domain object.
112
+ def domain_info(args)
113
+ if String === args
114
+ args = {:name => args}
115
+ end
116
+ response = send_request(domain_info_xml(args))
117
+
118
+ get_result(:xml => response, :callback => :domain_info_process)
119
+ end
120
+
121
+ def domain_info_process(xml) # :nodoc:
122
+ dom = xml.xpath('epp:resData/domain:infData', EPPClient::SCHEMAS_URL)
123
+ ret = {
124
+ :name => dom.xpath('domain:name', EPPClient::SCHEMAS_URL).text,
125
+ :roid => dom.xpath('domain:roid', EPPClient::SCHEMAS_URL).text,
126
+ }
127
+ if (status = dom.xpath('domain:status', EPPClient::SCHEMAS_URL)).size > 0
128
+ ret[:status] = status.map {|s| s.attr('s')}
129
+ end
130
+ if (registrant = dom.xpath('domain:registrant', EPPClient::SCHEMAS_URL)).size > 0
131
+ ret[:registrant] = registrant.text
132
+ end
133
+ if (contact = dom.xpath('domain:contact', EPPClient::SCHEMAS_URL)).size > 0
134
+ ret[:contacts] = contact.inject({}) do |a,c|
135
+ s = c.attr('type').to_sym
136
+ a[s] ||= []
137
+ a[s] << c.text
138
+ a
139
+ end
140
+ end
141
+ if (ns = dom.xpath('domain:ns', EPPClient::SCHEMAS_URL)).size > 0
142
+ if (hostObj = ns.xpath('domain:hostObj', EPPClient::SCHEMAS_URL)).size > 0
143
+ ret[:ns] = hostObj.map {|h| h.text}
144
+ elsif (hostAttr = ns.xpath('domain:hostAttr', EPPClient::SCHEMAS_URL)).size > 0
145
+ ret[:ns] = hostAttr.map do |h|
146
+ r = { :hostName => h.xpath('domain:hostName', EPPClient::SCHEMAS_URL).text }
147
+ if (v4 = h.xpath('domain:hostAddr[@ip="v4"]', EPPClient::SCHEMAS_URL)).size > 0
148
+ r[:hostAddrv4] = v4.map {|v| v.text}
149
+ end
150
+ if (v6 = h.xpath('domain:hostAddr[@ip="v6"]', EPPClient::SCHEMAS_URL)).size > 0
151
+ r[:hostAddrv6] = v6.map {|v| v.text}
152
+ end
153
+ r
154
+ end
155
+ end
156
+ end
157
+ if (host = dom.xpath('domain:host', EPPClient::SCHEMAS_URL)).size > 0
158
+ ret[:host] = host.map {|h| h.text}
159
+ end
160
+ %w(clID upID).each do |val|
161
+ if (r = dom.xpath("domain:#{val}", EPPClient::SCHEMAS_URL)).size > 0
162
+ ret[val.to_sym] = r.text
163
+ end
164
+ end
165
+ %w(crDate exDate upDate trDate).each do |val|
166
+ if (r = dom.xpath("domain:#{val}", EPPClient::SCHEMAS_URL)).size > 0
167
+ ret[val.to_sym] = DateTime.parse(r.text)
168
+ end
169
+ end
170
+ if (authInfo = dom.xpath('domain:authInfo', EPPClient::SCHEMAS_URL)).size > 0
171
+ ret[:authInfo] = authInfo.xpath('domain:pw', EPPClient::SCHEMAS_URL).text
172
+ end
173
+ return ret
174
+ end
175
+
176
+ def domain_nss_xml(xml, nss)
177
+ xml.ns do
178
+ if nss.first.is_a?(Hash)
179
+ nss.each do |ns|
180
+ xml.hostAttr do
181
+ xml.hostName ns[:hostName]
182
+ if ns.key?(:hostAddrv4)
183
+ ns[:hostAddrv4].each do |v4|
184
+ xml.hostAddr({:ip => :v4}, v4)
185
+ end
186
+ end
187
+ if ns.key?(:hostAddrv6)
188
+ ns[:hostAddrv6].each do |v6|
189
+ xml.hostAddr({:ip => :v6}, v6)
190
+ end
191
+ end
192
+ end
193
+ end
194
+ else
195
+ nss.each do |ns|
196
+ xml.hostObj ns
197
+ end
198
+ end
199
+ end
200
+ end
201
+
202
+ def domain_contacts_xml(xml, args)
203
+ args.each do |type,contacts|
204
+ contacts.each do |c|
205
+ xml.contact({:type => type}, c)
206
+ end
207
+ end
208
+ end
209
+
210
+ def domain_create_xml(args) #:nodoc:
211
+ command do |xml|
212
+ xml.create do
213
+ xml.create('xmlns' => EPPClient::SCHEMAS_URL['domain-1.0']) do
214
+ xml.name args[:name]
215
+
216
+ if args.key?(:period)
217
+ xml.period({:unit => args[:period][:unit]}, args[:period][:number])
218
+ end
219
+
220
+ if args.key?(:ns)
221
+ domain_nss_xml(xml, args[:ns])
222
+ end
223
+
224
+ xml.registrant args[:registrant] if args.key?(:registrant)
225
+
226
+ if args.key?(:contacts)
227
+ domain_contacts_xml(xml, args[:contacts])
228
+ end
229
+
230
+ xml.authInfo do
231
+ xml.pw args[:authInfo]
232
+ end
233
+ end
234
+ end
235
+ end
236
+ end
237
+
238
+ # Creates a domain
239
+ #
240
+ # Takes a hash as an argument, containing the following keys :
241
+ #
242
+ # [<tt>:name</tt>] the domain name
243
+ # [<tt>:period</tt>]
244
+ # an optionnal hash containing the period for withch the domain is
245
+ # registered with the following keys :
246
+ # [<tt>:unit</tt>] the unit of time, either "m"onth or "y"ear.
247
+ # [<tt>:number</tt>] the number of unit of time.
248
+ # [<tt>:ns</tt>]
249
+ # an optional array containing nameservers informations, which can either
250
+ # be an array of strings containing the nameserver's hostname, or an
251
+ # array of hashes containing the following keys :
252
+ # [<tt>:hostName</tt>] the hostname of the nameserver.
253
+ # [<tt>:hostAddrv4</tt>] an optionnal array of ipv4 addresses.
254
+ # [<tt>:hostAddrv6</tt>] an optionnal array of ipv6 addresses.
255
+ # [<tt>:registrant</tt>] an optionnal registrant nic handle.
256
+ # [<tt>:contacts</tt>]
257
+ # an optionnal hash which keys are choosen between +admin+, +billing+ and
258
+ # +tech+ and which values are arrays of nic handles for the corresponding
259
+ # contact types.
260
+ # [<tt>:authInfo</tt>] the password associated with the domain.
261
+ #
262
+ # Returns a hash with the following keys :
263
+ #
264
+ # [<tt>:name</tt>] the fully qualified name of the domain object.
265
+ # [<tt>:crDate</tt>] the date and time of domain object creation.
266
+ # [<tt>:exDate</tt>]
267
+ # the date and time identifying the end of the domain object's
268
+ # registration period.
269
+ def domain_create(args)
270
+ response = send_request(domain_create_xml(args))
271
+
272
+ get_result(:xml => response, :callback => :domain_create_process)
273
+ end
274
+
275
+ def domain_create_process(xml) #:nodoc:
276
+ dom = xml.xpath('epp:resData/domain:creData', EPPClient::SCHEMAS_URL)
277
+ ret = {
278
+ :name => dom.xpath('domain:name', EPPClient::SCHEMAS_URL).text,
279
+ :crDate => DateTime.parse(dom.xpath('domain:crDate', EPPClient::SCHEMAS_URL).text),
280
+ :upDate => DateTime.parse(dom.xpath('domain:crDate', EPPClient::SCHEMAS_URL).text),
281
+ }
282
+ end
283
+
284
+ def domain_delete_xml(domain) #:nodoc:
285
+ command do |xml|
286
+ xml.delete do
287
+ xml.delete('xmlns' => EPPClient::SCHEMAS_URL['domain-1.0']) do
288
+ xml.name domain
289
+ end
290
+ end
291
+ end
292
+ end
293
+
294
+ # Deletes a domain
295
+ #
296
+ # Takes a single fully qualified domain name for argument.
297
+ #
298
+ # Returns true on success, or raises an exception.
299
+ def domain_delete(domain)
300
+ response = send_request(domain_delete_xml(domain))
301
+
302
+ get_result(response)
303
+ end
304
+
305
+ def domain_update_xml(args) #:nodoc:
306
+ command do |xml|
307
+ xml.update do
308
+ xml.update('xmlns' => EPPClient::SCHEMAS_URL['domain-1.0']) do
309
+ xml.name args[:name]
310
+ [:add, :rem].each do |ar|
311
+ if args.key?(ar) && (args[ar].key?(:ns) || args[ar].key?(:contacts) || args[ar].key?(:status))
312
+ xml.__send__(ar) do
313
+ if args[ar].key?(:ns)
314
+ domain_nss_xml(xml, args[ar][:ns])
315
+ end
316
+ if args[ar].key?(:contacts)
317
+ domain_contacts_xml(xml, args[ar][:contacts])
318
+ end
319
+ if args[ar].key?(:status)
320
+ args[ar][:status].each do |st,text|
321
+ if text.nil?
322
+ xml.status(:s => st)
323
+ else
324
+ xml.status({:s => st}, text)
325
+ end
326
+ end
327
+ end
328
+ end
329
+ end
330
+ end
331
+ if args.key?(:chg) && (args[:chg].key?(:registrant) || args[:chg].key?(:authInfo))
332
+ xml.chg do
333
+ if args[:chg].key?(:registrant)
334
+ xml.registrant args[:chg][:registrant]
335
+ end
336
+ if args[:chg].key?(:authInfo)
337
+ xml.authInfo do
338
+ xml.pw args[:chg][:authInfo]
339
+ end
340
+ end
341
+ end
342
+ end
343
+ end
344
+ end
345
+ end
346
+ end
347
+
348
+ # Updates a domain
349
+ #
350
+ # Takes a hash with the name, and at least one of the following keys :
351
+ # [<tt>:name</tt>]
352
+ # the fully qualified name of the domain object to be updated.
353
+ # [<tt>:add</tt>/<tt>:rem</tt>]
354
+ # adds / removes the following data to/from the domain object :
355
+ # [<tt>:ns</tt>]
356
+ # an optional array containing nameservers informations, which can either
357
+ # be an array of strings containing the nameserver's hostname, or an
358
+ # array of hashes containing the following keys :
359
+ # [<tt>:hostName</tt>] the hostname of the nameserver.
360
+ # [<tt>:hostAddrv4</tt>] an optionnal array of ipv4 addresses.
361
+ # [<tt>:hostAddrv6</tt>] an optionnal array of ipv6 addresses.
362
+ # [<tt>:contacts</tt>]
363
+ # an optionnal hash which keys are choosen between +admin+, +billing+ and
364
+ # +tech+ and which values are arrays of nic handles for the corresponding
365
+ # contact types.
366
+ # [<tt>:status</tt>]
367
+ # an optional hash with status values to be applied to or removed from
368
+ # the object. When specifying a value to be removed, only the attribute
369
+ # value is significant; element text is not required to match a value
370
+ # for removal.
371
+ # [<tt>:chg</tt>]
372
+ # changes the following in the domain object.
373
+ # [<tt>:registrant</tt>] an optionnal registrant nic handle.
374
+ # [<tt>:authInfo</tt>] an optional password associated with the domain.
375
+ #
376
+ # Returns true on success, or raises an exception.
377
+ def domain_update(args)
378
+ response = send_request(domain_update_xml(args))
379
+
380
+ get_result(response)
381
+ end
382
+
383
+
384
+ def domain_pending_action_process(xml) #:nodoc:
385
+ dom = xml.xpath('epp:resData/domain:panData', EPPClient::SCHEMAS_URL)
386
+ ret = {
387
+ :name => dom.xpath('domain:name', EPPClient::SCHEMAS_URL).text,
388
+ :paResult => dom.xpath('domain:name', EPPClient::SCHEMAS_URL).attribute('paResult').value,
389
+ :paTRID => get_trid(dom.xpath('domain:paTRID', EPPClient::SCHEMAS_URL)),
390
+ :paDate => DateTime.parse(dom.xpath('domain:paDate', EPPClient::SCHEMAS_URL).text),
391
+ }
392
+ end
393
+ end
394
+ end
@@ -0,0 +1,21 @@
1
+ module EPPClient
2
+ class EPPErrorResponse < StandardError
3
+ attr_accessor :response_xml, :response_code, :message
4
+
5
+ # An exception with an added field so that it can store the xml response
6
+ # that generated it.
7
+ def initialize(attrs = {})
8
+ @response_xml = attrs[:xml]
9
+ @response_code = attrs[:code]
10
+ @message = attrs[:message]
11
+ end
12
+
13
+ def to_s #:nodoc:
14
+ "#{@message} (code #{@response_code})"
15
+ end
16
+
17
+ def inspect #:nodoc:
18
+ "#<#{self.class}: code: #{@response_code}, message: #{@message.inspect}, xml: #{@response_xml}>"
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,69 @@
1
+ module EPPClient
2
+ module Poll
3
+ def poll_req_xml #:nodoc:
4
+ command do |xml|
5
+ xml.poll(:op => :req)
6
+ end
7
+ end
8
+
9
+ # sends a <tt><epp:poll op="req"></tt> command to the server.
10
+ #
11
+ # if there is a message in the queue, returns a hash with the following keys :
12
+ # [<tt>:qDate</tt>] the date and time that the message was enqueued.
13
+ # [<tt>:msg</tt>, <tt>:msg_xml</tt>]
14
+ # a human readble message, the <tt>:msg</tt> version has all the possible
15
+ # xml stripped, whereas the <tt>:msg_xml</tt> contains the original
16
+ # message.
17
+ # [<tt>:obj</tt>, <tt>:obj_xml</tt>]
18
+ # contains a possible <tt><epp:resData></tt> object, the original one in
19
+ # <tt>:obj_xml</tt>, and if a parser is available, the parsed one in
20
+ # <tt>:obj</tt>.
21
+ def poll_req
22
+ response = send_request(poll_req_xml)
23
+
24
+ get_result(:xml => response, :callback => :poll_req_process)
25
+ end
26
+
27
+ PARSERS = {}
28
+
29
+ def poll_req_process(xml) #:nodoc:
30
+ ret = {}
31
+ if (date = xml.xpath("epp:msgQ/epp:qDate", EPPClient::SCHEMAS_URL)).size > 0
32
+ ret[:qDate] = DateTime.parse(date.text)
33
+ end
34
+ if (msg = xml.xpath("epp:msgQ/epp:msg", EPPClient::SCHEMAS_URL)).size > 0
35
+ ret[:msg] = msg.text
36
+ ret[:msg_xml] = msg.to_s
37
+ end
38
+ if (obj = xml.xpath('epp:resData', EPPClient::SCHEMAS_URL)).size > 0
39
+ ret[:obj_xml] = obj.to_s
40
+ PARSERS.each do |xpath,parser|
41
+ if obj.xpath(xpath, EPPClient::SCHEMAS_URL).size > 0
42
+ ret[:obj] = case parser
43
+ when Symbol
44
+ send(parser, xml)
45
+ else
46
+ raise NotImplementedError
47
+ end
48
+ end
49
+ end
50
+ end
51
+ ret
52
+ end
53
+
54
+ def poll_ack_xml(mid) #:nodoc:
55
+ command do |xml|
56
+ xml.poll(:op => :ack, :msgID => mid)
57
+ end
58
+ end
59
+
60
+ # sends a <tt><epp:poll op="ack" msgID="<mid>"></tt> command to the server.
61
+ # Most of the time, you should not pass any argument, as it will "do the
62
+ # right thing".
63
+ def poll_ack(mid = @msgQ_id)
64
+ response = send_request(poll_ack_xml(mid))
65
+
66
+ get_result(response)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,56 @@
1
+ module EPPClient
2
+ module Session
3
+
4
+ # Sends an hello epp command.
5
+ def hello
6
+ send_request(command do |xml|
7
+ xml.hello
8
+ end)
9
+ end
10
+
11
+ def login_xml(new_pw = nil) #:nodoc:
12
+ command do |xml|
13
+ xml.login do
14
+ xml.clID(@client_id)
15
+ xml.pw(@password)
16
+ xml.newPW(new_pw) unless new_pw.nil?
17
+ xml.options do
18
+ xml.version(@version)
19
+ xml.lang(@lang)
20
+ end
21
+ xml.svcs do
22
+ services.each do |s|
23
+ xml.objURI(s)
24
+ end
25
+ unless extensions.empty?
26
+ xml.svcExtension do
27
+ extensions.each do |e|
28
+ xml.extURI(e)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ private :login_xml
37
+
38
+ # Perform the login command on the server. Takes an optionnal argument, the
39
+ # new password for the account.
40
+ def login(new_pw = nil)
41
+ response = send_request(login_xml(new_pw))
42
+
43
+ get_result(response)
44
+ end
45
+
46
+ # Performs the logout command, after it, the server terminates the
47
+ # connection.
48
+ def logout
49
+ response = send_request(command do |xml|
50
+ xml.logout
51
+ end)
52
+
53
+ get_result(response)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,46 @@
1
+ module EPPClient
2
+ module SSL
3
+ def self.included(base) # :nodoc:
4
+ base.class_eval do
5
+ alias_method :open_connection_without_ssl, :open_connection
6
+ alias_method :open_connection, :open_connection_with_ssl
7
+ end
8
+ end
9
+
10
+ attr_reader :ssl_cert, :ssl_key
11
+
12
+ def ssl_key=(key) #:nodoc:
13
+ case key
14
+ when OpenSSL::PKey::RSA
15
+ @ssl_key = key
16
+ when String
17
+ unless key =~ /-----BEGIN RSA PRIVATE KEY-----/
18
+ key = File.read(key)
19
+ end
20
+ @ssl_key = OpenSSL::PKey::RSA.new(key)
21
+ else
22
+ raise ArgumentError, "Must either be an OpenSSL::PKey::RSA object, a filename or a key"
23
+ end
24
+ end
25
+
26
+ def ssl_cert=(cert) #:nodoc:
27
+ case cert
28
+ when OpenSSL::X509::Certificate
29
+ @ssl_cert = cert
30
+ when String
31
+ unless cert =~ /-----BEGIN CERTIFICATE-----/
32
+ cert = File.read(cert)
33
+ end
34
+ @ssl_cert = OpenSSL::X509::Certificate.new(cert)
35
+ else
36
+ raise ArgumentError, "Must either be an OpenSSL::X509::Certificate object, a filename or a certificate"
37
+ end
38
+ end
39
+
40
+ def open_connection_with_ssl # :nodoc:
41
+ @context.cert ||= ssl_cert if ssl_cert.is_a?(OpenSSL::X509::Certificate)
42
+ @context.key ||= ssl_key if ssl_key.is_a?(OpenSSL::PKey::RSA)
43
+ open_connection_without_ssl
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,3 @@
1
+ module EPPClient
2
+ VERSION = "0.11.0"
3
+ end