nominet-epp 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Geoff Garside
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,17 @@
1
+ = nominet-epp
2
+
3
+ Description goes here.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2010 Geoff Garside. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,55 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "nominet-epp"
8
+ gem.summary = %Q{Nominet EPP (Extensible Provisioning Protocol) Client}
9
+ gem.description = %Q{Client for communicating with the Nominet EPP}
10
+ gem.email = "geoff@geoffgarside.co.uk"
11
+ gem.homepage = "http://github.com/geoffgarside/nominet-epp"
12
+ gem.authors = ["Geoff Garside"]
13
+ gem.add_development_dependency "shoulda", ">= 0"
14
+ gem.add_development_dependency "yard", ">= 0"
15
+
16
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
17
+ gem.add_dependency "epp-client", ">= 0"
18
+ end
19
+ Jeweler::GemcutterTasks.new
20
+ rescue LoadError
21
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
22
+ end
23
+
24
+ require 'rake/testtask'
25
+ Rake::TestTask.new(:test) do |test|
26
+ test.libs << 'lib' << 'test'
27
+ test.pattern = 'test/**/test_*.rb'
28
+ test.verbose = true
29
+ end
30
+
31
+ begin
32
+ require 'rcov/rcovtask'
33
+ Rcov::RcovTask.new do |test|
34
+ test.libs << 'test'
35
+ test.pattern = 'test/**/test_*.rb'
36
+ test.verbose = true
37
+ end
38
+ rescue LoadError
39
+ task :rcov do
40
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
41
+ end
42
+ end
43
+
44
+ task :test => :check_dependencies
45
+
46
+ task :default => :test
47
+
48
+ begin
49
+ require 'yard'
50
+ YARD::Rake::YardocTask.new
51
+ rescue LoadError
52
+ task :yardoc do
53
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
54
+ end
55
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.2
@@ -0,0 +1,139 @@
1
+ require 'epp-client'
2
+ require 'time'
3
+
4
+ require File.dirname(__FILE__) + '/nominet-epp/operations'
5
+ require File.dirname(__FILE__) + '/nominet-epp/helpers'
6
+
7
+ # Nominet EPP Module
8
+ module NominetEPP
9
+ # Front end interface Client to the NominetEPP Service
10
+ class Client
11
+ # Default Nominet Service URNs
12
+ DEFAULT_SERVICES = %w(http://www.nominet.org.uk/epp/xml/nom-domain-2.0
13
+ http://www.nominet.org.uk/epp/xml/nom-notifications-2.0
14
+ urn:ietf:params:xml:ns:host-1.0)
15
+
16
+ # Create a new instance of NominetEPP::Client
17
+ #
18
+ # @param [String] tag Nominet TAG
19
+ # @param [String] passwd Nominet TAG EPP Password
20
+ # @param [String] server Nominet EPP Server address
21
+ def initialize(tag, passwd, server = 'epp.nominet.org.uk')
22
+ @tag, @server = tag, server
23
+ @client = EPP::Client.new(tag, passwd, server, :services => DEFAULT_SERVICES)
24
+ end
25
+
26
+ # @see Object#inspect
27
+ def inspect
28
+ "#<#{self.class} #{@tag}@#{@server}>"
29
+ end
30
+
31
+ # @return [Hash] Nominet Namespaces by prefixes
32
+ def namespaces
33
+ { :domain => 'http://www.nominet.org.uk/epp/xml/nom-domain-2.0',
34
+ :account => 'http://www.nominet.org.uk/epp/xml/nom-account-2.0',
35
+ :contact => 'http://www.nominet.org.uk/epp/xml/nom-contact-2.0',
36
+ :tag => 'http://www.nominet.org.uk/epp/xml/nom-tag-1.0',
37
+ :n => 'http://www.nominet.org.uk/epp/xml/nom-notifications-2.0' }
38
+ end
39
+
40
+ # @return [Hash] Nominet Schema Locations by prefix
41
+ def schemaLocations
42
+ { :domain => 'http://www.nominet.org.uk/epp/xml/nom-domain-2.0 nom-domain-2.0.xsd',
43
+ :account => 'http://www.nominet.org.uk/epp/xml/nom-account-2.0 nom-account-2.0.xsd',
44
+ :contact => 'http://www.nominet.org.uk/epp/xml/nom-contact-2.0 nom-contact-1.0.xsd',
45
+ :tag => 'http://www.nominet.org.uk/epp/xml/nom-tag-1.0 nom-tag-1.0.xsd',
46
+ :n => 'http://www.nominet.org.uk/epp/xml/nom-notifications-2.0 nom-notifications-2.0.xsd' }
47
+ end
48
+
49
+ include Helpers
50
+
51
+ include Operations::Check
52
+ include Operations::Create
53
+ include Operations::Delete
54
+ include Operations::Fork
55
+ include Operations::Hello
56
+ include Operations::Info
57
+ include Operations::List
58
+ include Operations::Lock
59
+ include Operations::Merge
60
+ include Operations::Poll
61
+ include Operations::Renew
62
+ include Operations::Transfer
63
+ include Operations::Unlock
64
+ include Operations::Unrenew
65
+ include Operations::Update
66
+
67
+ private
68
+ # @param [XML::Node] node XML Node to find the value from
69
+ # @param [String] xpath XPath Expression for the value
70
+ # @return [String] node value
71
+ def node_value(node, xpath)
72
+ n = node.find(xpath, namespaces).first
73
+ n && n.content.strip
74
+ end
75
+
76
+ # @param [String] name XML Element name
77
+ # @param [Symbol] ns_prefix Namespace Prefix
78
+ # @yield [node, ns] block to populate node
79
+ # @yieldparam [XML::Node] node XML Node to populate
80
+ # @yieldparam [XML::Namespace] ns XML Namespace of the node
81
+ # @return [XML::Node] newly created node
82
+ def new_node(name, ns_prefix, &block)
83
+ node = XML::Node.new(name)
84
+ node.namespaces.namespace = ns = XML::Namespace.new(node, ns_prefix.to_s, namespaces[ns_prefix])
85
+ node['xsi:schemaLocation'] = schemaLocations[ns_prefix]
86
+
87
+ case block.arity
88
+ when 0
89
+ yield
90
+ when 1
91
+ yield node
92
+ when 2
93
+ yield node, ns
94
+ end
95
+
96
+ node
97
+ end
98
+
99
+ # @param [String] node_name XML Element name
100
+ # @yield [node, ns] block to populate node
101
+ # @return [XML::Node] new node in :domain namespace
102
+ # @see new_node
103
+ def domain(node_name, &block)
104
+ new_node(node_name, :domain, &block)
105
+ end
106
+
107
+ # @param [String] node_name XML Element name
108
+ # @yield [node, ns] block to populate node
109
+ # @return [XML::Node] new node in :account namespace
110
+ # @see new_node
111
+ def account(node_name, &block)
112
+ new_node(node_name, :account, &block)
113
+ end
114
+
115
+ # @param [String] node_name XML Element name
116
+ # @yield [node, ns] block to populate node
117
+ # @return [XML::Node] new node in :contact namespace
118
+ # @see new_node
119
+ def contact(node_name, &block)
120
+ new_node(node_name, :contact, &block)
121
+ end
122
+
123
+ # @param [String] node_name XML Element name
124
+ # @yield [node, ns] block to populate node
125
+ # @return [XML::Node] new node in :tag namespace
126
+ # @see new_node
127
+ def tag(node_name, &block)
128
+ new_node(node_name, :tag, &block)
129
+ end
130
+
131
+ # @param [String] node_name XML Element name
132
+ # @yield [node, ns] block to populate node
133
+ # @return [XML::Node] new node in :notification namespace
134
+ # @see new_node
135
+ def notification(node_name, &block)
136
+ new_node(node_name, :n, &block)
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,83 @@
1
+ module NominetEPP
2
+ # Helper methods
3
+ module Helpers
4
+ # @param [String, Array] nameservers Nameserver host name or array of host names or hashes
5
+ # @param [XML::Namespace] ns XML Namespace to create the elements with
6
+ # @return [XML::Node] +ns+ element of +host+ elements
7
+ # @see domain_host_xml
8
+ def domain_ns_xml(nameservers, ns)
9
+ ns_el = XML::Node.new('ns', nil, ns)
10
+
11
+ case nameservers.class
12
+ when String
13
+ ns_el << (XML::Node.new('host', nil, ns) << XML::Node.new('hostName', nameservers, ns))
14
+ when Array
15
+ nameservers.each do |nameserver|
16
+ ns_el << domain_host_xml(nameserver, ns)
17
+ end
18
+ else
19
+ raise ArgumentError, "nameservers must either be a string or array of strings"
20
+ end
21
+
22
+ ns_el
23
+ end
24
+
25
+ # @param [String, Hash] nameserver Nameserver host name or hash of +:name+ and +:v4+ or +:v6+ address
26
+ # @param [XML::Namespace] ns XML Namespace to create the elements with
27
+ # @return [XML::Node] +host+ element with +hostName+ and optionally +ip+ subelements
28
+ def domain_host_xml(nameserver, ns)
29
+ host = XML::Node.new('host', nil, ns)
30
+
31
+ case nameserver.class
32
+ when String
33
+ host << XML::Node.new('hostName', nameserver, ns)
34
+ when Hash
35
+ host << XML::Node.new('hostName', nameserver[:name], ns)
36
+ if nameserver[:v4]
37
+ n = XML::Node.new('hostAddr', nameserver[:v4], ns)
38
+ n['ip'] = 'v4'
39
+ host << n
40
+ end
41
+ if nameserver[:v6]
42
+ n = XML::Node.new('hostAddr', nameserver[:v6], ns)
43
+ n['ip'] = 'v6'
44
+ host << n
45
+ end
46
+ end
47
+
48
+ host
49
+ end
50
+
51
+ # Adds the attributes from the fields to the +node+.
52
+ #
53
+ # @param [Hash] fields Account attributes to marshal
54
+ # @param [XML::Node] node XML Node to add the attributes to
55
+ # @param [XML::Namespace] ns XML Namespace to create the elements under
56
+ def account_fields_xml(fields, node, ns)
57
+ fields.each do |k,v|
58
+ case k
59
+ when :contacts
60
+ v.each do |c|
61
+ node << (c_node = XML::Node.new('contact', nil, ns))
62
+ c_node['order'] = c.delete(:order)
63
+ c_node['type'] = 'admin'
64
+
65
+ c_node << contact(c.delete(:action) || 'create') do |cn, cns|
66
+ c.each do |l,w|
67
+ cn << XML::Node.new(l, w, cns)
68
+ end
69
+ end
70
+ end
71
+ when :addr # Limitation, can only handle one addr at a time
72
+ node << (a_node = XML::Node.new('addr', nil, ns))
73
+ a_node['type'] = 'admin'
74
+ v.each do |l,w|
75
+ a_node << XML::Node.new(l, w, ns)
76
+ end
77
+ else
78
+ node << XML::Node.new(k.to_s.gsub('_', '-'), v, ns)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,20 @@
1
+ module NominetEPP
2
+ # Module to enclose the Operations
3
+ module Operations
4
+ autoload :Check, File.dirname(__FILE__) + '/operations/check.rb'
5
+ autoload :Create, File.dirname(__FILE__) + '/operations/create.rb'
6
+ autoload :Delete, File.dirname(__FILE__) + '/operations/delete.rb'
7
+ autoload :Fork, File.dirname(__FILE__) + '/operations/fork.rb'
8
+ autoload :Hello, File.dirname(__FILE__) + '/operations/hello.rb'
9
+ autoload :Info, File.dirname(__FILE__) + '/operations/info.rb'
10
+ autoload :List, File.dirname(__FILE__) + '/operations/list.rb'
11
+ autoload :Lock, File.dirname(__FILE__) + '/operations/lock.rb'
12
+ autoload :Merge, File.dirname(__FILE__) + '/operations/merge.rb'
13
+ autoload :Poll, File.dirname(__FILE__) + '/operations/poll.rb'
14
+ autoload :Renew, File.dirname(__FILE__) + '/operations/renew.rb'
15
+ autoload :Transfer, File.dirname(__FILE__) + '/operations/transfer.rb'
16
+ autoload :Unlock, File.dirname(__FILE__) + '/operations/unlock.rb'
17
+ autoload :Unrenew, File.dirname(__FILE__) + '/operations/unrenew.rb'
18
+ autoload :Update, File.dirname(__FILE__) + '/operations/update.rb'
19
+ end
20
+ end
@@ -0,0 +1,39 @@
1
+ module NominetEPP
2
+ module Operations
3
+ # EPP Check Operation
4
+ module Check
5
+ # Check the availablity of one or more domain names
6
+ #
7
+ # @param [String, ...] *names List of names to check
8
+ # @return [false] request failed
9
+ # @return [true] the domain name is available
10
+ # @return [Hash<String,Boolean>] availability by domain name
11
+ def check(*names)
12
+ resp = @client.check do
13
+ domain('check') do |node, ns|
14
+ names.each do |name|
15
+ node << XML::Node.new('name', name, ns)
16
+ end
17
+ end
18
+ end
19
+
20
+ return false unless resp.success?
21
+
22
+ @check_limit = check_abuse_limit(resp.data)
23
+ results = resp.data.find('//domain:name', namespaces)
24
+ if results.size > 1
25
+ hash = {}
26
+ results.each {|node| hash[node.content.strip] = (node['avail'] == '1') }
27
+ hash
28
+ else
29
+ results.first['avail'] == '1'
30
+ end
31
+ end
32
+
33
+ protected
34
+ def check_abuse_limit(data)
35
+ data.find('//domain:chkData/@abuse-limit', namespaces).first.value.to_i
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,166 @@
1
+ module NominetEPP
2
+ module Operations
3
+ # EPP Create Operation
4
+ module Create
5
+ # Register a domain name with Nominet.
6
+ #
7
+ # The returned hash has the following keys
8
+ # - (String) +:name+ -- Domain name registered
9
+ # - (Time) +:crDate+ -- Domain creation date
10
+ # - (Time) +:exDate+ -- Domain expiration date
11
+ # - (Hash) +:account+ -- Created account data (if returned)
12
+ #
13
+ # The +:account+ hash contains the following keys
14
+ # - (String) +:roid+ -- Account ID
15
+ # - (String) +:name+ -- Account Name
16
+ # - (Array) +:contacts+ -- Account contacts
17
+ #
18
+ # The +:contacts+ array contains hashes of the following keys
19
+ # - (String) +:roid+ -- Contact ID
20
+ # - (String) +:name+ -- Contact Name
21
+ # - (String) +:type+ -- Contact type if set
22
+ # - (Integer) +:order+ -- Contacts order in the list of contacts
23
+ #
24
+ # @param [String] name Domain name to register
25
+ # @param [String] acct
26
+ # @param [Array] nameservers Nameservers to set for the domain
27
+ # @param [Hash] options Registration options
28
+ # @option options [String] :period
29
+ # @option options [String] :first_bill
30
+ # @option options [String] :recur_bill
31
+ # @option options [String] :auto_bill
32
+ # @option options [String] :next_bill
33
+ # @option options [String] :notes
34
+ # @raise [ArgumentError] invalid option keys
35
+ # @return [false] registration failed
36
+ # @return [Hash] Domain creation data
37
+ def create(name, acct, nameservers, options = {})
38
+ raise ArgumentError, "options allowed keys are period, first_bill, recur_bill, auto_bill, next_bill, notes" unless (options.keys - [:period, :first_bill, :recur_bill, :auto_bill, :next_bill, :notes]).empty?
39
+
40
+ resp = @client.create do
41
+ domain('create') do |node, ns|
42
+ node << XML::Node.new('name', name, ns)
43
+
44
+ options[:period] && node << XML::Node.new('period', options.delete(:period), ns)
45
+
46
+ acct_xml = XML::Node.new('account', nil, ns)
47
+ acct_xml << create_account(acct, ns)
48
+ node << acct_xml
49
+
50
+ node << domain_ns_xml(nameservers, ns)
51
+
52
+ options.each do |key, value|
53
+ node << XML::Node.new(key.to_s.gsub('_', '-'), value, ns)
54
+ end
55
+ end
56
+ end
57
+
58
+ return false unless resp.success?
59
+
60
+ creData = resp.data.find('/domain:creData', namespaces).first
61
+ h = { :name => node_value(creData, 'domain:name'),
62
+ :crDate => Time.parse(node_value(creData, 'domain:crDate')),
63
+ :exDate => Time.parse(node_value(creData, 'domain:exDate')) }
64
+ unless creData.find('domain:account').first.nil?
65
+ h[:account] = created_account(creData.find('domain:account/account:creData', namespaces).first)
66
+ end
67
+ h
68
+ end
69
+
70
+ private
71
+ # Create the account payload
72
+ #
73
+ # @param [String, Hash] acct Account ID or New account fields
74
+ # @param [XML::Namespace] domain_ns +:domain+ namespace
75
+ # @raise [ArgumentError] acct must be a String or a Hash of account fields
76
+ # @return [XML::Node]
77
+ def create_account(acct, domain_ns)
78
+ if acct.kind_of?(String)
79
+ XML::Node.new('account-id', acct, domain_ns)
80
+ elsif acct.kind_of?(Hash)
81
+ account('create') do |node, ns|
82
+ node << XML::Node.new('name', acct[:name], ns)
83
+ node << XML::Node.new('trad-name', acct[:trad_name], ns)
84
+ node << XML::Node.new('type', acct[:type], ns)
85
+ node << XML::Node.new('opt-out', acct[:opt_out], ns)
86
+
87
+ acct[:contacts].each_with_index do |cont, i|
88
+ c = XML::Node.new('contact', nil, ns)
89
+ c['order'] = i
90
+ node << (c << create_account_contact(cont))
91
+ end
92
+
93
+ node << create_account_address(acct[:addr], ns)
94
+ end
95
+ else
96
+ raise ArgumentError, "acct must be String or Hash"
97
+ end
98
+ end
99
+
100
+ # Create account contact payload
101
+ #
102
+ # @param [Hash] cont Contact fields
103
+ # @raise [ArgumentError] invalid contact fields
104
+ # @raise [ArgumentError] name or email key is missing
105
+ # @return [XML::Node]
106
+ def create_account_contact(cont)
107
+ raise ArgumentError, "Contact allowed keys are name, email, phone and mobile" unless (cont.keys -[:name, :email, :phone, :mobile]).empty?
108
+ raise ArgumentError, "Contact requires name and email keys" unless cont.has_key?(:name) && cont.has_key?(:email)
109
+
110
+ contact('create') do |node, ns|
111
+ cont.each do |key, value|
112
+ node << XML::Node.new(key, value, ns)
113
+ end
114
+ end
115
+ end
116
+
117
+ # Create contact address
118
+ #
119
+ # @param [Hash] addr Address fields
120
+ # @raise [ArgumentError] invalid keys in addr
121
+ # @return [XML::Node]
122
+ def create_account_address(addr, ns)
123
+ raise ArgumentError, "Address allowed keys are street, locality, city, county, postcode, country" unless (addr.keys - [:street, :locality, :city, :county, :postcode, :country]).empty?
124
+
125
+ addr = XML::Node.new('addr', nil, ns)
126
+ addr.each do |key, value|
127
+ addr << XML::Node.new(key, value, ns)
128
+ end
129
+
130
+ addr
131
+ end
132
+
133
+ # Collects created account information
134
+ #
135
+ # @param [XML::Node] creData XML
136
+ # @return [Hash]
137
+ def created_account(creData)
138
+ { :roid => node_value(creData, 'account:roid'),
139
+ :name => node_value(creData, 'account:name'),
140
+ :contacts => created_contacts(creData.find(
141
+ 'account:contact', namespaces)) }
142
+ end
143
+
144
+ # Collects created account contacts
145
+ #
146
+ # @param [XML::Node] account_contacts Account contacts
147
+ # @return [Hash]
148
+ def created_contacts(account_contacts)
149
+ account_contacts.map do |cont|
150
+ { :type => cont['type'],
151
+ :order => cont['order'] }.merge(
152
+ created_contact(cont.find('contact:creData', namespaces).first))
153
+ end
154
+ end
155
+
156
+ # Collects create contact information
157
+ #
158
+ # @param [XML::Node] creData XML
159
+ # @return [Hash]
160
+ def created_contact(creData)
161
+ { :roid => node_value(creData, 'contact:roid'),
162
+ :name => node_value(creData, 'contact:name') }
163
+ end
164
+ end
165
+ end
166
+ end