transip 0.1.0 → 0.2.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.
Files changed (4) hide show
  1. data/README.rdoc +15 -6
  2. data/VERSION.yml +1 -1
  3. data/lib/transip.rb +215 -16
  4. metadata +1 -1
data/README.rdoc CHANGED
@@ -13,8 +13,11 @@ Credits:
13
13
 
14
14
  == Install
15
15
 
16
- Have to turn into a gem..
17
- Download / clone the repository. Bundle install the needed gems and require the lib.
16
+ Use the gem.
17
+
18
+ gem install transip
19
+
20
+ For the latest version: Download / clone the repository. Bundle install the needed gems and require the lib.
18
21
 
19
22
  git clone git://github.com/joost/transip-api.git
20
23
  bundle install
@@ -24,11 +27,17 @@ Download / clone the repository. Bundle install the needed gems and require the
24
27
 
25
28
  For the most up-to-date documentation see the source files. Use as follows:
26
29
 
27
- transip = Transip.new('username', '12.34.12.3')
28
- transip.generate_hash('your_api_password')
29
- transip.actions # => [:check_availability, .., :set_contacts]
30
+ require 'transip'
31
+ transip = Transip.new(:username => 'api_username') # will try to determine IP (will not work behind NAT) and uses readonly mode
32
+ transip = Transip.new(:username => 'api_username', :ip => '12.34.12.3', :mode => 'readwrite') # use this in production
33
+ transip.actions # => [:check_availability, :get_whois, :get_domain_names, :get_info, :get_auth_code, :get_is_locked, :register, :cancel, :transfer_with_owner_change, :transfer_without_owner_change, :set_nameservers, :set_lock, :unset_lock, :set_dns_entries, :set_owner, :set_contacts]
30
34
  transip.request(:get_domain_names)
31
- transip.request(:get_info, :domain_name => 'domain.com')
35
+ transip.request(:get_info, :domain_name => 'yelloyello.be')
36
+ transip.request_with_ip4_fix(:check_availability, :domain_name => 'yelloyello.be') # Will fix your IP if not correct (voodoo magic)
37
+ transip.request_with_ip4_fix(:get_info, :domain_name => 'one_of_your_domains.com')
38
+ transip.request(:get_whois, :domain_name => 'google.com')
39
+ transip.request(:set_dns_entries, :domain_name => 'bdgg.nl', :dns_entries => [Transip::DnsEntry.new('test', 5.minutes, 'A', '74.125.77.147')])
40
+ transip.request(:register, Transip::Domain.new('newdomain.com', nil, nil, [Transip::DnsEntry.new('test', 5.minutes, 'A', '74.125.77.147')]))
32
41
 
33
42
  == TODO
34
43
 
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 0
3
- :minor: 1
3
+ :minor: 2
4
4
  :patch: 0
data/lib/transip.rb CHANGED
@@ -8,13 +8,21 @@ require 'digest/md5'
8
8
  # Implements the www.transip.nl API (v2). For more info see: https://www.transip.nl/g/api/
9
9
  #
10
10
  # Usage:
11
- # transip = Transip.new('username', '12.34.12.3') # will use readonly mode
12
- # transip = Transip.new('username', '12.34.12.3', :readwrite) # use this in production
13
- # transip.generate_hash('your_api_password') # Use this to generate a authentication hash
14
- # transip.hash = 'your_hash' # Or use this to directly set the hash (so you don't have to use your password in your code)
11
+ # transip = Transip.new(:username => 'api_username') # will try to determine IP (will not work behind NAT) and uses readonly mode
12
+ # transip = Transip.new(:username => 'api_username', :ip => '12.34.12.3', :mode => 'readwrite') # use this in production
15
13
  # transip.actions # => [:check_availability, :get_whois, :get_domain_names, :get_info, :get_auth_code, :get_is_locked, :register, :cancel, :transfer_with_owner_change, :transfer_without_owner_change, :set_nameservers, :set_lock, :unset_lock, :set_dns_entries, :set_owner, :set_contacts]
16
14
  # transip.request(:get_domain_names)
17
15
  # transip.request(:get_info, :domain_name => 'yelloyello.be')
16
+ # transip.request_with_ip4_fix(:check_availability, :domain_name => 'yelloyello.be')
17
+ # transip.request_with_ip4_fix(:get_info, :domain_name => 'one_of_your_domains.com')
18
+ # transip.request(:get_whois, :domain_name => 'google.com')
19
+ # transip.request(:set_dns_entries, :domain_name => 'bdgg.nl', :dns_entries => [Transip::DnsEntry.new('test', 5.minutes, 'A', '74.125.77.147')])
20
+ # transip.request(:register, Transip::Domain.new('newdomain.com', nil, nil, [Transip::DnsEntry.new('test', 5.minutes, 'A', '74.125.77.147')]))
21
+ #
22
+ # Some other methods:
23
+ # transip.generate_hash # Use this to generate a authentication hash
24
+ # transip.hash = 'your_hash' # Or use this to directly set the hash (so you don't have to use your password in your code)
25
+ # transip.client! # This returns a new Savon::Client. It is cached in transip.client so when you update your username, password or hash call this method!
18
26
  #
19
27
  # Credits:
20
28
  # Savon Gem - See: http://savonrb.com/. Wouldn't be so simple without it!
@@ -22,16 +30,164 @@ class Transip
22
30
 
23
31
  WSDL = 'https://api.transip.nl/wsdl/?service=DomainService'
24
32
 
25
- attr_accessor :login, :ip, :mode, :hash
33
+ attr_accessor :username, :password, :ip, :mode, :hash
26
34
  attr_reader :response
27
35
 
36
+ # Following Error needs to be catched in your code!
37
+ class ApiError < RuntimeError
38
+
39
+ IP4_REGEXP = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
40
+
41
+ # Returns true if we have a authentication error and gets ip from error msg.
42
+ # "Wrong API credentials (bad hash); called from IP 213.86.41.114"
43
+ def ip4_authentication_error?
44
+ self.message.to_s =~ /called from IP\s(#{IP4_REGEXP})/ # "Wrong API credentials (bad hash); called from IP 213.86.41.114"
45
+ @error_msg_ip = $1
46
+ !@error_msg_ip.nil?
47
+ end
48
+
49
+ # Returns the ip coming from the error msg.
50
+ def error_msg_ip
51
+ @error_msg_ip || ip4_authentication_error? && @error_msg_ip
52
+ end
53
+
54
+ end
55
+
56
+ # Following subclasses are actually not needed (as you can also
57
+ # do the same by just creating hashes..).
58
+
59
+ class TransipStruct < Struct
60
+
61
+ # See Rails' underscore method.
62
+ def underscore(string)
63
+ string.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
64
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
65
+ tr("-", "_").
66
+ downcase
67
+ end
68
+
69
+ # Converts Transip::DnsEntry into :dns_entry
70
+ def class_name_to_sym
71
+ self.underscore(self.class.name.split('::').last).to_sym
72
+ end
73
+
74
+ # Gyoku.xml (see: https://github.com/rubiii/gyoku) is used by Savon.
75
+ # It calls to_s on unknown Objects. We use it to convert
76
+ def to_s
77
+ Gyoku.xml(self.to_hash)
78
+ end
79
+
80
+ # See what happens here: http://snippets.dzone.com/posts/show/302
81
+ def members_to_hash
82
+ Hash[*members.collect {|m| [m, self.send(m)]}.flatten]
83
+ end
84
+
85
+ def to_hash
86
+ { self.class_name_to_sym => self.members_to_hash }
87
+ end
88
+
89
+ end
90
+
91
+ # name - String (Eg. '@' or 'www')
92
+ # expire - Integer (1.day)
93
+ # type - String (Eg. A, AAAA, CNAME, MX, NS, TXT, SRV)
94
+ # content - String (Eg. '10 mail', '127.0.0.1' or 'www')
95
+ class DnsEntry < TransipStruct.new(:name, :expire, :type, :content)
96
+ end
97
+
98
+ # hostname - string
99
+ # ipv4 - string
100
+ # ipv6 - string (optional)
101
+ class Nameserver < TransipStruct.new(:name, :ipv4, :ipv6)
102
+ end
103
+
104
+ # type - string
105
+ # first_name - string
106
+ # middle_name - string
107
+ # last_name - string
108
+ # company_name - string
109
+ # company_kvk - string
110
+ # company_type - string ('BV', 'BVI/O', 'COOP', 'CV'..) (see WhoisContact.php)
111
+ # street - string
112
+ # number - string (streetnumber)
113
+ # postal_code - string
114
+ # city - string
115
+ # phone_number - string
116
+ # fax_number - string
117
+ # email - string
118
+ # country - string (one of the ISO country abbrevs, must be lowercase) ('nl', 'de', ) (see WhoisContact.php)
119
+ class WhoisContact < TransipStruct.new(:type, :first_name, :middle_name, :last_name, :company_name, :company_kvk, :company_type, :street, :number, :postal_code, :city, :phone_number, :fax_number, :email, :country)
120
+ end
121
+
122
+ # company_name - string
123
+ # support_email - string
124
+ # company_url - string
125
+ # terms_of_usage_url - string
126
+ # banner_line1 - string
127
+ # banner_line2 - string
128
+ # banner_line3 - string
129
+ class DomainBranding < TransipStruct.new(:company_name, :support_email, :company_url, :terms_of_usage_url, :banner_line1, :banner_line2, :banner_line3)
130
+ end
131
+
132
+ # name - String
133
+ # nameservers - Array of Transip::Nameserver
134
+ # contacts - Array of Transip::WhoisContact
135
+ # dns_entries - Array of Transip::DnsEntry
136
+ # branding - Transip::DomainBranding
137
+ class Domain < TransipStruct.new(:name, :nameservers, :contacts, :dns_entries, :branding)
138
+ end
139
+
140
+ # Options:
141
+ # * username
142
+ # * ip
143
+ # * password
144
+ # * mode
145
+ #
28
146
  # Example:
29
- # transip = Transip.new('username', '12.34.12.3') # will use readonly mode
30
- # transip = Transip.new('username', '12.34.12.3', 'readwrite') # use this in production
31
- def initialize(login, ip, mode = :readonly)
32
- @login = login
33
- @ip = ip
34
- @mode = mode
147
+ # transip = Transip.new(:username => 'api_username') # will try to determine IP (will not work behind NAT) and uses readonly mode
148
+ # transip = Transip.new(:username => 'api_username', :ip => '12.34.12.3', :mode => 'readwrite') # use this in production
149
+ def initialize(options = {})
150
+ @username = options[:username]
151
+ raise ArgumentError, "The :username options is required!" if @username.nil?
152
+ @ip = options[:ip] || self.class.local_ip
153
+ @mode = options[:mode] || :readonly
154
+ if options[:password]
155
+ @password = options[:password]
156
+ self.generate_hash
157
+ end
158
+
159
+ # By default we don't want to debug!
160
+ self.turn_off_debugging!
161
+ end
162
+
163
+ # By default we don't want to debug!
164
+ # Changing might impact other Savon usages.
165
+ def turn_off_debugging!
166
+ Savon.configure do |config|
167
+ config.log = false # disable logging
168
+ config.log_level = :info # changing the log level
169
+ end
170
+ end
171
+
172
+ # Make Savon log.
173
+ # Changing might impact other Savon usages.
174
+ def turn_on_debugging!
175
+ Savon.configure do |config|
176
+ config.log = true
177
+ config.log_level = :debug
178
+ end
179
+ end
180
+
181
+ # Make Savon log to Rails.logger and turn_off_debugging!
182
+ def use_with_rails!
183
+ Savon.configure do |config|
184
+ if Rails.env.production?
185
+ self.turn_off_debugging!
186
+ # else
187
+ # self.turn_on_debugging!
188
+ end
189
+ config.logger = Rails.logger # using the Rails logger
190
+ end
35
191
  end
36
192
 
37
193
  # Generates the needed authentication hash.
@@ -39,16 +195,17 @@ class Transip
39
195
  # NOTE: The password is NOT your general TransIP password
40
196
  # but one specially for the API. Configure it in the Control
41
197
  # Panel.
42
- def generate_hash(password)
43
- digest_string = "#{login}:#{password}@#{ip}"
198
+ def generate_hash
199
+ raise StandardError, "Need username and password to (re)generate the authentication hash." if self.username.nil? || self.password.nil?
200
+ digest_string = "#{self.username}:#{self.password}@#{self.ip}"
44
201
  digest = Digest::MD5.hexdigest(digest_string)
45
- @hash = digest
202
+ self.hash = digest
46
203
  end
47
204
 
48
205
  # Used as authentication
49
206
  def cookie
50
- raise StandardError, "Don't have an authentication hash yet. Please set a hash using generate_hash('your_api_password') or hash= method." if hash.blank?
51
- "login=#{login}; hash=#{hash}; mode=#{mode}; "
207
+ raise StandardError, "Don't have an authentication hash yet. Please set a hash using generate_hash or hash= method." if hash.blank?
208
+ "login=#{self.username}; hash=#{self.hash}; mode=#{self.mode}; "
52
209
  end
53
210
 
54
211
  # Same as client method but initializes a brand new fresh client.
@@ -86,10 +243,52 @@ class Transip
86
243
  @response = client.request(action) do
87
244
  soap.body = options
88
245
  end
246
+ elsif options.class < Transip::TransipStruct
247
+ # If we call request(:register, Transip::Domain.new('newdomain.com')) we turn the Transip::Domain into a Hash.
248
+ @response = client.request(action) do
249
+ soap.body = options.to_hash
250
+ end
89
251
  else
90
252
  raise ArgumentError, "Expected options to be nil or a Hash!"
91
253
  end
92
254
  @response.to_hash
255
+ rescue Savon::SOAP::Fault => e
256
+ raise ApiError.new(e), e.message.sub(/^\(\d+\)\s+/,'') # We raise our own error (FIXME: Correct?).
257
+ # TODO: Curl::Err::HostResolutionError, Couldn't resolve host name
258
+ end
259
+
260
+ # This is voodoo. Use it only if you know voodoo kung-fu.
261
+ #
262
+ # The method fixes the ip that is set. It uses the error from
263
+ # Transip to set the ip and re-request an authentication hash.
264
+ #
265
+ # It only works if you set password (via the password= method)!
266
+ def request_with_ip4_fix(*args)
267
+ self.request(*args)
268
+ rescue ApiError => e
269
+ if e.ip4_authentication_error?
270
+ if !(@ip == e.error_msg_ip) # If not the same IP we try it with this IP..
271
+ self.ip = e.error_msg_ip
272
+ self.generate_hash # Generate a new authentication hash.
273
+ self.client! # Update the client with the new authentication hash in the cookie!
274
+ return self.request(*args)
275
+ end
276
+ end
277
+ raise # If we haven't returned anything.. we raise the ApiError again.
278
+ end
279
+
280
+ private
281
+
282
+ # Find my local_ip..
283
+ def self.local_ip
284
+ orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true # turn off reverse DNS resolution temporarily
285
+
286
+ UDPSocket.open do |s|
287
+ s.connect('74.125.77.147', 1) # Connects to a Google IP '74.125.77.147'.
288
+ s.addr.last
289
+ end
290
+ ensure
291
+ Socket.do_not_reverse_lookup = orig
93
292
  end
94
293
 
95
294
  end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: transip
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.1.0
5
+ version: 0.2.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Joost Hietbrink