transip 0.3.7 → 0.4.1
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.
- data/.gitignore +5 -1
- data/Gemfile +1 -3
- data/README.rdoc +37 -11
- data/lib/transip.rb +17 -235
- data/lib/transip/api_error.rb +5 -0
- data/lib/transip/client.rb +285 -0
- data/lib/transip/version.rb +2 -2
- data/transip.gemspec +2 -2
- metadata +17 -7
- checksums.yaml +0 -15
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/README.rdoc
CHANGED
@@ -1,19 +1,17 @@
|
|
1
1
|
= TransIP API
|
2
2
|
|
3
|
-
Ruby gem to use the full TransIP API (
|
4
|
-
Uses an updated version of savon
|
3
|
+
Ruby gem to use the full TransIP (www.transip.nl) API (v5.0).
|
4
|
+
Uses an updated version of savon and implements the new request signing method that the guys at TransIP have introduced into their API.
|
5
5
|
|
6
|
-
The
|
7
|
-
control panel to give your server access to the
|
8
|
-
use the key together with your username to gain access to the
|
6
|
+
The TransIP API makes use of public/private key encryption. You need to use the TransIP
|
7
|
+
control panel to give your server access to the API and to generate a key. You can then
|
8
|
+
use the key together with your username to gain access to the API.
|
9
9
|
|
10
10
|
For more info see:
|
11
11
|
|
12
12
|
* <b>The origin of this code:</b> https://github.com/joost/transip
|
13
13
|
* <b>TransIP API Docs:</b> https://www.transip.nl/g/api
|
14
14
|
|
15
|
-
Credits for full rewrite to work with new TransIP API version go to Richard Bronkhorst (https://github.com/richmans).
|
16
|
-
|
17
15
|
== Install
|
18
16
|
|
19
17
|
Install from rubygems (rubygems.org/gems/transip):
|
@@ -26,7 +24,7 @@ or add in your Bundle Gemfile:
|
|
26
24
|
|
27
25
|
For the latest version: Download / clone the repository. Bundle install the needed gems and require the lib.
|
28
26
|
|
29
|
-
git clone
|
27
|
+
git clone https://github.com/joost/transip.git
|
30
28
|
cd transip
|
31
29
|
bundle install
|
32
30
|
irb # and require './lib/transip'
|
@@ -46,19 +44,47 @@ For the most up-to-date documentation see the source files.
|
|
46
44
|
Setup the API client:
|
47
45
|
|
48
46
|
# use this in production
|
49
|
-
transip = Transip.new(username: 'your_username', key: 'your_private_rsa_key', ip: '12.34.12.3', mode: :readwrite)
|
47
|
+
transip = Transip::DomainClient.new(username: 'your_username', key: 'your_private_rsa_key', ip: '12.34.12.3', mode: :readwrite)
|
48
|
+
|
49
|
+
You can use Transip::DomainClient, Transip::VpsClient, Transip::ColocationClient, Transip::WebhostingClient and Transip::ForwardClient.
|
50
50
|
|
51
51
|
In development you can leave out the ip. To test request use :readonly mode.
|
52
52
|
|
53
|
+
If you store your private key in a seperate file you can do:
|
54
|
+
|
55
|
+
transip = Transip::DomainClient.new(username: 'your_username', key_file: 'path_to_your_private_key_file', ip: '12.34.12.3', mode: :readwrite)
|
56
|
+
|
57
|
+
=== DomainClient
|
58
|
+
|
59
|
+
transip = Transip::DomainClient.new(username: 'your_username', key: 'your_private_rsa_key', ip: '12.34.12.3', mode: :readwrite)
|
60
|
+
|
53
61
|
transip.actions # => [:batch_check_availability, :check_availability, :get_whois, :get_domain_names, :get_info, :batch_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, :get_all_tld_infos, :get_tld_info, :get_current_domain_action, :retry_current_domain_action_with_new_data, :retry_transfer_with_different_auth_code, :cancel_domain_action]
|
54
62
|
|
55
63
|
transip.request(:get_domain_names)
|
56
64
|
transip.request(:get_info, :domain_name => 'example.com')
|
57
65
|
transip.request(:get_whois, :domain_name => 'example.com')
|
58
66
|
transip.request(:set_dns_entries, :domain_name => 'example.com', :dns_entries => [Transip::DnsEntry.new('test', 5.inutes, 'A', '74.125.77.147')])
|
59
|
-
transip.request(:register, Transip::Domain.new('example.com', nil, nil, [Transip::DnsEntry.new('test',
|
67
|
+
transip.request(:register, Transip::Domain.new('example.com', nil, nil, [Transip::DnsEntry.new('test', 300, 'A', '74.125.77.147')]))
|
60
68
|
transip.request(:set_contacts, :domain_name => 'example.com', :contacts => [Transip::WhoisContact.new('type', 'first', 'middle', 'last', 'company', 'kvk', 'companyType', 'street', 'number', 'postalCode', 'city', 'phoneNumber', 'faxNumber', 'email', 'country')])
|
61
69
|
|
70
|
+
=== VpsClient
|
71
|
+
|
72
|
+
transip_vps = Transip::VpsClient.new(username: 'your_username', key: 'your_private_rsa_key', ip: '12.34.12.3', mode: :readwrite)
|
73
|
+
|
74
|
+
transip_vps.actions # => [:get_available_products, :get_available_addons, :get_active_addons_for_vps, :get_available_upgrades, :get_available_addons_for_vps, :get_cancellable_addons_for_vps, :order_vps, :order_addon, :order_private_network, :upgrade_vps, :cancel_vps, :cancel_addon, :cancel_private_network, :get_private_networks_by_vps, :get_all_private_networks, :add_vps_to_private_network, :remove_vps_from_private_network, :get_amount_of_traffic_used, :start, :stop, :reset, :create_snapshot, :revert_snapshot, :remove_snapshot, :get_vps, :get_vpses, :get_snapshots_by_vps, :get_operating_systems, :install_operating_system, :get_ips_for_vps, :get_all_ips, :add_ipv6_to_vps, :update_ptr_record]
|
75
|
+
|
76
|
+
transip_vps.request(:get_available_products)
|
77
|
+
transip_vps.request(:get_operating_systems)
|
78
|
+
transip_vps.request(:order_vps, product_name: 'vps-bladevps-x1', addons: nil, operating_system_name: 'ubuntu-12.04.1-transip', hostname: 'my.hostname.com')
|
79
|
+
|
80
|
+
=== ColocationClient, WebhostingClient and ForwardClient
|
81
|
+
|
82
|
+
Same as above.
|
83
|
+
|
84
|
+
Find out full methods and parameters by going throught the PHP sources of the TransIP API. You can find them via www.transip.nl.
|
85
|
+
|
86
|
+
== Contribute and licence
|
87
|
+
|
62
88
|
Please feel free to contribute and send me a pull request via Github!
|
63
89
|
|
64
|
-
|
90
|
+
(C)opyright 2014 Joost Hietbrink / Richard Bronkhorst, released under the MIT license.
|
data/lib/transip.rb
CHANGED
@@ -9,14 +9,17 @@ require 'base64'
|
|
9
9
|
require 'ipaddr'
|
10
10
|
|
11
11
|
require File.expand_path '../transip/version', __FILE__
|
12
|
+
require File.expand_path '../transip/client', __FILE__
|
13
|
+
require File.expand_path '../transip/api_error', __FILE__
|
14
|
+
|
12
15
|
#
|
13
|
-
# Implements the www.transip.nl API (
|
16
|
+
# Implements the www.transip.nl API (v5.0). For more info see: https://www.transip.nl/g/api/
|
14
17
|
#
|
15
18
|
# The transip API makes use of public/private key encryption. You need to use the TransIP
|
16
19
|
# control panel to give your server access to the api, and to generate a key. You can then
|
17
20
|
# use the key together with your username to gain access to the api
|
18
21
|
# Usage:
|
19
|
-
# transip = Transip.new(:username => 'api_username', :key => private_key, :ip => '12.34.12.3', :mode => 'readwrite') # use this in production
|
22
|
+
# transip = Transip::DomainClient.new(:username => 'api_username', :key => private_key, :ip => '12.34.12.3', :mode => 'readwrite') # use this in production
|
20
23
|
# 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]
|
21
24
|
# transip.request(:get_domain_names)
|
22
25
|
# transip.request(:get_info, :domain_name => 'example.com')
|
@@ -25,16 +28,13 @@ require File.expand_path '../transip/version', __FILE__
|
|
25
28
|
# transip.request(:set_contacts, :domain_name => 'example.com', :contacts => [Transip::WhoisContact.new('type', 'first', 'middle', 'last', 'company', 'kvk', 'companyType', 'street','number','postalCode','city','phoneNumber','faxNumber','email','country')])
|
26
29
|
# transip.request(:register, Transip::Domain.new('example.com', nil, nil, [Transip::DnsEntry.new('test', 5.minutes, 'A', '74.125.77.147')]))
|
27
30
|
#
|
28
|
-
|
29
|
-
SERVICE = 'DomainService'
|
30
|
-
WSDL = 'https://api.transip.nl/wsdl/?service=DomainService'
|
31
|
-
API_VERSION = '4.2'
|
32
|
-
|
33
|
-
attr_accessor :username, :password, :ip, :mode, :hash
|
34
|
-
attr_reader :response
|
31
|
+
module Transip
|
35
32
|
|
36
|
-
#
|
37
|
-
|
33
|
+
# Backwards compatibility with v3.x of the gem.
|
34
|
+
# TODO: Remove
|
35
|
+
def self.new(*args)
|
36
|
+
puts "Transip.new is deprecated. Use Transip::DomainClient.new instead!"
|
37
|
+
Client.new(*args)
|
38
38
|
end
|
39
39
|
|
40
40
|
# Following subclasses are actually not needed (as you can also
|
@@ -70,7 +70,7 @@ class Transip
|
|
70
70
|
|
71
71
|
# See what happens here: http://snippets.dzone.com/posts/show/302
|
72
72
|
def members_to_hash
|
73
|
-
Hash[*members.collect {|m| [member_name_to_camel(m), self.send(m)]}.flatten]
|
73
|
+
Hash[*members.collect {|m| [member_name_to_camel(m), self.send(m)]}.flatten(1)]
|
74
74
|
end
|
75
75
|
|
76
76
|
def to_hash
|
@@ -186,231 +186,13 @@ class Transip
|
|
186
186
|
class Tld < TransipStruct.new(:name, :price, :renewal_price, :capabilities, :registration_period_length, :cancel_time_frame)
|
187
187
|
end
|
188
188
|
|
189
|
-
|
190
|
-
|
191
|
-
# * ip
|
192
|
-
# * key
|
193
|
-
# * mode
|
194
|
-
#
|
195
|
-
# Example:
|
196
|
-
# transip = Transip.new(:username => 'api_username', :ip => '12.34.12.3', :key => mykey, :mode => 'readwrite') # use this in production
|
197
|
-
def initialize(options = {})
|
198
|
-
@key = options[:key]
|
199
|
-
@username = options[:username]
|
200
|
-
@ip = options[:ip]
|
201
|
-
raise ArgumentError, "The :username, :ip and :key options are required!" if @username.nil? or @key.nil?
|
202
|
-
|
203
|
-
@mode = options[:mode] || :readonly
|
204
|
-
@endpoint = options[:endpoint] || 'api.transip.nl'
|
205
|
-
if options[:password]
|
206
|
-
@password = options[:password]
|
207
|
-
end
|
208
|
-
@savon_options = {
|
209
|
-
:wsdl => WSDL
|
210
|
-
}
|
211
|
-
# By default we don't want to debug!
|
212
|
-
self.turn_off_debugging!
|
213
|
-
end
|
214
|
-
|
215
|
-
# By default we don't want to debug!
|
216
|
-
# Changing might impact other Savon usages.
|
217
|
-
def turn_off_debugging!
|
218
|
-
@savon_options[:log] = false # disable logging
|
219
|
-
@savon_options[:log_level] = :info # changing the log level
|
220
|
-
end
|
221
|
-
|
222
|
-
|
223
|
-
# Make Savon log to Rails.logger and turn_off_debugging!
|
224
|
-
def use_with_rails!
|
225
|
-
if Rails.env.production?
|
226
|
-
self.turn_off_debugging!
|
227
|
-
end
|
228
|
-
@savon_options[:logger] = Rails.logger # using the Rails logger
|
229
|
-
end
|
230
|
-
|
231
|
-
# yes, i know, it smells bad
|
232
|
-
def convert_array_to_hash(array)
|
233
|
-
result = {}
|
234
|
-
array.each_with_index do |value, index|
|
235
|
-
result[index] = value
|
236
|
-
end
|
237
|
-
result
|
238
|
-
end
|
239
|
-
|
240
|
-
def urlencode(input)
|
241
|
-
output = URI.encode_www_form_component(input)
|
242
|
-
output.gsub!('+', '%20')
|
243
|
-
output.gsub!('%7E', '~')
|
244
|
-
output
|
245
|
-
end
|
246
|
-
|
247
|
-
def serialize_parameters(parameters, key_prefix=nil)
|
248
|
-
parameters = parameters.to_hash.values.first if parameters.is_a? TransipStruct
|
249
|
-
parameters = convert_array_to_hash(parameters) if parameters.is_a? Array
|
250
|
-
if not parameters.is_a? Hash
|
251
|
-
return urlencode(parameters)
|
252
|
-
end
|
253
|
-
|
254
|
-
encoded_parameters = []
|
255
|
-
parameters.each do |key, value|
|
256
|
-
next if key.to_s == '@xsi:type'
|
257
|
-
encoded_key = (key_prefix.nil?) ? urlencode(key) : "#{key_prefix}[#{urlencode(key)}]"
|
258
|
-
if value.is_a? Hash or value.is_a? Array or value.is_a? TransipStruct
|
259
|
-
encoded_parameters << serialize_parameters(value, encoded_key)
|
260
|
-
else
|
261
|
-
encoded_value = urlencode(value)
|
262
|
-
encoded_parameters << "#{encoded_key}=#{encoded_value}"
|
263
|
-
end
|
264
|
-
end
|
265
|
-
|
266
|
-
encoded_parameters = encoded_parameters.join("&")
|
267
|
-
#puts encoded_parameters.split('&').join("\n")
|
268
|
-
encoded_parameters
|
269
|
-
end
|
270
|
-
|
271
|
-
|
272
|
-
# does all the techy stuff to calculate transip's sick authentication scheme:
|
273
|
-
# a hash with all the request information is subsequently:
|
274
|
-
# serialized like a www form
|
275
|
-
# SHA512 digested
|
276
|
-
# asn1 header added
|
277
|
-
# private key encrypted
|
278
|
-
# Base64 encoded
|
279
|
-
# URL encoded
|
280
|
-
# I think the guys at transip were trying to use their entire crypto-toolbox!
|
281
|
-
def signature(method, parameters, time, nonce)
|
282
|
-
formatted_method = method.to_s.lower_camelcase
|
283
|
-
parameters ||= {}
|
284
|
-
input = convert_array_to_hash(parameters.values)
|
285
|
-
options = {
|
286
|
-
'__method' => formatted_method,
|
287
|
-
'__service' => SERVICE,
|
288
|
-
'__hostname' => @endpoint,
|
289
|
-
'__timestamp' => time,
|
290
|
-
'__nonce' => nonce
|
291
|
-
|
292
|
-
}
|
293
|
-
input.merge!(options)
|
294
|
-
raise "Invalid RSA key" unless @key =~ /-----BEGIN (RSA )?PRIVATE KEY-----(.*)-----END (RSA )?PRIVATE KEY-----/sim
|
295
|
-
serialized_input = serialize_parameters(input)
|
296
|
-
|
297
|
-
digest = Digest::SHA512.new.digest(serialized_input)
|
298
|
-
asn_header = "\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40"
|
299
|
-
|
300
|
-
# convert asn_header literal to ASCII-8BIT
|
301
|
-
if RUBY_VERSION.split('.')[0] == "2"
|
302
|
-
asn = asn_header.b + digest
|
303
|
-
else
|
304
|
-
asn = asn_header + digest
|
305
|
-
end
|
306
|
-
private_key = OpenSSL::PKey::RSA.new(@key)
|
307
|
-
encrypted_asn = private_key.private_encrypt(asn)
|
308
|
-
readable_encrypted_asn = Base64.encode64(encrypted_asn)
|
309
|
-
urlencode(readable_encrypted_asn)
|
310
|
-
end
|
311
|
-
|
312
|
-
def to_cookies(content)
|
313
|
-
content.map do |item|
|
314
|
-
HTTPI::Cookie.new item
|
315
|
-
end
|
316
|
-
end
|
317
|
-
|
318
|
-
|
319
|
-
# Used for authentication
|
320
|
-
def cookies(method, parameters)
|
321
|
-
time = Time.new.to_i
|
322
|
-
#strip out the -'s because transip requires the nonce to be between 6 and 32 chars
|
323
|
-
nonce = SecureRandom.uuid.gsub("-", '')
|
324
|
-
result = to_cookies [ "login=#{self.username}",
|
325
|
-
"mode=#{self.mode}",
|
326
|
-
"timestamp=#{time}",
|
327
|
-
"nonce=#{nonce}",
|
328
|
-
"clientVersion=#{API_VERSION}",
|
329
|
-
"signature=#{signature(method, parameters, time, nonce)}"
|
330
|
-
|
331
|
-
]
|
332
|
-
#puts signature(method, parameters, time, nonce)
|
333
|
-
result
|
334
|
-
end
|
335
|
-
|
336
|
-
# Same as client method but initializes a brand new fresh client.
|
337
|
-
# You have to use this one when you want to re-set the mode (readwrite, readonly),
|
338
|
-
# or authentication details of your client.
|
339
|
-
def client!
|
340
|
-
@client = Savon::Client.new(@savon_options) do
|
341
|
-
namespaces(
|
342
|
-
"xmlns:enc" => "http://schemas.xmlsoap.org/soap/encoding/"
|
343
|
-
)
|
344
|
-
end
|
345
|
-
return @client
|
346
|
-
end
|
189
|
+
# VPS related methods
|
190
|
+
# Available from TransIp v5.0.
|
347
191
|
|
348
|
-
|
349
|
-
# This object is re-used and cached as @client.
|
350
|
-
def client
|
351
|
-
@client ||= client!
|
192
|
+
class Vps < TransipStruct.new(:name, :description, :operating_system, :disk_size, :memory_size, :cpus, :status, :ip_address, :vnc_hostname, :vnc_port_number, :vnc_password, :is_blocked, :is_customer_locked)
|
352
193
|
end
|
353
194
|
|
354
|
-
|
355
|
-
def actions
|
356
|
-
client.operations
|
195
|
+
class VpsService < TransipStruct.new(:name, :description, :operating_system, :disk_size, :memory_size, :cpus, :status, :ip_address, :vnc_hostname, :vnc_port_number, :vnc_password, :is_blocked, :is_customer_locked)
|
357
196
|
end
|
358
197
|
|
359
|
-
|
360
|
-
def fix_array_definitions(options)
|
361
|
-
result = {}
|
362
|
-
options.each do |key, value|
|
363
|
-
if value.is_a? Array and value.size > 0
|
364
|
-
entry_name = value.first.class.name.split(":").last
|
365
|
-
result[key] = {
|
366
|
-
'item' => {:content! => value, :'@xsi:type' => "tns:#{entry_name}"},
|
367
|
-
:'@xsi:type' => "tns:ArrayOf#{entry_name}",
|
368
|
-
:'@enc:arrayType' => "tns:#{entry_name}[#{value.size}]"
|
369
|
-
}
|
370
|
-
else
|
371
|
-
result[key] = value
|
372
|
-
end
|
373
|
-
end
|
374
|
-
result
|
375
|
-
end
|
376
|
-
|
377
|
-
|
378
|
-
# converts the savon response object to something we can return to the caller
|
379
|
-
# - A TransipStruct object
|
380
|
-
# - An array of TransipStructs
|
381
|
-
# - nil
|
382
|
-
def process_response(response)
|
383
|
-
response = response.to_hash.values.first[:return] rescue nil
|
384
|
-
TransipStruct.from_soap(response)
|
385
|
-
|
386
|
-
end
|
387
|
-
|
388
|
-
# This is the main request function
|
389
|
-
# throws ApiError
|
390
|
-
# returns response object (can be TransipStruct or Array of TransipStruct)
|
391
|
-
def request(action, options = nil)
|
392
|
-
formatted_action = action.to_s.lower_camelcase
|
393
|
-
parameters = {
|
394
|
-
# for some reason, the transip server wants the body root tag to be
|
395
|
-
# the name of the action.
|
396
|
-
:message_tag => formatted_action
|
397
|
-
}
|
398
|
-
options = options.to_hash if options.is_a? Transip::TransipStruct
|
399
|
-
|
400
|
-
if options.is_a? Hash
|
401
|
-
xml_options = fix_array_definitions(options)
|
402
|
-
elsif options.nil?
|
403
|
-
xml_options = nil
|
404
|
-
else
|
405
|
-
raise "Invalid parameter format (should be nil, hash or TransipStruct"
|
406
|
-
end
|
407
|
-
parameters[:message] = xml_options
|
408
|
-
parameters[:cookies] = cookies(action, options)
|
409
|
-
#puts parameters.inspect
|
410
|
-
response = client.call(action, parameters)
|
411
|
-
|
412
|
-
process_response(response)
|
413
|
-
rescue Savon::SOAPFault => e
|
414
|
-
raise ApiError.new(e), e.message.sub(/^\(\d+\)\s+/,'') # We raise our own error (FIXME: Correct?).
|
415
|
-
end
|
416
|
-
end
|
198
|
+
end
|
@@ -0,0 +1,285 @@
|
|
1
|
+
module Transip
|
2
|
+
|
3
|
+
class Client
|
4
|
+
|
5
|
+
API_VERSION = '5.0'
|
6
|
+
API_SERVICE = 'DomainService'
|
7
|
+
|
8
|
+
attr_accessor :username, :password, :ip, :mode, :hash
|
9
|
+
attr_reader :response
|
10
|
+
|
11
|
+
def api_version
|
12
|
+
# We use self.class:: here to not use parentclass constant.
|
13
|
+
@api_version || self.class::API_VERSION
|
14
|
+
end
|
15
|
+
|
16
|
+
def api_service
|
17
|
+
@api_service || self.class::API_SERVICE
|
18
|
+
end
|
19
|
+
|
20
|
+
def wsdl
|
21
|
+
"https://api.transip.nl/wsdl/?service=#{api_service}"
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_accessor :debug
|
25
|
+
|
26
|
+
# Options:
|
27
|
+
# * username - Your login name on the TransIP website.
|
28
|
+
# * ip - needed in production
|
29
|
+
# * 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.
|
30
|
+
# * mode - :readonly, :readwrite
|
31
|
+
#
|
32
|
+
# Example:
|
33
|
+
# transip = Transip.new(:username => 'api_username', :ip => '12.34.12.3', :key => mykey, :mode => 'readwrite') # use this in production
|
34
|
+
def initialize(options = {})
|
35
|
+
@key = options[:key] || (options[:key_file] && File.read(options[:key_file]))
|
36
|
+
@username = options[:username]
|
37
|
+
@ip = options[:ip]
|
38
|
+
@api_version = options[:api_version]
|
39
|
+
@api_service = options[:api_service]
|
40
|
+
raise ArgumentError, "The :username, :ip and :key options are required!" if @username.nil? or @key.nil?
|
41
|
+
|
42
|
+
@mode = options[:mode] || :readonly
|
43
|
+
@endpoint = options[:endpoint] || 'api.transip.nl'
|
44
|
+
if options[:password]
|
45
|
+
@password = options[:password]
|
46
|
+
end
|
47
|
+
@savon_options = {
|
48
|
+
:wsdl => wsdl
|
49
|
+
}
|
50
|
+
# By default we don't want to debug!
|
51
|
+
self.turn_off_debugging!
|
52
|
+
end
|
53
|
+
|
54
|
+
# By default we don't want to debug!
|
55
|
+
# Changing might impact other Savon usages.
|
56
|
+
def turn_off_debugging!
|
57
|
+
@savon_options[:log] = false # disable logging
|
58
|
+
@savon_options[:log_level] = :info # changing the log level
|
59
|
+
end
|
60
|
+
|
61
|
+
# Make Savon log to Rails.logger and turn_off_debugging!
|
62
|
+
def use_with_rails!
|
63
|
+
if Rails.env.production?
|
64
|
+
self.turn_off_debugging!
|
65
|
+
end
|
66
|
+
@savon_options[:logger] = Rails.logger # using the Rails logger
|
67
|
+
end
|
68
|
+
|
69
|
+
# yes, i know, it smells bad
|
70
|
+
def convert_array_to_hash(array)
|
71
|
+
result = {}
|
72
|
+
array.each_with_index do |value, index|
|
73
|
+
result[index] = value
|
74
|
+
end
|
75
|
+
result
|
76
|
+
end
|
77
|
+
|
78
|
+
def urlencode(input)
|
79
|
+
output = URI.encode_www_form_component(input)
|
80
|
+
output.gsub!('+', '%20')
|
81
|
+
output.gsub!('%7E', '~')
|
82
|
+
output
|
83
|
+
end
|
84
|
+
|
85
|
+
def serialize_parameters(parameters, key_prefix=nil)
|
86
|
+
debug_log("serialize_parameters(#{parameters.inspect}, #{key_prefix.inspect}")
|
87
|
+
|
88
|
+
parameters = parameters.to_hash.values.first if parameters.is_a? TransipStruct
|
89
|
+
parameters = convert_array_to_hash(parameters) if parameters.is_a? Array
|
90
|
+
if not parameters.is_a? Hash
|
91
|
+
return urlencode(parameters)
|
92
|
+
end
|
93
|
+
return "#{key_prefix}=" if parameters.empty?
|
94
|
+
|
95
|
+
encoded_parameters = []
|
96
|
+
parameters.each do |key, value|
|
97
|
+
next if key.to_s == '@xsi:type'
|
98
|
+
encoded_key = (key_prefix.nil?) ? urlencode(key) : "#{key_prefix}[#{urlencode(key)}]"
|
99
|
+
if value.is_a?(Hash) or value.is_a?(Array) or value.is_a?(TransipStruct)
|
100
|
+
encoded_parameters << serialize_parameters(value, encoded_key)
|
101
|
+
else
|
102
|
+
encoded_value = urlencode(value)
|
103
|
+
encoded_parameters << "#{encoded_key}=#{encoded_value}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
encoded_parameters = encoded_parameters.join("&")
|
108
|
+
debug_log("encoded_parameters:\n#{encoded_parameters.split('&').join("\n")}")
|
109
|
+
encoded_parameters
|
110
|
+
end
|
111
|
+
|
112
|
+
# does all the techy stuff to calculate transip's sick authentication scheme:
|
113
|
+
# a hash with all the request information is subsequently:
|
114
|
+
# serialized like a www form
|
115
|
+
# SHA512 digested
|
116
|
+
# asn1 header added
|
117
|
+
# private key encrypted
|
118
|
+
# Base64 encoded
|
119
|
+
# URL encoded
|
120
|
+
# I think the guys at transip were trying to use their entire crypto-toolbox!
|
121
|
+
def signature(method, parameters, time, nonce)
|
122
|
+
formatted_method = method.to_s.lower_camelcase
|
123
|
+
parameters ||= {}
|
124
|
+
input = convert_array_to_hash(parameters.values)
|
125
|
+
options = {
|
126
|
+
'__method' => formatted_method,
|
127
|
+
'__service' => api_service,
|
128
|
+
'__hostname' => @endpoint,
|
129
|
+
'__timestamp' => time,
|
130
|
+
'__nonce' => nonce
|
131
|
+
|
132
|
+
}
|
133
|
+
input.merge!(options)
|
134
|
+
raise "Invalid RSA key" unless @key =~ /-----BEGIN (RSA )?PRIVATE KEY-----(.*)-----END (RSA )?PRIVATE KEY-----/sim
|
135
|
+
serialized_input = serialize_parameters(input)
|
136
|
+
|
137
|
+
digest = Digest::SHA512.new.digest(serialized_input)
|
138
|
+
asn_header = "\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40"
|
139
|
+
|
140
|
+
# convert asn_header literal to ASCII-8BIT
|
141
|
+
if RUBY_VERSION.split('.')[0] == "2"
|
142
|
+
asn = asn_header.b + digest
|
143
|
+
else
|
144
|
+
asn = asn_header + digest
|
145
|
+
end
|
146
|
+
private_key = OpenSSL::PKey::RSA.new(@key)
|
147
|
+
encrypted_asn = private_key.private_encrypt(asn)
|
148
|
+
readable_encrypted_asn = Base64.encode64(encrypted_asn)
|
149
|
+
urlencode(readable_encrypted_asn)
|
150
|
+
end
|
151
|
+
|
152
|
+
def to_cookies(content)
|
153
|
+
content.map do |item|
|
154
|
+
HTTPI::Cookie.new item
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Used for authentication
|
159
|
+
def cookies(method, parameters)
|
160
|
+
time = Time.new.to_i
|
161
|
+
#strip out the -'s because transip requires the nonce to be between 6 and 32 chars
|
162
|
+
nonce = SecureRandom.uuid.gsub("-", '')
|
163
|
+
result = to_cookies [ "login=#{self.username}",
|
164
|
+
"mode=#{self.mode}",
|
165
|
+
"timestamp=#{time}",
|
166
|
+
"nonce=#{nonce}",
|
167
|
+
"clientVersion=#{api_version}",
|
168
|
+
"signature=#{signature(method, parameters, time, nonce)}"
|
169
|
+
|
170
|
+
]
|
171
|
+
debug_log("signature:\n#{signature(method, parameters, time, nonce)}")
|
172
|
+
result
|
173
|
+
end
|
174
|
+
|
175
|
+
# Same as client method but initializes a brand new fresh client.
|
176
|
+
# You have to use this one when you want to re-set the mode (readwrite, readonly),
|
177
|
+
# or authentication details of your client.
|
178
|
+
def client!
|
179
|
+
@client = Savon::Client.new(@savon_options) do
|
180
|
+
namespaces(
|
181
|
+
"xmlns:enc" => "http://schemas.xmlsoap.org/soap/encoding/"
|
182
|
+
)
|
183
|
+
end
|
184
|
+
return @client
|
185
|
+
end
|
186
|
+
|
187
|
+
# Returns a Savon::Client object to be used in the connection.
|
188
|
+
# This object is re-used and cached as @client.
|
189
|
+
def client
|
190
|
+
@client ||= client!
|
191
|
+
end
|
192
|
+
|
193
|
+
# Returns Array with all possible SOAP WSDL actions.
|
194
|
+
def actions
|
195
|
+
client.operations
|
196
|
+
end
|
197
|
+
|
198
|
+
# This makes sure that arrays are properly encoded as soap-arrays by Gyoku
|
199
|
+
def fix_array_definitions(options)
|
200
|
+
result = {}
|
201
|
+
options.each do |key, value|
|
202
|
+
if value.is_a?(Array) and (value.size > 0)
|
203
|
+
entry_name = value.first.class.name.split(":").last
|
204
|
+
result[key] = {
|
205
|
+
'item' => {:content! => value, :'@xsi:type' => "tns:#{entry_name}"},
|
206
|
+
:'@xsi:type' => "tns:ArrayOf#{entry_name}",
|
207
|
+
:'@enc:arrayType' => "tns:#{entry_name}[#{value.size}]"
|
208
|
+
}
|
209
|
+
elsif value.is_a?(Hash)
|
210
|
+
result[key] = fix_array_definitions(value)
|
211
|
+
else
|
212
|
+
result[key] = value
|
213
|
+
end
|
214
|
+
end
|
215
|
+
result
|
216
|
+
end
|
217
|
+
|
218
|
+
# converts the savon response object to something we can return to the caller
|
219
|
+
# - A TransipStruct object
|
220
|
+
# - An array of TransipStructs
|
221
|
+
# - nil
|
222
|
+
def process_response(response)
|
223
|
+
response = response.to_hash.values.first[:return] rescue nil
|
224
|
+
TransipStruct.from_soap(response)
|
225
|
+
end
|
226
|
+
|
227
|
+
# This is the main request function
|
228
|
+
# throws ApiError
|
229
|
+
# returns response object (can be TransipStruct or Array of TransipStruct)
|
230
|
+
def request(action, options = nil)
|
231
|
+
formatted_action = action.to_s.lower_camelcase
|
232
|
+
parameters = {
|
233
|
+
# for some reason, the transip server wants the body root tag to be
|
234
|
+
# the name of the action.
|
235
|
+
:message_tag => formatted_action
|
236
|
+
}
|
237
|
+
options = options.to_hash if options.is_a?(Transip::TransipStruct)
|
238
|
+
|
239
|
+
if options.is_a?(Hash)
|
240
|
+
xml_options = fix_array_definitions(options)
|
241
|
+
elsif options.nil?
|
242
|
+
xml_options = nil
|
243
|
+
else
|
244
|
+
raise "Invalid parameter format (should be nil, hash or TransipStruct)"
|
245
|
+
end
|
246
|
+
parameters[:message] = xml_options
|
247
|
+
parameters[:cookies] = cookies(action, options)
|
248
|
+
debug_log("parameters:\n#{parameters.inspect}")
|
249
|
+
response = client.call(action, parameters)
|
250
|
+
|
251
|
+
process_response(response)
|
252
|
+
rescue Savon::SOAPFault => e
|
253
|
+
raise ApiError.new(e), e.message.sub(/^\(\d+\)\s+/,'') # We raise our own error (FIXME: Correct?).
|
254
|
+
end
|
255
|
+
|
256
|
+
private
|
257
|
+
|
258
|
+
def debug_log(msg)
|
259
|
+
puts msg if @debug
|
260
|
+
end
|
261
|
+
|
262
|
+
end
|
263
|
+
|
264
|
+
# 'Aliased' by Transip::Client.
|
265
|
+
class DomainClient < Client;end
|
266
|
+
|
267
|
+
# We name it VpsClient instead of VpsService since the latter is already in use by
|
268
|
+
# the TransipStruct.
|
269
|
+
class VpsClient < Client
|
270
|
+
API_SERVICE = 'VpsService'
|
271
|
+
end
|
272
|
+
|
273
|
+
class ColocationClient < Client
|
274
|
+
API_SERVICE = 'ColocationService'
|
275
|
+
end
|
276
|
+
|
277
|
+
class WebhostingClient < Client
|
278
|
+
API_SERVICE = 'WebhostingService'
|
279
|
+
end
|
280
|
+
|
281
|
+
class ForwardClient < Client
|
282
|
+
API_SERVICE = 'ForwardService'
|
283
|
+
end
|
284
|
+
|
285
|
+
end
|
data/lib/transip/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
|
2
|
-
VERSION = "0.
|
1
|
+
module Transip
|
2
|
+
VERSION = "0.4.1"
|
3
3
|
end
|
data/transip.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = Transip::VERSION
|
9
9
|
spec.authors = ["Joost Hietbrink", "Richard Bronkhorst"]
|
10
10
|
spec.email = ["joost@joopp.com"]
|
11
|
-
spec.description = %q{Ruby gem to use the full TransIP API (
|
12
|
-
spec.summary = %q{Ruby gem to use the full TransIP API (
|
11
|
+
spec.description = %q{Ruby gem to use the full TransIP API (v5.0).}
|
12
|
+
spec.summary = %q{Ruby gem to use the full TransIP API (v5.0).}
|
13
13
|
spec.homepage = %q{http://github.com/joost/transip}
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
metadata
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: transip
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.1
|
5
|
+
prerelease:
|
5
6
|
platform: ruby
|
6
7
|
authors:
|
7
8
|
- Joost Hietbrink
|
@@ -9,11 +10,12 @@ authors:
|
|
9
10
|
autorequire:
|
10
11
|
bindir: bin
|
11
12
|
cert_chain: []
|
12
|
-
date:
|
13
|
+
date: 2014-02-25 00:00:00.000000000 Z
|
13
14
|
dependencies:
|
14
15
|
- !ruby/object:Gem::Dependency
|
15
16
|
name: savon
|
16
17
|
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
17
19
|
requirements:
|
18
20
|
- - ! '>='
|
19
21
|
- !ruby/object:Gem::Version
|
@@ -21,6 +23,7 @@ dependencies:
|
|
21
23
|
type: :runtime
|
22
24
|
prerelease: false
|
23
25
|
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
24
27
|
requirements:
|
25
28
|
- - ! '>='
|
26
29
|
- !ruby/object:Gem::Version
|
@@ -28,6 +31,7 @@ dependencies:
|
|
28
31
|
- !ruby/object:Gem::Dependency
|
29
32
|
name: curb
|
30
33
|
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
31
35
|
requirements:
|
32
36
|
- - ! '>='
|
33
37
|
- !ruby/object:Gem::Version
|
@@ -35,6 +39,7 @@ dependencies:
|
|
35
39
|
type: :runtime
|
36
40
|
prerelease: false
|
37
41
|
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
38
43
|
requirements:
|
39
44
|
- - ! '>='
|
40
45
|
- !ruby/object:Gem::Version
|
@@ -42,6 +47,7 @@ dependencies:
|
|
42
47
|
- !ruby/object:Gem::Dependency
|
43
48
|
name: facets
|
44
49
|
requirement: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
45
51
|
requirements:
|
46
52
|
- - ! '>='
|
47
53
|
- !ruby/object:Gem::Version
|
@@ -49,11 +55,12 @@ dependencies:
|
|
49
55
|
type: :runtime
|
50
56
|
prerelease: false
|
51
57
|
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
52
59
|
requirements:
|
53
60
|
- - ! '>='
|
54
61
|
- !ruby/object:Gem::Version
|
55
62
|
version: 2.9.3
|
56
|
-
description: Ruby gem to use the full TransIP API (
|
63
|
+
description: Ruby gem to use the full TransIP API (v5.0).
|
57
64
|
email:
|
58
65
|
- joost@joopp.com
|
59
66
|
executables: []
|
@@ -68,31 +75,34 @@ files:
|
|
68
75
|
- MIT-LICENSE
|
69
76
|
- README.rdoc
|
70
77
|
- lib/transip.rb
|
78
|
+
- lib/transip/api_error.rb
|
79
|
+
- lib/transip/client.rb
|
71
80
|
- lib/transip/version.rb
|
72
81
|
- transip.gemspec
|
73
82
|
homepage: http://github.com/joost/transip
|
74
83
|
licenses:
|
75
84
|
- MIT
|
76
|
-
metadata: {}
|
77
85
|
post_install_message:
|
78
86
|
rdoc_options:
|
79
87
|
- --charset=UTF-8
|
80
88
|
require_paths:
|
81
89
|
- lib
|
82
90
|
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
83
92
|
requirements:
|
84
93
|
- - ! '>='
|
85
94
|
- !ruby/object:Gem::Version
|
86
95
|
version: '0'
|
87
96
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
88
98
|
requirements:
|
89
99
|
- - ! '>='
|
90
100
|
- !ruby/object:Gem::Version
|
91
101
|
version: '0'
|
92
102
|
requirements: []
|
93
103
|
rubyforge_project:
|
94
|
-
rubygems_version:
|
104
|
+
rubygems_version: 1.8.23
|
95
105
|
signing_key:
|
96
|
-
specification_version:
|
97
|
-
summary: Ruby gem to use the full TransIP API (
|
106
|
+
specification_version: 3
|
107
|
+
summary: Ruby gem to use the full TransIP API (v5.0).
|
98
108
|
test_files: []
|
checksums.yaml
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
---
|
2
|
-
!binary "U0hBMQ==":
|
3
|
-
metadata.gz: !binary |-
|
4
|
-
NjcxZDVlYWExZTQ0MjA2NGZmYWNjM2QzOTkwNjg0OTgzODgzNGRiNg==
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
NzM0ZjJlMzBjNDExYmVmMzE1ZDA1MjA0NTk1NGVkZTIwNDZmOTBlNQ==
|
7
|
-
SHA512:
|
8
|
-
metadata.gz: !binary |-
|
9
|
-
OGNhMDg5ZjBiZTg2ZmZjZGUzZjZiYTkxZWI4MGMxMzYwZWFhNDM5OGIxZWVl
|
10
|
-
ZDY1ZmY0YWYxZjZhYWIzZGU4NzM1YTYyZGZlOTBjMGYxMWIzN2FiNWUxMjBl
|
11
|
-
NzA1OWE1Nzk4NTg0ZDBlNDM1MGFmZWQ2NzdjYzMyMTFiNTU3MzA=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
NGFkYTQ4NWY5Y2U5ZDg4YTQ2ODdjYjY4ZGRiNzA0NmRiZGRiMWVlYzg2YjIx
|
14
|
-
YzRlNTNlODM3Yjg5NThlNzBkZTRjMmU0MWU1NDVjZDUyNDZhMzVmY2RjMThh
|
15
|
-
MmFkMGVmYWNhMmM1YWY1MzE4YjA4YWZiMDc4ZWNjODNlN2FhYzA=
|