transip 0.1.0 → 0.2.0

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