diff --git a/CHANGELOG.textile b/CHANGELOG.textile index b33cc14..8e4f540 100644 --- a/CHANGELOG.textile +++ b/CHANGELOG.textile @@ -1,3 +1,6 @@ +*3.2* +* Add password-based authentication with `SoftLayer::Client.with_password(username: '...', password: '...', ...)`. + *3.0* * Substantially rewrote the ObjectFilter class. ObjectFilters used to be hashes which made it easy to manipulate their content incorrectly. The new implementation has a strict interface that makes it harder to manipulate filters incorrectly. * Added a model for Virtual Server Image Templates (SoftLayer::ImageTemplate) - VirtualServerOrder now requires an instance of this class rather than allowing you to provide the global_id of an image diff --git a/lib/softlayer/Client.rb b/lib/softlayer/Client.rb index e846b75..d22ba32 100644 --- a/lib/softlayer/Client.rb +++ b/lib/softlayer/Client.rb @@ -58,6 +58,32 @@ def self.default_client=(new_default) @@default_client = new_default end + ## + # This will be using your username and password to get a portal + # token with which to authenticate client calls. + # This is a wrapper around Client.new. You can pass it the same + # parameters as with Client.new, with the exception that this will + # be expecting a password in the options hash. + def self.with_password(options = {}) + if options[:username].nil? || options[:username].empty? + raise 'A username is required to create this client' + end + + if options[:password].nil? || options[:password].empty? + raise 'A password is required to create this client' + end + + service = SoftLayer::Service.new('SoftLayer_User_Customer') + token = service.getPortalLoginToken( + options[:username], options[:password] + ) + + options[:userId] = token['userId'] + options[:authToken] = token['hash'] + + SoftLayer::Client.new(options) + end + ## # # Clients are built with a number of settings: @@ -76,10 +102,14 @@ def initialize(options = {}) settings = Config.client_settings(options) # pick up the username from the options, the global, or assume no username - @username = settings[:username] || "" + @username = settings[:username] # do a similar thing for the api key - @api_key = settings[:api_key] || "" + @api_key = settings[:api_key] + + # grab token pair + @userId = settings[:userId] + @authToken = settings[:authToken] # and the endpoint url @endpoint_url = settings[:endpoint_url] || API_PUBLIC_ENDPOINT @@ -90,19 +120,39 @@ def initialize(options = {}) # and assign a time out if the settings offer one @network_timeout = settings[:timeout] if settings.has_key?(:timeout) - raise "A SoftLayer Client requires a username" if !@username || @username.empty? - raise "A SoftLayer Client requires an api_key" if !@api_key || @api_key.empty? raise "A SoftLayer Client requires an endpoint URL" if !@endpoint_url || @endpoint_url.empty? end + # return whether this client is using token-based authentication + def token_based? + @userId && @authToken && !@authToken.empty? + end + + # return whether this client is using api_key-based authentication + def key_based? + @username && !@username.empty? && @api_key && !@api_key.empty? + end + # return a hash of the authentication headers for the client def authentication_headers - { - "authenticate" => { - "username" => @username, - "apiKey" => @api_key + if token_based? + { + 'authenticate' => { + 'complexType' => 'PortalLoginToken', + 'userId' => @userId, + 'authToken' => @authToken + } } - } + elsif key_based? + { + 'authenticate' => { + 'username' => @username, + 'apiKey' => @api_key + } + } + else + {} + end end # Returns a service with the given name. diff --git a/lib/softlayer/base.rb b/lib/softlayer/base.rb index ada6f8e..c5779db 100644 --- a/lib/softlayer/base.rb +++ b/lib/softlayer/base.rb @@ -12,7 +12,7 @@ module SoftLayer # The version number (including major, minor, and bugfix numbers) # This should change in accordance with the concept of Semantic Versioning - VERSION = "3.1.1" # version history in the CHANGELOG.textile file at the root of the source + VERSION = "3.2.0" # version history in the CHANGELOG.textile file at the root of the source # The base URL of the SoftLayer API available to the public internet. API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3/' diff --git a/spec/Client_spec.rb b/spec/Client_spec.rb index 927488d..22dbf45 100644 --- a/spec/Client_spec.rb +++ b/spec/Client_spec.rb @@ -41,42 +41,43 @@ expect(client.api_key).to eq 'fake_key' end - it 'raises an error if passed an empty user name' do - expect do - $SL_API_USERNAME = '' - client = SoftLayer::Client.new(:api_key => 'fake_key', :endpoint_url => 'http://fakeurl.org/') - end.to raise_error - - expect do - $SL_API_USERNAME = 'good_username' - $SL_API_KEY = 'sample' - client = SoftLayer::Client.new(:username => '', :api_key => 'fake_key', :endpoint_url => 'http://fakeurl.org/') - end.to raise_error - end - - it 'fails if the user name is nil' do - expect do - $SL_API_USERNAME = nil - client = SoftLayer::Client.new(:username => nil, :api_key => 'fake_key', :endpoint_url => 'http://fakeurl.org/') - end.to raise_error - end - - it 'fails if the api_key is empty' do - expect do - $SL_API_KEY = '' - client = SoftLayer::Client.new(:username => 'fake_user', :endpoint_url => 'http://fakeurl.org/') - end.to raise_error - - expect do - client = SoftLayer::Client.new(:username => 'fake_user', :api_key => '', :endpoint_url => 'http://fakeurl.org/') - end.to raise_error - end - - it 'fails if the api_key is nil' do - expect do - $SL_API_KEY = nil - client = SoftLayer::Client.new(:username => 'fake_user', :endpoint_url => 'http://fakeurl.org/', :api_key => nil) - end.to raise_error + it 'produces empty auth headers if the username is empty' do + + $SL_API_USERNAME = '' + client = SoftLayer::Client.new(:api_key => 'fake_key', :endpoint_url => 'http://fakeurl.org/') + + expect(client.authentication_headers.empty?).to be true + + $SL_API_USERNAME = 'good_username' + $SL_API_KEY = 'sample' + client = SoftLayer::Client.new(:username => '', :api_key => 'fake_key', :endpoint_url => 'http://fakeurl.org/') + + expect(client.authentication_headers.empty?).to be true + end + + it 'produces empty auth headers if the username is nil' do + $SL_API_USERNAME = nil + client = SoftLayer::Client.new(:username => nil, :api_key => 'fake_key', :endpoint_url => 'http://fakeurl.org/') + + expect(client.authentication_headers.empty?).to be true + end + + it 'produces empty auth headers if the api_key is empty' do + $SL_API_KEY = '' + client = SoftLayer::Client.new(:username => 'fake_user', :endpoint_url => 'http://fakeurl.org/') + + expect(client.authentication_headers.empty?).to be true + + client = SoftLayer::Client.new(:username => 'fake_user', :api_key => '', :endpoint_url => 'http://fakeurl.org/') + + expect(client.authentication_headers.empty?).to be true + end + + it 'produces empty auth headers if the api_key is nil' do + $SL_API_KEY = nil + client = SoftLayer::Client.new(:username => 'fake_user', :endpoint_url => 'http://fakeurl.org/', :api_key => nil) + + expect(client.authentication_headers.empty?).to be true end it 'initializes by default with nil as the timeout' do