class Transip::Client

Constants

API_SERVICE
API_VERSION

Attributes

debug[RW]
hash[RW]
ip[RW]
mode[RW]
password[RW]
response[R]
username[RW]

Public Class Methods

new(options = {}) click to toggle source

Options:

  • username - Your login name on the TransIP website.

  • ip - needed in production

  • key / key_file - key is one of your private keys (these can be requested via your Controlpanel). key_file is path to file containing key.

  • mode - :readonly, :readwrite

  • proxy - url of proxy through which you want to route API requests. For example, if you use Quataguard Static on Heroku, use ENV. If not used, leave blank or don't supply it as a parameter.

Example:

transip = Transip.new(:username => 'api_username', :ip => '12.34.12.3', :key => mykey, :mode => 'readwrite', :proxy => '') # use this in production
# File lib/transip/client.rb, line 35
def initialize(options = {})
  @key = options[:key] || (options[:key_file] && File.read(options[:key_file]))
  @username = options[:username]
  @ip = options[:ip]
  @proxy = options[:proxy]
  @api_version = options[:api_version]
  @api_service = options[:api_service]
  raise ArgumentError, "The :username, :ip and :key options are required!" if @username.nil? or @key.nil?

  @mode = options[:mode] || :readonly
  @endpoint = options[:endpoint] || 'api.transip.nl'
  if options[:password]
    @password = options[:password]
  end

 @savon_options = {
    :wsdl => wsdl
  }
  # if proxy is present, use it
  if @proxy != nil
          @savon_options[:proxy] = @proxy
      end
  # By default we don't want to debug!
   self.turn_off_debugging!
end

Public Instance Methods

actions() click to toggle source

Returns Array with all possible SOAP WSDL actions.

# File lib/transip/client.rb, line 202
def actions
  client.operations
end
api_service() click to toggle source
# File lib/transip/client.rb, line 16
def api_service
  @api_service || self.class::API_SERVICE
end
api_version() click to toggle source
# File lib/transip/client.rb, line 11
def api_version
  # We use self.class:: here to not use parentclass constant.
  @api_version || self.class::API_VERSION
end
client() click to toggle source

Returns a Savon::Client object to be used in the connection. This object is re-used and cached as @client.

# File lib/transip/client.rb, line 197
def client
  @client ||= client!
end
client!() click to toggle source

Same as client method but initializes a brand new fresh client. You have to use this one when you want to re-set the mode (readwrite, readonly), or authentication details of your client.

# File lib/transip/client.rb, line 186
def client!
  @client = Savon::Client.new(@savon_options) do
    namespaces(
      "xmlns:enc" => "http://schemas.xmlsoap.org/soap/encoding/"
    )
  end
  return @client
end
convert_array_to_hash(array) click to toggle source

yes, i know, it smells bad

# File lib/transip/client.rb, line 77
def convert_array_to_hash(array)
  result = {}
  array.each_with_index do |value, index|
    result[index] = value
  end
  result
end
cookies(method, parameters) click to toggle source

Used for authentication

# File lib/transip/client.rb, line 167
def cookies(method, parameters)
  time = Time.new.to_i
  #strip out the -'s because transip requires the nonce to be between 6 and 32 chars
  nonce = SecureRandom.uuid.gsub("-", '')
  result = to_cookies [ "login=#{self.username}",
               "mode=#{self.mode}",
               "timestamp=#{time}",
               "nonce=#{nonce}",
               "clientVersion=#{api_version}",
               "signature=#{signature(method, parameters, time, nonce)}"

             ]
  debug_log("signature:\n#{signature(method, parameters, time, nonce)}")
  result
end
fix_array_definitions(options) click to toggle source

This makes sure that arrays are properly encoded as soap-arrays by Gyoku

# File lib/transip/client.rb, line 207
def fix_array_definitions(options)
  result = {}
  options.each do |key, value|
    if value.is_a?(Array) and (value.size > 0)
      entry_name = value.first.class.name.split(":").last
      result[key] = {
        'item' => {:content! => value, :'@xsi:type' => "tns:#{entry_name}"},
        :'@xsi:type' => "tns:ArrayOf#{entry_name}",
        :'@enc:arrayType' => "tns:#{entry_name}[#{value.size}]"
      }
    elsif value.is_a?(Hash)
      result[key] = fix_array_definitions(value)
    else
      result[key] = value
    end
  end
  result
end
process_response(response) click to toggle source

converts the savon response object to something we can return to the caller

# File lib/transip/client.rb, line 230
def process_response(response)
  response = response.to_hash.values.first[:return] rescue nil
  TransipStruct.from_soap(response)
end
request(action, options = nil) click to toggle source

This is the main request function throws ApiError returns response object (can be TransipStruct or Array of TransipStruct)

# File lib/transip/client.rb, line 238
def request(action, options = nil)
  formatted_action = action.to_s.camelize(:lower)
  parameters = {
    # for some reason, the transip server wants the body root tag to be
    # the name of the action.
    :message_tag => formatted_action
  }
  options = options.to_hash if options.is_a?(Transip::TransipStruct)

  if options.is_a?(Hash)
    xml_options = fix_array_definitions(options)
  elsif options.nil?
    xml_options = nil
  else
    raise "Invalid parameter format (should be nil, hash or TransipStruct)"
  end
  parameters[:message] = xml_options
  parameters[:cookies] = cookies(action, options)
  debug_log("parameters:\n#{parameters.inspect}")
  response = client.call(action, parameters)

  process_response(response)
rescue Savon::SOAPFault => e
  raise ApiError.new(e), e.message.sub(/^\(\d+\)\s+/,'') # We raise our own error (FIXME: Correct?).
end
serialize_parameters(parameters, key_prefix=nil) click to toggle source
# File lib/transip/client.rb, line 93
def serialize_parameters(parameters, key_prefix=nil)
  debug_log("serialize_parameters(#{parameters.inspect}, #{key_prefix.inspect}")

  parameters = parameters.to_hash.values.first if parameters.is_a? TransipStruct
  parameters = convert_array_to_hash(parameters) if parameters.is_a? Array
  if not parameters.is_a? Hash
    return urlencode(parameters)
  end
  return "#{key_prefix}=" if parameters.empty?

  encoded_parameters = []
  parameters.each do |key, value|
    next if key.to_s == '@xsi:type'
    encoded_key = (key_prefix.nil?) ? urlencode(key) : "#{key_prefix}[#{urlencode(key)}]"
    if value.is_a?(Hash) or value.is_a?(Array) or value.is_a?(TransipStruct)
      encoded_parameters << serialize_parameters(value, encoded_key)
    else
      encoded_value = urlencode(value)
      encoded_parameters << "#{encoded_key}=#{encoded_value}"
    end
  end

  encoded_parameters = encoded_parameters.join("&")
  debug_log("encoded_parameters:\n#{encoded_parameters.split('&').join("\n")}")
  encoded_parameters
end
signature(method, parameters, time, nonce) click to toggle source

does all the techy stuff to calculate transip's sick authentication scheme: a hash with all the request information is subsequently: serialized like a www form SHA512 digested asn1 header added private key encrypted Base64 encoded URL encoded I think the guys at transip were trying to use their entire crypto-toolbox!

# File lib/transip/client.rb, line 129
def signature(method, parameters, time, nonce)
  formatted_method = method.to_s.camelize(:lower)
  parameters ||= {}
  input = convert_array_to_hash(parameters.values)
  options = {
    '__method' => formatted_method,
    '__service' => api_service,
    '__hostname' => @endpoint,
    '__timestamp' => time,
    '__nonce' => nonce

  }
  input.merge!(options)
  raise "Invalid RSA key" unless @key =~ /-----BEGIN (RSA )?PRIVATE KEY-----(.*)-----END (RSA )?PRIVATE KEY-----/sim
  serialized_input = serialize_parameters(input)

  digest = Digest::SHA512.new.digest(serialized_input)
  asn_header = "\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40"

  # convert asn_header literal to ASCII-8BIT
  if RUBY_VERSION.split('.')[0] == "2"
    asn = asn_header.b + digest
  else
    asn = asn_header + digest
  end
  private_key = OpenSSL::PKey::RSA.new(@key)
  encrypted_asn = private_key.private_encrypt(asn)
  readable_encrypted_asn = Base64.encode64(encrypted_asn)
  urlencode(readable_encrypted_asn)
end
to_cookies(content) click to toggle source
# File lib/transip/client.rb, line 160
def to_cookies(content)
  content.map do |item|
    HTTPI::Cookie.new item
  end
end
turn_off_debugging!() click to toggle source

By default we don't want to debug! Changing might impact other Savon usages.

# File lib/transip/client.rb, line 63
def turn_off_debugging!
    @savon_options[:log] = false            # disable logging
    @savon_options[:log_level] = :info      # changing the log level
end
urlencode(input) click to toggle source
# File lib/transip/client.rb, line 85
def urlencode(input)
  output = URI.encode_www_form_component(input)
  output.gsub!('+', '%20')
  output.gsub!('%7E', '~')
  output.gsub!('*', '%2A')
  output
end
use_with_rails!() click to toggle source

Make Savon log to Rails.logger and turn_off_debugging!

# File lib/transip/client.rb, line 69
def use_with_rails!
  if Rails.env.production?
    self.turn_off_debugging!
  end
  @savon_options[:logger] = Rails.logger  # using the Rails logger
end
wsdl() click to toggle source
# File lib/transip/client.rb, line 20
def wsdl
  "https://api.transip.nl/wsdl/?service=#{api_service}"
end

Private Instance Methods

debug_log(msg) click to toggle source
# File lib/transip/client.rb, line 266
def debug_log(msg)
  puts msg if @debug
end