Class: Transip::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/transip/client.rb

Constant Summary collapse

API_VERSION =
'5.0'
API_SERVICE =
'DomainService'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Client

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

Raises:

  • (ArgumentError)


35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# 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

Instance Attribute Details

#debugObject

Returns the value of attribute debug.



24
25
26
# File 'lib/transip/client.rb', line 24

def debug
  @debug
end

#hashObject

Returns the value of attribute hash.



8
9
10
# File 'lib/transip/client.rb', line 8

def hash
  @hash
end

#ipObject

Returns the value of attribute ip.



8
9
10
# File 'lib/transip/client.rb', line 8

def ip
  @ip
end

#modeObject

Returns the value of attribute mode.



8
9
10
# File 'lib/transip/client.rb', line 8

def mode
  @mode
end

#passwordObject

Returns the value of attribute password.



8
9
10
# File 'lib/transip/client.rb', line 8

def password
  @password
end

#responseObject (readonly)

Returns the value of attribute response.



9
10
11
# File 'lib/transip/client.rb', line 9

def response
  @response
end

#usernameObject

Returns the value of attribute username.



8
9
10
# File 'lib/transip/client.rb', line 8

def username
  @username
end

Instance Method Details

#actionsObject

Returns Array with all possible SOAP WSDL actions.



202
203
204
# File 'lib/transip/client.rb', line 202

def actions
  client.operations
end

#api_serviceObject



16
17
18
# File 'lib/transip/client.rb', line 16

def api_service
  @api_service || self.class::API_SERVICE
end

#api_versionObject



11
12
13
14
# 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

#clientObject

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



197
198
199
# File 'lib/transip/client.rb', line 197

def client
  @client ||= client!
end

#client!Object

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.



186
187
188
189
190
191
192
193
# 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) ⇒ Object

yes, i know, it smells bad



77
78
79
80
81
82
83
# 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) ⇒ Object

Used for authentication



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# 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) ⇒ Object

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



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# 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) ⇒ Object

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

  • A TransipStruct object

  • An array of TransipStructs

  • nil



230
231
232
233
# 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) ⇒ Object

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



238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# 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) ⇒ Object



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# 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) ⇒ Object

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!



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# 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) ⇒ Object



160
161
162
163
164
# File 'lib/transip/client.rb', line 160

def to_cookies(content)
  content.map do |item|
    HTTPI::Cookie.new item
  end
end

#turn_off_debugging!Object

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



63
64
65
66
# 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) ⇒ Object



85
86
87
88
89
90
91
# 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!Object

Make Savon log to Rails.logger and turn_off_debugging!



69
70
71
72
73
74
# 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

#wsdlObject



20
21
22
# File 'lib/transip/client.rb', line 20

def wsdl
  "https://api.transip.nl/wsdl/?service=#{api_service}"
end