diff --git a/lib/fog/openstack/auth/token/v3.rb b/lib/fog/openstack/auth/token/v3.rb index a5037bfbe..f5a86aeee 100644 --- a/lib/fog/openstack/auth/token/v3.rb +++ b/lib/fog/openstack/auth/token/v3.rb @@ -18,6 +18,11 @@ def credentials 'methods' => ['token'], 'token' => {'id' => @token} } + elsif @application_credential + { + 'methods' => ['application_credential'], + 'application_credential' => @application_credential + } else { 'methods' => ['password'], @@ -50,6 +55,7 @@ def path end def scope + return nil if @application_credential return @project.identity if @project return @domain.identity if @domain end @@ -96,6 +102,11 @@ def build_credentials(auth) if auth[:openstack_auth_token] @token = auth[:openstack_auth_token] + elsif auth[:openstack_application_credential_id] and auth[:openstack_application_credential_secret] + @application_credential = { + :id => auth[:openstack_application_credential_id], + :secret => auth[:openstack_application_credential_secret], + } else @user = Fog::OpenStack::Auth::User.new(auth[:openstack_userid], auth[:openstack_username]) @user.password = auth[:openstack_api_key] diff --git a/lib/fog/openstack/compute.rb b/lib/fog/openstack/compute.rb index 38bc5a2ed..b0ab2f4a8 100644 --- a/lib/fog/openstack/compute.rb +++ b/lib/fog/openstack/compute.rb @@ -14,7 +14,8 @@ class Compute < Fog::Service :openstack_project_name, :openstack_project_id, :openstack_project_domain, :openstack_user_domain, :openstack_domain_name, :openstack_project_domain_id, :openstack_user_domain_id, :openstack_domain_id, - :openstack_identity_api_version + :openstack_identity_api_version, :openstack_application_credential_id, + :openstack_application_credential_secret ## MODELS # diff --git a/lib/fog/openstack/core.rb b/lib/fog/openstack/core.rb index 86090e9ba..2b3a5a53d 100644 --- a/lib/fog/openstack/core.rb +++ b/lib/fog/openstack/core.rb @@ -16,6 +16,8 @@ module Core attr_reader :openstack_project_id attr_reader :openstack_project_domain_id attr_reader :openstack_identity_api_version + attr_reader :openstack_application_credential_id + attr_reader :openstack_application_credential_secret # fallback def self.not_found_class @@ -184,10 +186,11 @@ def setup(options) @openstack_can_reauthenticate = false else missing_credentials = [] - - missing_credentials << :openstack_api_key unless @openstack_api_key - unless @openstack_username || @openstack_userid - missing_credentials << 'openstack_username or openstack_userid' + unless @openstack_application_credential_secret and @openstack_application_credential_id + missing_credentials << :openstack_api_key unless @openstack_api_key + unless @openstack_username || @openstack_userid + missing_credentials << 'openstack_username/openstack_userid or openstack_application_credential_secret and openstack_application_credential_id' + end end unless missing_credentials.empty? raise ArgumentError, "Missing required arguments: #{missing_credentials.join(', ')}" diff --git a/lib/fog/openstack/identity/v3.rb b/lib/fog/openstack/identity/v3.rb index 3798b7c29..4c95b80d5 100644 --- a/lib/fog/openstack/identity/v3.rb +++ b/lib/fog/openstack/identity/v3.rb @@ -13,7 +13,8 @@ class V3 < Fog::Service :openstack_user_domain_id, :openstack_project_domain_id, :openstack_api_key, :openstack_current_user_id, :openstack_userid, :openstack_username, :current_user, :current_user_id, :current_tenant, - :provider, :openstack_identity_api_version, :openstack_cache_ttl + :provider, :openstack_identity_api_version, :openstack_cache_ttl, :openstack_application_credential_id, + :openstack_application_credential_secret model_path 'fog/openstack/identity/v3/models' model :domain diff --git a/lib/fog/openstack/network.rb b/lib/fog/openstack/network.rb index 10070d4d3..bd725ee3b 100644 --- a/lib/fog/openstack/network.rb +++ b/lib/fog/openstack/network.rb @@ -13,7 +13,8 @@ class Network < Fog::Service :openstack_project_name, :openstack_project_id, :openstack_project_domain, :openstack_user_domain, :openstack_domain_name, :openstack_project_domain_id, :openstack_user_domain_id, :openstack_domain_id, - :openstack_identity_api_version + :openstack_identity_api_version, :openstack_application_credential_id, + :openstack_application_credential_secret ## MODELS # diff --git a/spec/fixtures/openstack/identity_v3/authv3_application_credential.yml b/spec/fixtures/openstack/identity_v3/authv3_application_credential.yml new file mode 100644 index 000000000..901ff55a5 --- /dev/null +++ b/spec/fixtures/openstack/identity_v3/authv3_application_credential.yml @@ -0,0 +1,92 @@ +--- +http_interactions: +- request: + method: post + uri: http://devstack.openstack.stack:5000/v3/auth/tokens + body: + encoding: UTF-8 + string: '{"auth":{"identity":{"methods":["application_credential"],"application_credential":{"id":"423f19a4ac1e4f48bbb4180756e6eb6c","secret":"rEaqvJka48mpv"}}}}' + headers: + User-Agent: + - fog-core/1.38.0 + Content-Type: + - application/json + response: + status: + code: 201 + message: '' + headers: + Date: + - Tue, 03 May 2016 13:51:40 GMT + Server: + - Apache/2.4.7 (Ubuntu) + X-Subject-Token: + - f8f31a7a07344ae1a8fd9ea8c03689ab + Vary: + - X-Auth-Token + X-Openstack-Request-Id: + - req-993c0edc-e28b-4d39-b38f-e56fb0210c0f + Content-Length: + - '4597' + Content-Type: + - application/json + body: + encoding: UTF-8 + string: '{"token": {"domain": {"id": "default", "name": "Default"}, "methods": + ["password"], "roles": [{"id": "5929fb4077c3415c9850e66f2c2be16b", "name": + "admin"}], "expires_at": "2016-05-03T14:51:40.683156Z", "catalog": [{"endpoints": + [], "type": "volumev2", "id": "1766683b2f9f4ef2acf7f9e4e359fb9a", "name": + "cinderv2"}, {"endpoints": [], "type": "metering", "id": "18dbb2f0ddeb45b99d11ed0568f153ab", + "name": "ceilometer"}, {"endpoints": [{"region_id": "RegionOne", "url": "http://mo-edbdf14f1.mo.sap.corp:8777/", + "region": "RegionOne", "interface": "admin", "id": "6f44eb5bf4e646f9a7afebb1f32501f1"}, + {"region_id": "RegionOne", "url": "http://mo-edbdf14f1.mo.sap.corp:8777/", + "region": "RegionOne", "interface": "public", "id": "9bb4f1c83833484e9d53ce2b57d6308b"}, + {"region_id": "RegionOne", "url": "http://mo-edbdf14f1.mo.sap.corp:8777/", + "region": "RegionOne", "interface": "internal", "id": "e62f1c8709554031ace717700c431635"}], + "type": "metering", "id": "261a2c1f467c4b148f38822ae9f179a3", "name": "ceilometer"}, + {"endpoints": [{"region_id": "RegionOne", "url": "http://devstack.openstack.stack:35357/v3", + "region": "RegionOne", "interface": "admin", "id": "634d57ce5d534c808afb24127b7ac355"}, + {"region_id": "RegionOne", "url": "http://devstack.openstack.stack:5000/v3", "region": + "RegionOne", "interface": "public", "id": "8a7a2620740c4c2fb6fabf15746c101f"}, + {"region_id": "RegionOne", "url": "http://devstack.openstack.stack:5000/v3", "region": + "RegionOne", "interface": "internal", "id": "b53b60875b214e7f925f96d3a2a57464"}], + "type": "identity", "id": "311850187e5a47108ac9b43ec5346658", "name": "keystone"}, + {"endpoints": [], "type": "compute", "id": "3fd114ff2cff43be8bd3ecc5bc117ec8", + "name": "nova"}, {"endpoints": [], "type": "volume", "id": "4a6033d57e30494a9577358d90d08123", + "name": "cinder"}, {"endpoints": [{"region_id": "RegionOne", "url": "http://devstack.openstack.stack:8773/", + "region": "RegionOne", "interface": "internal", "id": "54546c7c383a4d6abc2b93c94d75e2f7"}, + {"region_id": "RegionOne", "url": "http://devstack.openstack.stack:8773/", "region": "RegionOne", + "interface": "admin", "id": "b253dd3d3f7d43d8ab35ad97c68440c3"}, {"region_id": + "RegionOne", "url": "http://devstack.openstack.stack:8773/", "region": "RegionOne", "interface": + "public", "id": "e7287110f01248d49cef8447d0f8d2cb"}], "type": "ec2", "id": + "5eff43878c134ae7a5f405cf0d191aff", "name": "ec2"}, {"endpoints": [{"region_id": + "RegionOne", "url": "http://i056593.dev.mo.sap.corp:8080/v2.0", "region": + "RegionOne", "interface": "internal", "id": "11d18be7930947f696db531c9bbdf245"}, + {"region_id": "RegionOne", "url": "http://i056593.dev.mo.sap.corp:8080/v2.0", + "region": "RegionOne", "interface": "admin", "id": "a603e6ffd0804c4f82a8770d71dede64"}, + {"region_id": "RegionOne", "url": "http://i056593.dev.mo.sap.corp:8080/v2.0", + "region": "RegionOne", "interface": "public", "id": "cf084db111ad4818a4f94080e0ed7819"}], + "type": "monitoring", "id": "73e3bfb0b1b944f0ace3a078baad1fcc", "name": "Monitoring"}, + {"endpoints": [], "type": "compute_legacy", "id": "92e80becd6d8462b8f51fb227eb11999", + "name": "nova_legacy"}, {"endpoints": [{"region_id": "RegionOne", "url": "http://devstack.openstack.stack:9292", + "region": "RegionOne", "interface": "admin", "id": "acebdcb3418045b9a62ed295349327c3"}, + {"region_id": "RegionOne", "url": "http://devstack.openstack.stack:9292", "region": "RegionOne", + "interface": "public", "id": "b3b24c2c4ef44ff48049caff79149091"}, {"region_id": + "RegionOne", "url": "http://devstack.openstack.stack:9292", "region": "RegionOne", "interface": + "internal", "id": "b9d30173e66148baa3ab2dc2df33cb5e"}], "type": "image", "id": + "b936e5bfd38e4a3b97fcb8d08840881f", "name": "glance"}, {"endpoints": [{"region_id": + "RegionOne", "url": "http://devstack.openstack.stack:9696/", "region": "RegionOne", "interface": + "admin", "id": "1a6718d75cd94e24993a27d275442a17"}, {"region_id": "RegionOne", + "url": "http://devstack.openstack.stack:9696/", "region": "RegionOne", "interface": "public", + "id": "5cfedecf28a54bd38031380dd0c255b1"}, {"region_id": "RegionOne", "url": + "http://devstack.openstack.stack:9696/", "region": "RegionOne", "interface": "internal", + "id": "e1be91e12d5646a8830c32146a3ed1aa"}], "type": "network", "id": "c626cfc79ed34e3699c2d96c58d105cd", + "name": "neutron"}, {"endpoints": [{"region_id": "RegionOne", "url": "http://devstack.openstack.stack:8090", + "region": "RegionOne", "interface": "admin", "id": "b91bd1a6c34b4973b3d48f94516358bc"}], + "type": "object-store", "id": "e6a92b95728740ea9bda0c348a89f0f1", "name": + "swift"}], "extras": {}, "user": {"domain": {"id": "default", "name": "Default"}, + "id": "205e0e39a2534743b517ed0aa2fbcda7", "name": "admin"}, "audit_ids": ["ySKiB4frRTSad2LVXZzW4A"], + "issued_at": "2016-05-03T13:51:40.683240Z"}}' + http_version: + recorded_at: Tue, 03 May 2016 13:51:40 GMT +recorded_with: VCR 3.0.1 diff --git a/spec/identity_v3_spec.rb b/spec/identity_v3_spec.rb index 899a587d4..3e23178e4 100644 --- a/spec/identity_v3_spec.rb +++ b/spec/identity_v3_spec.rb @@ -12,17 +12,17 @@ end it 'authenticates with password, userid and domain_id' do - VCR.use_cassette('authv3_a') do - Fog::OpenStack::Identity::V3.new( - :openstack_domain_id => @openstack_vcr.domain_id, - :openstack_api_key => @openstack_vcr.password, - :openstack_userid => @openstack_vcr.user_id, - :openstack_region => @openstack_vcr.region, - :openstack_auth_url => @os_auth_url + VCR.use_cassette('authv3_a') do + Fog::OpenStack::Identity::V3.new( + :openstack_domain_id => @openstack_vcr.domain_id, + :openstack_api_key => @openstack_vcr.password, + :openstack_userid => @openstack_vcr.user_id, + :openstack_region => @openstack_vcr.region, + :openstack_auth_url => @os_auth_url ) end end - + it 'authenticates with password, username and domain_id' do VCR.use_cassette('authv3_b') do Fog::OpenStack::Identity::V3.new( @@ -47,6 +47,19 @@ end end + it 'authenticates with application credentials' do + VCR.use_cassette('authv3_application_credential') do + Fog::OpenStack::Identity::V3.new( + :openstack_domain_id => @openstack_vcr.domain_id, + :openstack_application_credential_id => @openstack_vcr.application_credential_id, + :openstack_application_credential_secret => @openstack_vcr.application_credential_secret, + :openstack_userid => @openstack_vcr.user_id, + :openstack_region => @openstack_vcr.region, + :openstack_auth_url => @os_auth_url + ) + end + end + it 'authenticates in another region' do VCR.use_cassette('idv3_endpoint') do @endpoints_all = @service.endpoints.all diff --git a/spec/shared_context.rb b/spec/shared_context.rb index a83276983..64dae1a15 100644 --- a/spec/shared_context.rb +++ b/spec/shared_context.rb @@ -29,7 +29,9 @@ class OpenStackVCR :domain_id, :region, :region_other, - :interface + :interface, + :application_credential_id, + :application_credential_secret # This method should be called in a "before :all" call to set everything up. # A properly configured instance of the service class (e.g. @@ -109,6 +111,8 @@ def initialize(options) @domain_id = 'default' @domain_name = 'Default' @project_name = 'admin' + @application_credential_id = '423f19a4ac1e4f48bbb4180756e6eb6c' + @application_credential_secret = 'rEaqvJka48mpv' unless use_recorded @region = ENV['OS_REGION_NAME'] || options[:region_name] || @region @@ -121,6 +125,8 @@ def initialize(options) @domain_name = ENV['OS_USER_DOMAIN_NAME'] || options[:domain_name] || @domain_name @domain_id = ENV['OS_USER_DOMAIN_ID'] || options[:domain_id] || @domain_id @project_name = ENV['OS_PROJECT_NAME'] || options[:project_name] || @project_name + @application_credential_id = ENV['OS_APPLICATION_CREDENTIAL_ID'] || options[:application_credential_id] || @application_credential_id + @application_credential_secret = ENV['OS_APPLICATION_CREDENTIAL_SECRET'] || options[:application_credential_secret] || @application_credential_secret end # TODO: remove