epp-client-afnic 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ 2010-05-14 mat
2
+ * first release
3
+
4
+ 2010-05-04 mat
5
+ * Initial commit
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pp'
4
+ require 'rubygems'
5
+ require 'epp-client/afnic'
6
+
7
+ c = EPPClient::AFNIC.new(
8
+ :client_id => '-test-.fr',
9
+ :password => 'pass',
10
+ :ssl_cert => 'server.crt',
11
+ :ssl_key => 'server.key',
12
+ :test => true
13
+ )
14
+
15
+ begin
16
+
17
+ c.open_connection
18
+ c.login
19
+ if c.msgQ_count > 0
20
+ pp msg = c.poll_req
21
+ pp c.poll_ack if Hash === msg[:obj]
22
+ end
23
+ pp c.domain_check('mat.fr', 'nonexistantdomain.fr', 'paris.fr', 'trafiquants.fr', 'toto.wf')
24
+ pp c.domain_info('afnic.fr')
25
+ # pas de contact check
26
+ pp c.contact_info('A7534') # legal
27
+ pp c.contact_info('CT214') # particulier
28
+ pp contact = c.contact_create( {
29
+ :email=>"foo@example.org",
30
+ :authInfo => "foobar",
31
+ :voice=>"+33.123456787",
32
+ :postalInfo=> { "loc"=> { :addr=> { :city=>"Paris", :cc=>"FR", :pc=>"75001", :street=>["BP 18", "1, rue Royale"] }, :name=>"Dupond", :org => 'FooBar Inc.' }, },
33
+ #:list => 'restrictedPublication',
34
+ #:individualInfos => {:birthDate => '1978-01-01', :birthCc => 'FR', :birthPc => '75013', :birthCity => 'Paris'},
35
+ #:firstName => 'Jean',
36
+ :legalEntityInfos=> {:siren=>"418565404", :legalStatus=>"company"},
37
+ #:legalEntityInfos=> {:siren=>"418565404", :legalStatus=>"association", :asso => { :decl => '2001-01-01', :publ => {:date => '2001-01-02', :announce => 5, :page => 3}}},
38
+ #:legalEntityInfos=> {:siren=>"418565404", :legalStatus=>"association", :asso => { :waldec => 2 }},
39
+ })
40
+ pp c.contact_info(contact[:id])
41
+ pp c.contact_update({
42
+ :id => contact[:id],
43
+ :chg => {
44
+ :email => 'bazar@example.com',
45
+ }})
46
+ pp c.contact_info(contact[:id])
47
+ pp c.contact_info(contact[:id])
48
+ pp c.domain_create({
49
+ :registrant => "A7534",
50
+ :contacts => {:tech => ["ADM3"], :admin => ["ADM3"]},
51
+ :name => "truc-#{$$}.fr",
52
+ :authInfo => "PN16IZ0V"
53
+ })
54
+ pp c.domain_info(:name => "truc-#{$$}.fr")
55
+ pp c.domain_update({
56
+ :name => "truc-#{$$}.fr",
57
+ #:chg => { :authInfo => 'bazar' },
58
+ :add => {
59
+ :ns => %w(ns1.absolight.net ns2.absolight.net ns3.absolight.net ns4.absolight.net),
60
+ },
61
+ })
62
+ pp c.domain_info(:name => "truc-#{$$}.fr")
63
+ pp c.domain_delete("truc-#{$$}.fr")
64
+ pp c.domain_info(:name => "truc-#{$$}.fr")
65
+ pp c.domain_restore(:name => "truc-#{$$}.fr")
66
+ pp c.domain_info(:name => "truc-#{$$}.fr")
67
+
68
+ ensure
69
+ c.logout
70
+ end
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in epp-client.gemspec
4
+ Dir['*.gemspec'].each do |i|
5
+ gemspec :name => i.sub(/\.gemspec$/, '')
6
+ end
@@ -0,0 +1,19 @@
1
+ Copyright (C) 2010 Mathieu Arnold, Absolight
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ of the Software, and to permit persons to whom the Software is furnished to do
8
+ so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/README ADDED
@@ -0,0 +1,5 @@
1
+ An extensible EPP client library
2
+
3
+ When possible, the objects that are received via EPP are translated to their
4
+ equivalent in ruby, that is, dates in Date object, timestamps in DateTime
5
+ objects.
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env rake
2
+ require 'rake'
3
+ require 'rdoc/task'
4
+ require 'rubygems/package_task'
5
+ require "bundler/gem_helper"
6
+
7
+ MY_GEMS = Dir['*.gemspec'].map {|g| g.sub(/.*-(.*)\.gemspec/, '\1')}
8
+
9
+ MY_GEMS.each do |g|
10
+ namespace g do
11
+ Bundler::GemHelper.new(Dir.pwd, "epp-client-#{g}").install
12
+ end
13
+ end
14
+
15
+ namespace :all do
16
+ task :build => MY_GEMS.map { |f| "#{f}:build" }
17
+ task :install => MY_GEMS.map { |f| "#{f}:install" }
18
+ task :release => MY_GEMS.map { |f| "#{f}:release" }
19
+ end
20
+
21
+ task :build => 'all:build'
22
+ task :install => 'all:install'
23
+ task :release => 'all:release'
24
+
25
+ desc "Generate documentation for the Rails framework"
26
+ Rake::RDocTask.new do |rdoc|
27
+ rdoc.rdoc_dir = 'doc/rdoc'
28
+ rdoc.title = "Documentation"
29
+
30
+ rdoc.options << '--line-numbers' << '--inline-source'
31
+ rdoc.options << '--charset' << 'utf-8'
32
+
33
+ rdoc.rdoc_files.include('README')
34
+ rdoc.rdoc_files.include('ChangeLog')
35
+ rdoc.rdoc_files.include('lib/**/*.rb')
36
+ end
37
+
@@ -0,0 +1,40 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/epp-client/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = 'epp-client-afnic'
6
+ gem.version = EPPClient::VERSION
7
+ gem.date = '2010-05-14'
8
+ gem.authors = ['Mathieu Arnold']
9
+ gem.email = ['m@absolight.fr']
10
+ gem.description = 'AFNIC EPP client library.'
11
+ gem.summary = 'AFNIC EPP client library'
12
+ gem.homepage = "https://github.com/Absolight/epp-client"
13
+
14
+ gem.required_ruby_version = '>= 1.8.7'
15
+ gem.required_rubygems_version = ">= 1.3.6"
16
+
17
+ gem.files = [
18
+ 'ChangeLog',
19
+ 'EXAMPLE.AFNIC',
20
+ 'Gemfile',
21
+ 'MIT-LICENSE',
22
+ 'README',
23
+ 'Rakefile',
24
+ 'epp-client-afnic.gemspec',
25
+ 'lib/epp-client/afnic.rb',
26
+ 'vendor/afnic/frnic-1.0.xsd',
27
+ 'vendor/afnic/frnic-1.1.xsd',
28
+ 'vendor/afnic/frnic-1.2.xsd',
29
+ ]
30
+
31
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
32
+ gem.require_paths = ['lib']
33
+
34
+ gem.add_development_dependency "bundler", ">= 1.0.0"
35
+ gem.add_dependency('nokogiri', '~> 1.4')
36
+ gem.add_dependency('builder', '>= 2.1.2')
37
+ gem.add_dependency('epp-client-base', "~> #{EPPClient::VERSION}")
38
+ gem.add_dependency('epp-client-rgp', "~> #{EPPClient::VERSION}")
39
+ gem.add_dependency('epp-client-secdns', "~> #{EPPClient::VERSION}")
40
+ end
@@ -0,0 +1,505 @@
1
+ require 'epp-client/base'
2
+ require 'epp-client/rgp'
3
+ require 'epp-client/secdns'
4
+
5
+ module EPPClient
6
+ class AFNIC < Base
7
+ SCHEMAS_AFNIC = %w[
8
+ frnic-1.2
9
+ ]
10
+
11
+ EPPClient::SCHEMAS_URL.merge!(SCHEMAS_AFNIC.inject({}) do |a,s|
12
+ a[s.sub(/-1\.2$/, '')] = "http://www.afnic.fr/xml/epp/#{s}" if s =~ /-1\.2$/
13
+ a[s] = "http://www.afnic.fr/xml/epp/#{s}"
14
+ a
15
+ end)
16
+
17
+ # Sets the default for AFNIC, that is, server and port, according to
18
+ # AFNIC's documentation.
19
+ # http://www.afnic.fr/doc/interface/epp
20
+ #
21
+ # ==== Optional Attributes
22
+ # [<tt>:test</tt>] sets the server to be the test server.
23
+ def initialize(args)
24
+ if args.delete(:test) == true
25
+ args[:server] ||= 'epp.test.nic.fr'
26
+ else
27
+ args[:server] ||= 'epp.nic.fr'
28
+ end
29
+ @services = EPPClient::SCHEMAS_URL.values_at('domain', 'contact')
30
+ args[:port] ||= 700
31
+ super(args)
32
+ @extensions << EPPClient::SCHEMAS_URL['frnic']
33
+ end
34
+
35
+ # Extends the base domain check so that the specific afnic check
36
+ # informations are processed, the additionnal informations are :
37
+ #
38
+ # [<tt>:reserved</tt>] the domain is a reserved name.
39
+ # [<tt>:rsvReason</tt>] the optional reason why the domain is reserved.
40
+ # [<tt>:forbidden</tt>] the domain is a forbidden name.
41
+ # [<tt>:fbdReason</tt>] the optional reason why the domain is forbidden.
42
+ def domain_check(*domains)
43
+ super # placeholder so that I can add some doc
44
+ end
45
+
46
+ def domain_check_process(xml) # :nodoc:
47
+ ret = super
48
+ xml.xpath('epp:extension/frnic:ext/frnic:resData/frnic:chkData/frnic:domain/frnic:cd', EPPClient::SCHEMAS_URL).each do |dom|
49
+ name = dom.xpath('frnic:name', EPPClient::SCHEMAS_URL)
50
+ hash = ret.select {|d| d[:name] == name.text}.first
51
+ hash[:reserved] = name.attr('reserved').value == "1"
52
+ unless (reason = dom.xpath('frnic:rsvReason', EPPClient::SCHEMAS_URL).text).empty?
53
+ hash[:rsvReason] = reason
54
+ end
55
+ hash[:forbidden] = name.attr('forbidden').value == "1"
56
+ unless (reason = dom.xpath('frnic:fbdReason', EPPClient::SCHEMAS_URL).text).empty?
57
+ hash[:fbdReason] = reason
58
+ end
59
+ end
60
+ return ret
61
+ end
62
+
63
+ # Extends the base domain info so that the specific afnic <tt>:status</tt>
64
+ # can be added.
65
+ def domain_info(domain)
66
+ super # placeholder so that I can add some doc
67
+ end
68
+
69
+ def domain_info_process(xml) #:nodoc:
70
+ ret = super
71
+ if (frnic_status = xml.xpath('epp:extension/frnic:ext/frnic:resData/frnic:infData/frnic:domain/frnic:status', EPPClient::SCHEMAS_URL)).size > 0
72
+ ret[:status] += frnic_status.map {|s| s.attr('s')}
73
+ end
74
+ ret
75
+ end
76
+
77
+ # parse legalEntityInfos content.
78
+ def legalEntityInfos(leI) #:nodoc:
79
+ ret = {}
80
+ ret[:legalStatus] = leI.xpath('frnic:legalStatus', EPPClient::SCHEMAS_URL).attr('s').value
81
+ if (r = leI.xpath("frnic:idStatus", EPPClient::SCHEMAS_URL)).size > 0
82
+ ret[:idStatus] = {:value => r.text}
83
+ ret[:idStatus][:when] = r.attr('when').value if r.attr('when')
84
+ ret[:idStatus][:source] = r.attr('source').value if r.attr('source')
85
+ end
86
+ %w(siren VAT trademark DUNS local).each do |val|
87
+ if (r = leI.xpath("frnic:#{val}", EPPClient::SCHEMAS_URL)).size > 0
88
+ ret[val.to_sym] = r.text
89
+ end
90
+ end
91
+ if (asso = leI.xpath("frnic:asso", EPPClient::SCHEMAS_URL)).size > 0
92
+ ret[:asso] = {}
93
+ if (r = asso.xpath("frnic:waldec", EPPClient::SCHEMAS_URL)).size > 0
94
+ ret[:asso][:waldec] = r.text
95
+ else
96
+ if (decl = asso.xpath('frnic:decl', EPPClient::SCHEMAS_URL)).size > 0
97
+ ret[:asso][:decl] = Date.parse(decl.text)
98
+ end
99
+ publ = asso.xpath('frnic:publ', EPPClient::SCHEMAS_URL)
100
+ ret[:asso][:publ] = {
101
+ :date => Date.parse(publ.text),
102
+ :page => publ.attr('page').value,
103
+ }
104
+ if (announce = publ.attr('announce')) && announce.value != '0'
105
+ ret[:asso][:publ][:announce] = announce.value
106
+ end
107
+ end
108
+ end
109
+ ret
110
+ end
111
+ private :legalEntityInfos
112
+
113
+ # Extends the base contact info so that the specific afnic check
114
+ # informations are processed, the additionnal informations are :
115
+ #
116
+ # either :
117
+ # [<tt>:legalEntityInfos</tt>]
118
+ # indicating that the contact is an organisation with the following
119
+ # informations :
120
+ # [<tt>:legalStatus</tt>]
121
+ # should be either +company+, +association+ or +other+.
122
+ # [<tt>:idStatus</tt>]
123
+ # indicates the identification process status. Has optional
124
+ # <tt>:when</tt> and <tt>:source</tt> attributes.
125
+ # [<tt>:siren</tt>] contains the SIREN number of the organisation.
126
+ # [<tt>:VAT</tt>]
127
+ # is optional and contains the VAT number of the organisation.
128
+ # [<tt>:trademark</tt>]
129
+ # is optional and contains the trademark number of the organisation.
130
+ # [<tt>:DUNS</tt>]
131
+ # is optional and contains the Data Universal Numbering System number of
132
+ # the organisation.
133
+ # [<tt>:local</tt>]
134
+ # is optional and contains an identifier local to the eligible country.
135
+ # [<tt>:asso</tt>]
136
+ # indicates the organisation is an association and contains either a
137
+ # +waldec+ or a +decl+ and a +publ+ :
138
+ # [<tt>:waldec</tt>] contains the waldec id of the association.
139
+ # [<tt>:decl</tt>]
140
+ # optionally indicate the date of the association was declared at the
141
+ # prefecture.
142
+ # [<tt>:publ</tt>]
143
+ # contains informations regarding the publication in the "Journal
144
+ # Officiel" :
145
+ # [<tt>:date</tt>] the date of publication.
146
+ # [<tt>:page</tt>] the page the announce is on.
147
+ # [<tt>:announce</tt>] the announce number on the page (optional).
148
+ # [<tt>:individualInfos</tt>]
149
+ # indicating that the contact is a person with the following
150
+ # informations :
151
+ # [<tt>:idStatus</tt>]
152
+ # indicates the identification process status. Has optional
153
+ # <tt>:when</tt> and <tt>:source</tt> attributes.
154
+ # [<tt>:birthDate</tt>] the date of birth of the contact.
155
+ # [<tt>:birthCity</tt>] the city of birth of the contact.
156
+ # [<tt>:birthPc</tt>] the postal code of the city of birth.
157
+ # [<tt>:birthCc</tt>] the country code of the place of birth.
158
+ #
159
+ # Additionnaly, when the contact is a person, there can be the following
160
+ # informations :
161
+ # [<tt>:firstName</tt>]
162
+ # the first name of the person. (The last name being stored in the +name+
163
+ # field in the +postalInfo+.)
164
+ # [<tt>:list</tt>]
165
+ # with the value of +restrictedPublication+ mean that the element
166
+ # diffusion should be restricted.
167
+ #
168
+ # Optionnaly, there can be :
169
+ # [<tt>:obsoleted</tt>]
170
+ # the contact info is obsolete since/from the optional date <tt>:when</tt>.
171
+ # [<tt>:reachable</tt>]
172
+ # the contact is reachable through the optional <tt>:media</tt> since/from
173
+ # the optional date <tt>:when</tt>. The info having been specified by the
174
+ # <tt>:source</tt>.
175
+ def contact_info(contact)
176
+ super # placeholder so that I can add some doc
177
+ end
178
+
179
+ def contact_info_process(xml) #:nodoc:
180
+ ret = super
181
+ if (contact = xml.xpath('epp:extension/frnic:ext/frnic:resData/frnic:infData/frnic:contact', EPPClient::SCHEMAS_URL)).size > 0
182
+ if (list = contact.xpath('frnic:list', EPPClient::SCHEMAS_URL)).size > 0
183
+ ret[:list] = list.map {|l| l.text}
184
+ end
185
+ if (firstName = contact.xpath('frnic:firstName', EPPClient::SCHEMAS_URL)).size > 0
186
+ ret[:firstName] = firstName.text
187
+ end
188
+ if (iI = contact.xpath('frnic:individualInfos', EPPClient::SCHEMAS_URL)).size > 0
189
+ ret[:individualInfos] = {}
190
+ ret[:individualInfos][:birthDate] = Date.parse(iI.xpath('frnic:birthDate', EPPClient::SCHEMAS_URL).text)
191
+ if (r = iI.xpath("frnic:idStatus", EPPClient::SCHEMAS_URL)).size > 0
192
+ ret[:individualInfos][:idStatus] = {:value => r.text}
193
+ ret[:individualInfos][:idStatus][:when] = r.attr('when').value if r.attr('when')
194
+ ret[:individualInfos][:idStatus][:source] = r.attr('source').value if r.attr('source')
195
+ end
196
+ %w(birthCity birthPc birthCc).each do |val|
197
+ if (r = iI.xpath("frnic:#{val}", EPPClient::SCHEMAS_URL)).size > 0
198
+ ret[:individualInfos][val.to_sym] = r.text
199
+ end
200
+ end
201
+ end
202
+ if (leI = contact.xpath('frnic:legalEntityInfos', EPPClient::SCHEMAS_URL)).size > 0
203
+ ret[:legalEntityInfos] = legalEntityInfos(leI)
204
+ end
205
+ if (obsoleted = contact.xpath('frnic:obsoleted', EPPClient::SCHEMAS_URL)).size > 0
206
+ if obsoleted.text != '0'
207
+ ret[:obsoleted] = {}
208
+ ret[:obsoleted][:when] = DateTime.parse(v_when.value) if v_when = obsoleted.attr('when')
209
+ end
210
+ end
211
+ if (reachable = contact.xpath('frnic:reachable', EPPClient::SCHEMAS_URL)).size > 0
212
+ if reachable.text != '0'
213
+ ret[:reachable] = {}
214
+ if v_when = reachable.attr('when')
215
+ ret[:reachable][:when] = DateTime.parse(v_when.value)
216
+ end
217
+ if media = reachable.attr('media')
218
+ ret[:reachable][:media] = media.value
219
+ end
220
+ if source = reachable.attr('source')
221
+ ret[:reachable][:source] = source.value
222
+ end
223
+ end
224
+ end
225
+ end
226
+ ret
227
+ end
228
+
229
+ def contact_create_xml(contact) #:nodoc:
230
+ ret = super
231
+
232
+ ext = extension do |xml|
233
+ xml.ext( :xmlns => EPPClient::SCHEMAS_URL['frnic']) do
234
+ xml.create do
235
+ xml.contact do
236
+ if contact.key?(:legalEntityInfos)
237
+ lEI = contact[:legalEntityInfos]
238
+ xml.legalEntityInfos do
239
+ xml.idStatus(lEI[:idStatus]) if lEI.key?(:idStatus)
240
+ xml.legalStatus(:s => lEI[:legalStatus])
241
+ [:siren, :VAT, :trademark, :DUNS, :local].each do |val|
242
+ if lEI.key?(val)
243
+ xml.__send__(val, lEI[val])
244
+ end
245
+ end
246
+ if lEI.key?(:asso)
247
+ asso = lEI[:asso]
248
+ xml.asso do
249
+ if asso.key?(:waldec)
250
+ xml.waldec(asso[:waldec])
251
+ else
252
+ xml.decl(asso[:decl]) if asso.key?(:decl)
253
+ attrs = {:page => asso[:publ][:page]}
254
+ attrs[:announce] = asso[:publ][:announce] if asso[:publ].key?(:announce)
255
+ xml.publ(attrs, asso[:publ][:date])
256
+ end
257
+ end
258
+ end
259
+ end
260
+ else
261
+ if contact.key?(:list)
262
+ xml.list(contact[:list])
263
+ end
264
+ if contact.key?(:individualInfos)
265
+ iI = contact[:individualInfos]
266
+ xml.individualInfos do
267
+ xml.idStatus(iI[:idStatus]) if iI.key?(:idStatus)
268
+ xml.birthDate(iI[:birthDate])
269
+ if iI.key?(:birthCity)
270
+ xml.birthCity(iI[:birthCity])
271
+ end
272
+ if iI.key?(:birthPc)
273
+ xml.birthPc(iI[:birthPc])
274
+ end
275
+ xml.birthCc(iI[:birthCc])
276
+ end
277
+ end
278
+ if contact.key?(:firstName)
279
+ xml.firstName(contact[:firstName])
280
+ end
281
+ end
282
+ if contact.key?(:reachable)
283
+ if Hash === (reachable = contact[:reachable])
284
+ xml.reachable(reachable, 1)
285
+ else
286
+ raise ArgumentError, "reachable has to be a Hash"
287
+ end
288
+ end
289
+ end
290
+ end
291
+ end
292
+ end
293
+
294
+ insert_extension(ret, ext)
295
+ end
296
+
297
+ # Extends the base contact create so that the specific afnic create
298
+ # informations can be sent, the additionnal informations are :
299
+ #
300
+ # either :
301
+ # [<tt>:legalEntityInfos</tt>]
302
+ # indicating that the contact is an organisation with the following
303
+ # informations :
304
+ # [<tt>:idStatus</tt>]
305
+ # indicates the identification process status.
306
+ # [<tt>:legalStatus</tt>]
307
+ # should be either +company+, +association+ or +other+.
308
+ # [<tt>:siren</tt>] contains the SIREN number of the organisation.
309
+ # [<tt>:VAT</tt>]
310
+ # is optional and contains the VAT number of the organisation.
311
+ # [<tt>:trademark</tt>]
312
+ # is optional and contains the trademark number of the organisation.
313
+ # [<tt>:DUNS</tt>]
314
+ # is optional and contains the Data Universal Numbering System number of
315
+ # the organisation.
316
+ # [<tt>:local</tt>]
317
+ # is optional and contains an identifier local to the eligible country.
318
+ # [<tt>:asso</tt>]
319
+ # indicates the organisation is an association and contains either a
320
+ # +waldec+ or a +decl+ and a +publ+ :
321
+ # [<tt>:waldec</tt>] contains the waldec id of the association.
322
+ # [<tt>:decl</tt>]
323
+ # optionally indicate the date of the association was declared at the
324
+ # prefecture.
325
+ # [<tt>:publ</tt>]
326
+ # contains informations regarding the publication in the "Journal
327
+ # Officiel" :
328
+ # [<tt>:date</tt>] the date of publication.
329
+ # [<tt>:page</tt>] the page the announce is on.
330
+ # [<tt>:announce</tt>] the announce number on the page (optional).
331
+ # [<tt>:individualInfos</tt>]
332
+ # indicating that the contact is a person with the following
333
+ # informations :
334
+ # [<tt>:idStatus</tt>]
335
+ # indicates the identification process status.
336
+ # [<tt>:birthDate</tt>] the date of birth of the contact.
337
+ # [<tt>:birthCity</tt>] the city of birth of the contact.
338
+ # [<tt>:birthPc</tt>] the postal code of the city of birth.
339
+ # [<tt>:birthCc</tt>] the country code of the place of birth.
340
+ #
341
+ # Additionnaly, when the contact is a person, there can be the following
342
+ # informations :
343
+ # [<tt>:firstName</tt>]
344
+ # the first name of the person. (The last name being stored in the +name+
345
+ # field in the +postalInfo+.)
346
+ # [<tt>:list</tt>]
347
+ # with the value of +restrictedPublication+ mean that the element
348
+ # diffusion should be restricted.
349
+ #
350
+ # Optionnaly, there can be :
351
+ # [<tt>:reachable</tt>]
352
+ # the contact is reachable through the optional <tt>:media</tt>.
353
+ #
354
+ # The returned information contains new keys :
355
+ # [<tt>:idStatus</tt>]
356
+ # indicates the identification process status. It's only present when the
357
+ # created contact was created with the +:individualInfos+ or
358
+ # +:legalEntityInfos+ extensions.
359
+ # [<tt>:nhStatus</tt>]
360
+ # is a boolean indicating wether the contact is really new, or if there
361
+ # was already a contact with the exact same informations in the database,
362
+ # in which case, it has been returned.
363
+ def contact_create(contact)
364
+ super # placeholder so that I can add some doc
365
+ end
366
+
367
+ def contact_create_process(xml) #:nodoc:
368
+ ret = super
369
+ if (creData = xml.xpath('epp:extension/frnic:ext/frnic:resData/frnic:creData', EPPClient::SCHEMAS_URL)).size > 0
370
+ ret[:nhStatus] = creData.xpath('frnic:nhStatus', EPPClient::SCHEMAS_URL).attr('new').value == '1'
371
+ ret[:idStatus] = creData.xpath('frnic:idStatus', EPPClient::SCHEMAS_URL).text
372
+ end
373
+ ret
374
+ end
375
+
376
+ # Make sure there's no <tt>:ns</tt>, <tt>:dsData</tt> or <tt>:keyData</tt>
377
+ # records, AFNIC's servers sends quite a strange error when there is.
378
+ def domain_create(args)
379
+ raise ArgumentError, "You can't create a domain with ns records, you must do an update afterwards" if args.key?(:ns)
380
+ raise ArgumentError, "You can't create a domain with ds or key records, you must do an update afterwards" if args.key?(:dsData) || args.key?(:keyData)
381
+ super
382
+ end
383
+
384
+ # Raises an exception, as contacts are deleted with a garbage collector.
385
+ def contact_delete(args)
386
+ raise NotImplementedError, "Contacts are deleted with a garbage collector"
387
+ end
388
+
389
+ def contact_update_xml(args) #:nodoc:
390
+ ret = super
391
+
392
+ if [:add, :rem].any? {|c| args.key?(c) && [:list, :reachable, :idStatus].any? {|k| args[c].key?(k)}}
393
+ ext = extension do |xml|
394
+ xml.ext( :xmlns => EPPClient::SCHEMAS_URL['frnic']) do
395
+ xml.update do
396
+ xml.contact do
397
+ [:add, :rem].each do |c|
398
+ if args.key?(c) && [:list, :reachable, :idStatus].any? {|k| args[c].key?(k)}
399
+ xml.__send__(c) do
400
+ if args[c].key?(:list)
401
+ xml.list(args[c][:list])
402
+ end
403
+ if args[c].key?(:idStatus)
404
+ xml.idStatus(args[c][:idStatus])
405
+ end
406
+ if args[c].key?(:reachable)
407
+ if Hash === (reachable = args[c][:reachable])
408
+ xml.reachable(reachable, 1)
409
+ else
410
+ raise ArgumentError, "reachable has to be a Hash"
411
+ end
412
+ end
413
+ end
414
+ end
415
+ end
416
+ end
417
+ end
418
+ end
419
+ end
420
+
421
+ return insert_extension(ret, ext)
422
+ else
423
+ return ret
424
+ end
425
+ end
426
+
427
+ # Extends the base contact update so that the specific afnic update
428
+ # informations can be sent, the additionnal informations are :
429
+ #
430
+ # [<tt>:add</tt>/<tt>:rem</tt>]
431
+ # adds or removes the following datas :
432
+ # [<tt>:list</tt>]
433
+ # with the value of +restrictedPublication+ mean that the element
434
+ # diffusion should/should not be restricted.
435
+ # [<tt>:idStatus</tt>]
436
+ # indicates the identification process status.
437
+ # [<tt>:reachable</tt>]
438
+ # the contact is reachable through the optional <tt>:media</tt>.
439
+ def contact_update(args)
440
+ super # placeholder so that I can add some doc
441
+ end
442
+
443
+ # Extends the base domain update so that afnic's weirdnesses can be taken
444
+ # into account.
445
+ #
446
+ # AFNIC does not support ns/hostObj, only ns/hostAttr/Host*, so, take care
447
+ # of this here.
448
+ # Also, you can only do one of the following at a time :
449
+ # * update contacts
450
+ # * update name servers
451
+ # * update status & authInfo
452
+ def domain_update(args)
453
+ if args.key?(:chg) && args[:chg].key?(:registrant)
454
+ raise ArgumentError, "You need to do a trade or recover operation to change the registrant"
455
+ end
456
+ has_contacts = args.key?(:add) && args[:add].key?(:contacts) || args.key?(:add) && args[:add].key?(:contacts)
457
+ has_ns = args.key?(:add) && args[:add].key?(:ns) || args.key?(:add) && args[:add].key?(:ns)
458
+ has_other = args.key?(:add) && args[:add].key?(:status) || args.key?(:add) && args[:add].key?(:status) || args.key?(:chg) && args[:chg].key?(:authInfo)
459
+ if [has_contacts, has_ns, has_other].select {|v| v}.size > 1
460
+ raise ArgumentError, "You can't update all that at one time"
461
+ end
462
+ [:add, :rem].each do |ar|
463
+ if args.key?(ar) && args[ar].key?(:ns) && String === args[ar][:ns].first
464
+ args[ar][:ns] = args[ar][:ns].map {|ns| {:hostName => ns}}
465
+ end
466
+ end
467
+ super
468
+ end
469
+
470
+ # Extends the base poll req to be able to parse quallification response
471
+ # extension.
472
+ def poll_req
473
+ super # placeholder so that I can add some doc
474
+ end
475
+
476
+ def poll_req_process(xml) #:nodoc:
477
+ ret = super(xml)
478
+ if (quaData = xml.xpath('epp:extension/frnic:resData/frnic:quaData', EPPClient::SCHEMAS_URL)).size > 0
479
+ if (contact = xml.xpath('frnic:contact', EPPClient::SCHEMAS_URL)).size > 0
480
+ cret = {:id => contact.xpath('frnic:id', EPPClient::SCHEMAS_URL).text}
481
+ qP = contact.xpath('frnic:qualificationProcess', EPPClient::SCHEMAS_URL)
482
+ cret[:qualificationProcess][:s] = qP.attr('s').value
483
+ cret[:qualificationProcess][:lang] = qP.attr('lang').value if qP.attr('lang')
484
+ if (leI = contact.xpath('frnic:legalEntityInfos', EPPClient::SCHEMAS_URL)).size > 0
485
+ ret[:legalEntityInfos] = legalEntityInfos(leI)
486
+ end
487
+ reach = contact.xpath('frnic:reachability', EPPClient::SCHEMAS_URL)
488
+ cret[:reachability] = {:reStatus => reach.xpath('frnic:reStatus', EPPClient::SCHEMAS_URL).text}
489
+ if (voice = reach.xpath('frnic:voice', EPPClient::SCHEMAS_URL)).size > 0
490
+ cret[:reachability][:voice] = voice.text
491
+ end
492
+ if (email = reach.xpath('frnic:email', EPPClient::SCHEMAS_URL)).size > 0
493
+ cret[:reachability][:email] = email.text
494
+ end
495
+ ret[:quaData] = {:contact => cret}
496
+ end
497
+ end
498
+ ret
499
+ end
500
+
501
+ # keep that at the end.
502
+ include EPPClient::RGP
503
+ include EPPClient::SecDNS
504
+ end
505
+ end