Skip to content

Commit

Permalink
Version 0.2.0 now can do everything better.
Browse files Browse the repository at this point in the history
  • Loading branch information
Joost Hietbrink committed May 26, 2011
1 parent ec5d728 commit a409a29
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 22 deletions.
13 changes: 9 additions & 4 deletions README.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,16 @@ For the latest version: Download / clone the repository. Bundle install the need
For the most up-to-date documentation see the source files. Use as follows:

require 'transip'
transip = Transip.new('username', '12.34.12.3')
transip.generate_hash('your_api_password')
transip.actions # => [:check_availability, .., :set_contacts]
transip = Transip.new(:username => 'api_username') # will try to determine IP (will not work behind NAT) and uses readonly mode
transip = Transip.new(:username => 'api_username', :ip => '12.34.12.3', :mode => 'readwrite') # use this in production
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]
transip.request(:get_domain_names)
transip.request(:get_info, :domain_name => 'domain.com')
transip.request(:get_info, :domain_name => 'yelloyello.be')
transip.request_with_ip4_fix(:check_availability, :domain_name => 'yelloyello.be') # Will fix your IP if not correct (voodoo magic)
transip.request_with_ip4_fix(:get_info, :domain_name => 'one_of_your_domains.com')
transip.request(:get_whois, :domain_name => 'google.com')
transip.request(:set_dns_entries, :domain_name => 'bdgg.nl', :dns_entries => [Transip::DnsEntry.new('test', 5.minutes, 'A', '74.125.77.147')])
transip.request(:register, Transip::Domain.new('newdomain.com', nil, nil, [Transip::DnsEntry.new('test', 5.minutes, 'A', '74.125.77.147')]))

== TODO

Expand Down
2 changes: 1 addition & 1 deletion VERSION.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
---
:major: 0
:minor: 1
:minor: 2
:patch: 0
231 changes: 215 additions & 16 deletions lib/transip.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,47 +8,204 @@
# Implements the www.transip.nl API (v2). For more info see: https://www.transip.nl/g/api/
#
# Usage:
# transip = Transip.new('username', '12.34.12.3') # will use readonly mode
# transip = Transip.new('username', '12.34.12.3', :readwrite) # use this in production
# transip.generate_hash('your_api_password') # Use this to generate a authentication hash
# transip.hash = 'your_hash' # Or use this to directly set the hash (so you don't have to use your password in your code)
# transip = Transip.new(:username => 'api_username') # will try to determine IP (will not work behind NAT) and uses readonly mode
# transip = Transip.new(:username => 'api_username', :ip => '12.34.12.3', :mode => 'readwrite') # use this in production
# 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]
# transip.request(:get_domain_names)
# transip.request(:get_info, :domain_name => 'yelloyello.be')
# transip.request_with_ip4_fix(:check_availability, :domain_name => 'yelloyello.be')
# transip.request_with_ip4_fix(:get_info, :domain_name => 'one_of_your_domains.com')
# transip.request(:get_whois, :domain_name => 'google.com')
# transip.request(:set_dns_entries, :domain_name => 'bdgg.nl', :dns_entries => [Transip::DnsEntry.new('test', 5.minutes, 'A', '74.125.77.147')])
# transip.request(:register, Transip::Domain.new('newdomain.com', nil, nil, [Transip::DnsEntry.new('test', 5.minutes, 'A', '74.125.77.147')]))
#
# Some other methods:
# transip.generate_hash # Use this to generate a authentication hash
# transip.hash = 'your_hash' # Or use this to directly set the hash (so you don't have to use your password in your code)
# 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!
#
# Credits:
# Savon Gem - See: http://savonrb.com/. Wouldn't be so simple without it!
class Transip

WSDL = 'https://api.transip.nl/wsdl/?service=DomainService'

attr_accessor :login, :ip, :mode, :hash
attr_accessor :username, :password, :ip, :mode, :hash
attr_reader :response

# Following Error needs to be catched in your code!
class ApiError < RuntimeError

IP4_REGEXP = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/

# Returns true if we have a authentication error and gets ip from error msg.
# "Wrong API credentials (bad hash); called from IP 213.86.41.114"
def ip4_authentication_error?
self.message.to_s =~ /called from IP\s(#{IP4_REGEXP})/ # "Wrong API credentials (bad hash); called from IP 213.86.41.114"
@error_msg_ip = $1
!@error_msg_ip.nil?
end

# Returns the ip coming from the error msg.
def error_msg_ip
@error_msg_ip || ip4_authentication_error? && @error_msg_ip
end

end

# Following subclasses are actually not needed (as you can also
# do the same by just creating hashes..).

class TransipStruct < Struct

# See Rails' underscore method.
def underscore(string)
string.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
gsub(/([a-z\d])([A-Z])/,'\1_\2').
tr("-", "_").
downcase
end

# Converts Transip::DnsEntry into :dns_entry
def class_name_to_sym
self.underscore(self.class.name.split('::').last).to_sym
end

# Gyoku.xml (see: https://github.com/rubiii/gyoku) is used by Savon.
# It calls to_s on unknown Objects. We use it to convert
def to_s
Gyoku.xml(self.to_hash)
end

# See what happens here: http://snippets.dzone.com/posts/show/302
def members_to_hash
Hash[*members.collect {|m| [m, self.send(m)]}.flatten]
end

def to_hash
{ self.class_name_to_sym => self.members_to_hash }
end

end

# name - String (Eg. '@' or 'www')
# expire - Integer (1.day)
# type - String (Eg. A, AAAA, CNAME, MX, NS, TXT, SRV)
# content - String (Eg. '10 mail', '127.0.0.1' or 'www')
class DnsEntry < TransipStruct.new(:name, :expire, :type, :content)
end

# hostname - string
# ipv4 - string
# ipv6 - string (optional)
class Nameserver < TransipStruct.new(:name, :ipv4, :ipv6)
end

# type - string
# first_name - string
# middle_name - string
# last_name - string
# company_name - string
# company_kvk - string
# company_type - string ('BV', 'BVI/O', 'COOP', 'CV'..) (see WhoisContact.php)
# street - string
# number - string (streetnumber)
# postal_code - string
# city - string
# phone_number - string
# fax_number - string
# email - string
# country - string (one of the ISO country abbrevs, must be lowercase) ('nl', 'de', ) (see WhoisContact.php)
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)
end

# company_name - string
# support_email - string
# company_url - string
# terms_of_usage_url - string
# banner_line1 - string
# banner_line2 - string
# banner_line3 - string
class DomainBranding < TransipStruct.new(:company_name, :support_email, :company_url, :terms_of_usage_url, :banner_line1, :banner_line2, :banner_line3)
end

# name - String
# nameservers - Array of Transip::Nameserver
# contacts - Array of Transip::WhoisContact
# dns_entries - Array of Transip::DnsEntry
# branding - Transip::DomainBranding
class Domain < TransipStruct.new(:name, :nameservers, :contacts, :dns_entries, :branding)
end

# Options:
# * username
# * ip
# * password
# * mode
#
# Example:
# transip = Transip.new('username', '12.34.12.3') # will use readonly mode
# transip = Transip.new('username', '12.34.12.3', 'readwrite') # use this in production
def initialize(login, ip, mode = :readonly)
@login = login
@ip = ip
@mode = mode
# transip = Transip.new(:username => 'api_username') # will try to determine IP (will not work behind NAT) and uses readonly mode
# transip = Transip.new(:username => 'api_username', :ip => '12.34.12.3', :mode => 'readwrite') # use this in production
def initialize(options = {})
@username = options[:username]
raise ArgumentError, "The :username options is required!" if @username.nil?
@ip = options[:ip] || self.class.local_ip
@mode = options[:mode] || :readonly
if options[:password]
@password = options[:password]
self.generate_hash
end

# By default we don't want to debug!
self.turn_off_debugging!
end

# By default we don't want to debug!
# Changing might impact other Savon usages.
def turn_off_debugging!
Savon.configure do |config|
config.log = false # disable logging
config.log_level = :info # changing the log level
end
end

# Make Savon log.
# Changing might impact other Savon usages.
def turn_on_debugging!
Savon.configure do |config|
config.log = true
config.log_level = :debug
end
end

# Make Savon log to Rails.logger and turn_off_debugging!
def use_with_rails!
Savon.configure do |config|
if Rails.env.production?
self.turn_off_debugging!
# else
# self.turn_on_debugging!
end
config.logger = Rails.logger # using the Rails logger
end
end

# Generates the needed authentication hash.
#
# NOTE: The password is NOT your general TransIP password
# but one specially for the API. Configure it in the Control
# Panel.
def generate_hash(password)
digest_string = "#{login}:#{password}@#{ip}"
def generate_hash
raise StandardError, "Need username and password to (re)generate the authentication hash." if self.username.nil? || self.password.nil?
digest_string = "#{self.username}:#{self.password}@#{self.ip}"
digest = Digest::MD5.hexdigest(digest_string)
@hash = digest
self.hash = digest
end

# Used as authentication
def cookie
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?
"login=#{login}; hash=#{hash}; mode=#{mode}; "
raise StandardError, "Don't have an authentication hash yet. Please set a hash using generate_hash or hash= method." if hash.blank?
"login=#{self.username}; hash=#{self.hash}; mode=#{self.mode}; "
end

# Same as client method but initializes a brand new fresh client.
Expand Down Expand Up @@ -86,10 +243,52 @@ def request(action, options = nil)
@response = client.request(action) do
soap.body = options
end
elsif options.class < Transip::TransipStruct
# If we call request(:register, Transip::Domain.new('newdomain.com')) we turn the Transip::Domain into a Hash.
@response = client.request(action) do
soap.body = options.to_hash
end
else
raise ArgumentError, "Expected options to be nil or a Hash!"
end
@response.to_hash
rescue Savon::SOAP::Fault => e
raise ApiError.new(e), e.message.sub(/^\(\d+\)\s+/,'') # We raise our own error (FIXME: Correct?).
# TODO: Curl::Err::HostResolutionError, Couldn't resolve host name
end

# This is voodoo. Use it only if you know voodoo kung-fu.
#
# The method fixes the ip that is set. It uses the error from
# Transip to set the ip and re-request an authentication hash.
#
# It only works if you set password (via the password= method)!
def request_with_ip4_fix(*args)
self.request(*args)
rescue ApiError => e
if e.ip4_authentication_error?
if !(@ip == e.error_msg_ip) # If not the same IP we try it with this IP..
self.ip = e.error_msg_ip
self.generate_hash # Generate a new authentication hash.
self.client! # Update the client with the new authentication hash in the cookie!
return self.request(*args)
end
end
raise # If we haven't returned anything.. we raise the ApiError again.
end

private

# Find my local_ip..
def self.local_ip
orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true # turn off reverse DNS resolution temporarily

UDPSocket.open do |s|
s.connect('74.125.77.147', 1) # Connects to a Google IP '74.125.77.147'.
s.addr.last
end
ensure
Socket.do_not_reverse_lookup = orig
end

end
2 changes: 1 addition & 1 deletion transip.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Gem::Specification.new do |s|
s.name = %q{transip}
s.version = "0.1.0"
s.version = "0.2.0"

s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Joost Hietbrink"]
Expand Down

0 comments on commit a409a29

Please sign in to comment.