epp-client-base 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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