From d4e8d6b3d436c49062a4e00514374e084a213fcd Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Mon, 12 Nov 2012 08:55:16 +0000 Subject: [PATCH] Initial commit of working service --- .gitignore | 7 ++ Gemfile | 12 ++ Gemfile.lock | 225 +++++++++++++++++++++++++++++++++++++ README.md | 99 ++++++++++++++++ Rakefile | 7 ++ app.rb | 5 + bin/oauth2-gateway | 11 ++ config/oauth2_gateway.yml | 22 ++++ lib/service.rb | 3 + lib/service/gateway.rb | 17 +++ lib/service/provisioner.rb | 188 +++++++++++++++++++++++++++++++ lib/service/version.rb | 7 ++ oauth2_service.gemspec | 27 +++++ spec/gateway_spec.rb | 26 +++++ spec/provisioner_spec.rb | 168 +++++++++++++++++++++++++++ spec/spec_helper.rb | 31 +++++ 16 files changed, 855 insertions(+) create mode 100644 .gitignore create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 README.md create mode 100644 Rakefile create mode 100644 app.rb create mode 100755 bin/oauth2-gateway create mode 100644 config/oauth2_gateway.yml create mode 100644 lib/service.rb create mode 100644 lib/service/gateway.rb create mode 100644 lib/service/provisioner.rb create mode 100644 lib/service/version.rb create mode 100644 oauth2_service.gemspec create mode 100644 spec/gateway_spec.rb create mode 100644 spec/provisioner_spec.rb create mode 100644 spec/spec_helper.rb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..32a9e26 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.gem +.bundle +pkg/* +vendor/ +*~ +#* +dev.yml diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..437ff64 --- /dev/null +++ b/Gemfile @@ -0,0 +1,12 @@ +source "http://rubygems.org" + +gem 'eventmachine', :git => 'git://github.com/cloudfoundry/eventmachine.git', :branch => 'release-0.12.11-cf' +gem 'vcap_common', :require => ['vcap/common', 'vcap/component'], :git => 'git://github.com/cloudfoundry/vcap-common.git', :ref => 'fd6b6d91' +gem 'vcap_logging', :require => ['vcap/logging'], :git => 'git://github.com/cloudfoundry/common.git', :ref => 'b96ec1192' +gem 'vcap_services_base', :git => 'git://github.com/dsyer/vcap-services-base.git', :ref => 'debugging' +gem 'warden-client', :require => ['warden/client'], :git => 'git://github.com/cloudfoundry/warden.git', :ref => '21f9a32ab50' +gem 'warden-protocol', :require => ['warden/protocol'], :git => 'git://github.com/cloudfoundry/warden.git', :ref => '21f9a32ab50' +gem 'cf-uaa-client', :git => 'git://github.com/cloudfoundry/uaa.git', :ref => 'master' + +# Specify your gem's dependencies in test_service.gemspec +gemspec diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..ab09289 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,225 @@ +GIT + remote: git://github.com/cloudfoundry/common.git + revision: b96ec1192d961925d91e17ca3831f8547489d918 + ref: b96ec1192 + specs: + vcap_logging (1.0.2) + rake + +GIT + remote: git://github.com/cloudfoundry/eventmachine.git + revision: 2806c630d8631d5dcf9fb2555f665b829052aabe + branch: release-0.12.11-cf + specs: + eventmachine (0.12.11.cloudfoundry.3) + +GIT + remote: git://github.com/cloudfoundry/uaa.git + revision: 2bdf250144723ab572626d4b6c3535a16fb5470a + ref: master + specs: + cf-uaa-client (1.2.5) + em-http-request (>= 1.0.0.beta.3) + eventmachine + highline + launchy + rest-client + yajl-ruby + +GIT + remote: git://github.com/cloudfoundry/vcap-common.git + revision: fd6b6d91b19c551cf5091c8469595df923dd2612 + ref: fd6b6d91 + specs: + vcap_common (2.0.7) + em-http-request (~> 1.0.0.beta3) + eventmachine + httpclient + membrane (~> 0.0.2) + mime-types + multipart-post + nats (~> 0.4.24) + posix-spawn (~> 0.3.6) + thin + yajl-ruby (~> 0.8.3) + +GIT + remote: git://github.com/cloudfoundry/warden.git + revision: 21f9a32ab501a6485c9a4e7aff4f9b6f40e31e3c + ref: 21f9a32ab50 + specs: + warden-client (0.0.6) + warden-protocol + warden-protocol (0.0.5) + beefcake + +GIT + remote: git://github.com/dsyer/vcap-services-base.git + revision: 8ac662cde534771230fd1661af0c908eaa781ad5 + ref: debugging + specs: + vcap_services_base (0.1.16) + curb (~> 0.7.16) + datamapper (~> 1.1.0) + do_sqlite3 (~> 0.10.3) + em-http-request (~> 1.0.0.beta.3) + eventmachine (~> 0.12.11.cloudfoundry.3) + eventmachine_httpserver (~> 0.2.1) + json (~> 1.4.6) + nats (~> 0.4.22.beta.8) + resque (~> 1.20) + resque-status (~> 0.3.2) + ruby-hmac (~> 0.4.0) + rubyzip (~> 0.9.8) + sinatra (~> 1.2.3) + thin (~> 1.3.1) + uuidtools (~> 2.1.2) + vcap_common (>= 1.0.8) + vcap_logging (>= 1.0.2) + warden-client (~> 0.0.6) + warden-protocol (~> 0.0.5) + +PATH + remote: . + specs: + cf-oauth2-service (1.0.0) + cf-uaa-client + vcap_common + vcap_logging + vcap_services_base + +GEM + remote: http://rubygems.org/ + specs: + addressable (2.2.8) + bcrypt-ruby (2.1.4) + beefcake (0.3.7) + curb (0.7.18) + daemons (1.1.9) + data_objects (0.10.10) + addressable (~> 2.1) + datamapper (1.1.0) + dm-aggregates (= 1.1.0) + dm-constraints (= 1.1.0) + dm-core (= 1.1.0) + dm-migrations (= 1.1.0) + dm-serializer (= 1.1.0) + dm-timestamps (= 1.1.0) + dm-transactions (= 1.1.0) + dm-types (= 1.1.0) + dm-validations (= 1.1.0) + diff-lcs (1.1.3) + dm-aggregates (1.1.0) + dm-core (~> 1.1.0) + dm-constraints (1.1.0) + dm-core (~> 1.1.0) + dm-core (1.1.0) + addressable (~> 2.2.4) + dm-migrations (1.1.0) + dm-core (~> 1.1.0) + dm-serializer (1.1.0) + dm-core (~> 1.1.0) + fastercsv (~> 1.5.4) + json (~> 1.4.6) + dm-timestamps (1.1.0) + dm-core (~> 1.1.0) + dm-transactions (1.1.0) + dm-core (~> 1.1.0) + dm-types (1.1.0) + bcrypt-ruby (~> 2.1.4) + dm-core (~> 1.1.0) + fastercsv (~> 1.5.4) + json (~> 1.4.6) + stringex (~> 1.2.0) + uuidtools (~> 2.1.2) + dm-validations (1.1.0) + dm-core (~> 1.1.0) + do_sqlite3 (0.10.10) + data_objects (= 0.10.10) + em-http-request (1.0.0.beta.3) + addressable (>= 2.2.3) + em-socksify + eventmachine + http_parser.rb (>= 0.5.1) + em-socksify (0.1.0) + eventmachine + eventmachine_httpserver (0.2.1) + fastercsv (1.5.5) + highline (1.6.15) + http_parser.rb (0.5.3) + httpclient (2.3.0.1) + json (1.4.6) + json_pure (1.7.5) + launchy (2.1.0) + addressable (~> 2.2.6) + macaddr (1.6.1) + systemu (~> 2.5.0) + membrane (0.0.2) + mime-types (1.19) + multi_json (1.3.6) + multipart-post (1.1.5) + nats (0.4.24) + daemons (>= 1.1.5) + eventmachine (>= 0.12.10) + json_pure (>= 1.7.3) + thin (>= 1.3.1) + posix-spawn (0.3.6) + rack (1.4.1) + rake (0.9.2.2) + redis (3.0.2) + redis-namespace (1.2.1) + redis (~> 3.0.0) + redisk (0.2.2) + redis (>= 0.1.1) + redis-namespace (>= 0.1.0) + resque (1.23.0) + multi_json (~> 1.0) + redis-namespace (~> 1.0) + sinatra (>= 0.9.2) + vegas (~> 0.1.2) + resque-status (0.3.3) + redisk (>= 0.2.1) + resque (~> 1.19) + uuid (~> 2.3) + rest-client (1.6.7) + mime-types (>= 1.16) + rspec (2.10.0) + rspec-core (~> 2.10.0) + rspec-expectations (~> 2.10.0) + rspec-mocks (~> 2.10.0) + rspec-core (2.10.1) + rspec-expectations (2.10.0) + diff-lcs (~> 1.1.3) + rspec-mocks (2.10.1) + ruby-hmac (0.4.0) + rubyzip (0.9.9) + sinatra (1.2.8) + rack (~> 1.1) + tilt (>= 1.2.2, < 2.0) + stringex (1.2.2) + systemu (2.5.2) + thin (1.3.1) + daemons (>= 1.0.9) + eventmachine (>= 0.12.6) + rack (>= 1.0.0) + tilt (1.3.3) + uuid (2.3.5) + macaddr (~> 1.0) + uuidtools (2.1.3) + vegas (0.1.11) + rack (>= 1.0.0) + yajl-ruby (0.8.3) + +PLATFORMS + ruby + +DEPENDENCIES + cf-oauth2-service! + cf-uaa-client! + eventmachine! + rspec + vcap_common! + vcap_logging! + vcap_services_base! + warden-client! + warden-protocol! diff --git a/README.md b/README.md new file mode 100644 index 0000000..e689127 --- /dev/null +++ b/README.md @@ -0,0 +1,99 @@ +The project is a cloudfoundry service gateway exposing the kernel +[UAA][]. When you provision it (`vmc create-service`) an OAuth2 +client registration is created in the UAA. Then when you bind an +application to it (`vmc bind-service`), the app gets some credentials +in `VCAP_SERVICES` environment variable, e.g. + + VCAP_SERVICES={"oauth2-1.0":[{"name":"oauth2", "label":"oauth2-1.0", + "plan":"free", "tags":["uaa", "oauth2-1.0", "oauth2"], "credentials": + {"auth_server_url":"http://login.cloudfoundry.com", "token_server_url":"http://uaa.cloudfoundry.com", + "client_id":"b1366591-5456-4221-8563-9f8370ead694", + "client_secret":"af6c147d-5695-495a-bfdc-e7132c8b1dd2"}}]} + +The application can use the "credentials" field to drive an +authorization code flow and obtain an OAuth2 access token. The +default scope for a token is +`["openid", "cloud_controller.read", "cloud_controller.write"]` which +gives the application the ability to authenticate a user and obtain +basic profile information, and also to manage the users applications +and services in the cloud controller. + +[UAA]: http://github.com/cloudfoundry/uaa + +## Typical Log Output + +### Provision + + [2012-11-04 19:02:03.134148] gateway - pid=8970 tid=6f89 fid=17af DEBUG -- Provision request for label=test-1.0, plan=free, version=1.0 + [2012-11-04 19:02:03.134406] gateway - pid=8970 tid=6f89 fid=17af DEBUG -- [Test-Provisioner] Attempting to provision instance (request={:label=>"test-1.0", :name=>"test", :email=>"vcap_tester@vmware.com", :plan=>"free", :version=>"1.0"}) + [2012-11-04 19:02:03.134858] gateway - pid=8970 tid=6f89 fid=17af DEBUG -- Provisioned {:configuration=>{:plan=>"free", :version=>"1.0"}, :service_id=>"b5df21d7-ccfd-4a8e-adbe-55a2c3172de9", :credentials=>{"internal"=>{"name"=>"b5df21d7-ccfd-4a8e-adbe-55a2c3172de9"}}} + [2012-11-04 19:02:03.135036] gateway - pid=8970 tid=6f89 fid=17af DEBUG -- Reply status:200, headers:{"Content-Type"=>"application/json"}, body:{"configuration":{"plan":"free","version":"1.0"},"service_id":"b5df21d7-ccfd-4a8e-adbe-55a2c3172de9","credentials":{"internal":{"name":"b5df21d7-ccfd-4a8e-adbe-55a2c3172de9"}}} + +### Bind + + [2012-11-05 10:07:34.775173] gateway - pid=12579 tid=dc89 fid=23a0 DEBUG -- [Test-Provisioner] Attempting to bind to service b5df21d7-ccfd-4a8e-adbe-55a2c3172de9 + [2012-11-05 10:07:34.775694] gateway - pid=12579 tid=dc89 fid=23a0 DEBUG -- [Test-Provisioner] Binded: {:service_id=>"d5e8754d-b1dc-4562-9fce-eb7747460b89", :configuration=>{"plan"=>"free", "version"=>"1.0", "data"=>{"binding_options"=>{}}}, :credentials=>{"internal"=>{"name"=>"b5df21d7-ccfd-4a8e-adbe-55a2c3172de9"}}} + [2012-11-05 10:07:34.775903] gateway - pid=12579 tid=dc89 fid=23a0 DEBUG -- Reply status:200, headers:{"Content-Type"=>"application/json"}, body:{"service_id":"d5e8754d-b1dc-4562-9fce-eb7747460b89","configuration":{"plan":"free","version":"1.0","data":{"binding_options":{}}},"credentials":{"internal":{"name":"b5df21d7-ccfd-4a8e-adbe-55a2c3172de9"}}} + +### Unbind + + [2012-11-05 10:06:52.458826] gateway - pid=12579 tid=dc89 fid=23a0 INFO -- Unbind request for service_id=b5df21d7-ccfd-4a8e-adbe-55a2c3172de9 handle_id=a55a4fe5-5c3e-4403-960e-c78025e35324 + [2012-11-05 10:06:52.459112] gateway - pid=12579 tid=dc89 fid=23a0 DEBUG -- [Test-Provisioner] Attempting to unbind to service b5df21d7-ccfd-4a8e-adbe-55a2c3172de9 + [2012-11-05 10:06:52.459234] gateway - pid=12579 tid=dc89 fid=23a0 DEBUG -- Reply status:200, headers:{"Content-Type"=>"application/json"}, body:{} + + +## Steps to Register with the Cloud Controller + +### Provide a config file + +To register with the cloud controller you need to provide a config +file (the default has some values in it, but won't have the right +values for your environment). Then you can try and launch with, for +instance + + $ bin/gateway -c config/dev.yml + +Note that the services base code will require `/var/vcap/sys/run/LOCK` +to be writable. This is fixed in the `dsyer` fork so that the lock +file location can be overridden with an environment variable: + + $ LOCK_FILE=/tmp/LOCK bin/gateway -c config/dev.yml + +### NATS Registration + +NATS registration happens before contacting the Cloud Controller so if +you have problems connecting you are hosed, but you can disable it by +*not* providing an `mbus` entry in the local config. + +### Make the Cloud Controller aware of our offering + +The cloud controller has to be expecting us as well, so you need this +in `cloud_controller.yml` the first time you run the gateway (but not +subsequently): + + builtin_services: + test: 0xdeadbeef + +In a BOSH deployment you can do this by adding a snippet to the +manifest and then doing a `bosh deploy`: + + external_service_tokens: + test: 0xdeadbeef + +(Except there's a bug in the `cloud_controller` job where the template +doesn't expand the external service tokens into a hash before +iterating on them.) + +### Make sure the gateway is not registered as "core" + +The cloud controller will register the service, but you need it to be +registered wit the "core" provider, so *don't* specify that property +in the gateway config file. + +### Port Numbers + +The Cloud Controller database doesn't seem to get updated with the new +port if you change the gateweay, and the default is to pick an +ephemeral port. So it's best to fix the port in the gateway YML +config. + diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..f4bfd4b --- /dev/null +++ b/Rakefile @@ -0,0 +1,7 @@ +require "bundler/gem_tasks" +require "rspec/core/rake_task" + +RSpec::Core::RakeTask.new("test") do |test| + test.rspec_opts = ["--format", "documentation", "--colour"] + test.pattern = "**/*_spec.rb" +end diff --git a/app.rb b/app.rb new file mode 100644 index 0000000..752d0cd --- /dev/null +++ b/app.rb @@ -0,0 +1,5 @@ +# require 'sinatra' +$LOAD_PATH.unshift File.join(File.dirname(__FILE__), 'lib') +require 'service/gateway' + +CF::UAA::OAuth2Service::Gateway.new.start diff --git a/bin/oauth2-gateway b/bin/oauth2-gateway new file mode 100755 index 0000000..64d499a --- /dev/null +++ b/bin/oauth2-gateway @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby +# -*- mode: ruby -*- +# Copyright (c) 2009-2011 VMware, Inc. + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__) +require 'bundler/setup' +require 'vcap_services_base' + +$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib') +require 'service/gateway' +CF::UAA::OAuth2Service::Gateway.new.start diff --git a/config/oauth2_gateway.yml b/config/oauth2_gateway.yml new file mode 100644 index 0000000..cb5344b --- /dev/null +++ b/config/oauth2_gateway.yml @@ -0,0 +1,22 @@ +--- +cloud_controller_uri: api.vcap.me +service: + name: oauth2 + version: "1.0" + description: 'OAuth2 service' + plans: ['free'] + default_plan: 'free' + tags: ['oauth2','uaa'] + timeout: 60 + supported_versions: ["1.0"] + version_aliases: + current: "1.0" + uaa: http://uaa.vcap.me + login: http://uaa.vcap.me +index: 0 +mbus: nats://nats:nats@vcap:4222 +logging: + file: /tmp/gateway.log + level: debug +pid: /tmp/service.pid +token: 0xdeadbeef \ No newline at end of file diff --git a/lib/service.rb b/lib/service.rb new file mode 100644 index 0000000..cb85e2a --- /dev/null +++ b/lib/service.rb @@ -0,0 +1,3 @@ +require "service/version" +require "service/gateway" +require "service/provisioner" diff --git a/lib/service/gateway.rb b/lib/service/gateway.rb new file mode 100644 index 0000000..5a41449 --- /dev/null +++ b/lib/service/gateway.rb @@ -0,0 +1,17 @@ +require 'vcap_services_base' + +$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib') +require 'service/provisioner' + +class CF::UAA::OAuth2Service::Gateway < VCAP::Services::Base::Gateway + + def provisioner_class + CF::UAA::OAuth2Service::Provisioner + end + + def default_config_file + config_base_dir = ENV["CLOUD_FOUNDRY_CONFIG_PATH"] || File.join(File.dirname(__FILE__), '..', '..', 'config') + File.join(config_base_dir, 'oauth2_gateway.yml') + end + +end diff --git a/lib/service/provisioner.rb b/lib/service/provisioner.rb new file mode 100644 index 0000000..8b6d257 --- /dev/null +++ b/lib/service/provisioner.rb @@ -0,0 +1,188 @@ +#-- +# Cloud Foundry 2012.02.03 Beta +# Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved. +# +# This product is licensed to you under the Apache License, Version 2.0 (the "License"). +# You may not use this product except in compliance with the License. +# +# This product includes a number of subcomponents with +# separate copyright notices and license terms. Your use of these +# subcomponents is subject to the terms and conditions of the +# subcomponent's license, as noted in the LICENSE file. +#++ + +require 'uaa/token_issuer' +require 'uaa/client_reg' + +module CF::UAA::OAuth2Service +end + +class CF::UAA::OAuth2Service::Provisioner < VCAP::Services::Base::Provisioner + + DEFAULT_UAA_URL = "https://uaa.cloudfoundry.com" + DEFAULT_LOGIN_URL = "https://login.cloudfoundry.com" + + def service_name + "OAuth2" + end + + def initialize(options) + super(options) + @uaa_url = options[:service][:uaa] || DEFAULT_UAA_URL + @login_url = options[:service][:login] || options[:service][:uaa] || DEFAULT_LOGIN_URL + @client_id = options[:service][:client_id] || "kernelauth" + @client_secret = options[:service][:client_secret] || "kernelauthsecret" + @logger.debug("Initializing: #{options}") + @logger.info("UAA: #{@uaa_url}, Login: #{@login_url}") + @async = options[:service][:async] || true + end + + def provision_service(request, prov_handle=nil, &blk) + + @logger.debug("[#{service_description}] Attempting to provision instance (request=#{request.extract})") + + name = UUIDTools::UUID.random_create.to_s + plan = request.plan || "free" + version = request.version + + prov_req = request.extract.dup + prov_req[:plan] = plan + prov_req[:version] = version + # use old credentials to provision a service if provided. + prov_req[:credentials] = prov_handle["credentials"] if prov_handle + + credentials = gen_credentials(name) + svc = { + :configuration => prov_req, + :service_id => name, + :credentials => credentials + } + @logger.debug("Provisioned #{svc.inspect}") + @prov_svcs[svc[:service_id]] = svc + + blk.call(success(svc)) + + rescue => e + @logger.warn("Exception at provision_service #{e}") + blk.call(internal_fail) + + end + + def unprovision_service(instance_id, &blk) + + @logger.debug("[#{service_description}] Attempting to unprovision instance (instance id=#{instance_id})") + svc = @prov_svcs[instance_id] + raise ServiceError.new(ServiceError::NOT_FOUND, "instance_id #{instance_id}") if svc == nil + async do + client.delete(instance_id) + end + blk.call(success()) + bindings = find_all_bindings(instance_id) + @prov_svcs.delete(instance_id) + bindings.each do |b| + @prov_svcs.delete(b[:service_id]) + end + + rescue => e + @logger.warn("Exception at unprovision_service #{e}") + blk.call(internal_fail) + + end + + def bind_instance(instance_id, binding_options, bind_handle=nil, &blk) + + @logger.debug("[#{service_description}] Attempting to bind to service #{instance_id}") + svc = @prov_svcs[instance_id] + raise ServiceError.new(ServiceError::NOT_FOUND, "instance_id #{instance_id}") if svc == nil + + service_id = nil + if bind_handle + service_id = bind_handle["service_id"] + else + service_id = UUIDTools::UUID.random_create.to_s + end + + # Save binding-options in :data section of configuration + config = svc[:configuration].nil? ? {} : svc[:configuration].clone + config['data'] ||= {} + config['data']['binding_options'] = binding_options + credentials = svc[:credentials].dup + update_redirect_uri(credentials, config) + res = { + :service_id => service_id, + :configuration => config, + :credentials => credentials + } + @logger.debug("[#{service_description}] Bound: #{res.inspect}") + @prov_svcs[service_id] = res + blk.call(success(res)) + + rescue => e + @logger.warn("Exception at bind_instance #{e}") + blk.call(internal_fail) + + end + + def unbind_instance(instance_id, handle_id, binding_options, &blk) + + @logger.debug("[#{service_description}] Attempting to unbind to service #{instance_id}") + + svc = @prov_svcs[instance_id] + raise ServiceError.new(ServiceError::NOT_FOUND, "instance_id #{instance_id}") if svc == nil + + handle = @prov_svcs[handle_id] + raise ServiceError.new(ServiceError::NOT_FOUND, "handle_id #{handle_id}") if handle.nil? + + @prov_svcs.delete(handle_id) + config = svc[:configuration].nil? ? {} : svc[:configuration].clone + credentials = svc[:credentials] + update_redirect_uri(credentials, config) + blk.call(success()) + + end + + def update_redirect_uri(credentials, config) + end + + def client + return @client if @client + token = CF::UAA::TokenIssuer.new(@uaa_url, @client_id, @client_secret).client_credentials_grant + @logger.info("Client token: #{token}") + @client = CF::UAA::ClientReg.new(@uaa_url, token.auth_header) + @client.async = @async + @client + end + + def async(&blk) + if @async + Fiber.new { + blk.call() + }.resume + else + blk.call() + end + rescue + @logger.info("Failed. Retrying.") + retry + end + + def gen_credentials(name) + client_secret = UUIDTools::UUID.random_create.to_s + async() do + client.create(:client_id=>name, :client_secret=>client_secret, + :scope => ["cloud_controller.read", "cloud_controller.write", "openid"], + :authorized_grant_types => ["authorization_code", "refresh_token"], + :access_token_validity => 10*60, + :refresh_token_validity => 7*24*60*60) + end + # TODO: add redirect uri + credentials = { + "auth_server_url" => "#{@login_url}", + "token_server_url" => "#{@uaa_url}", + "client_id" => name, + "client_secret" => client_secret + } + end + +end + diff --git a/lib/service/version.rb b/lib/service/version.rb new file mode 100644 index 0000000..f523813 --- /dev/null +++ b/lib/service/version.rb @@ -0,0 +1,7 @@ +module CF + module UAA + module OAuth2Service + VERSION = "1.0.0" + end + end +end diff --git a/oauth2_service.gemspec b/oauth2_service.gemspec new file mode 100644 index 0000000..52466c7 --- /dev/null +++ b/oauth2_service.gemspec @@ -0,0 +1,27 @@ +# -*- encoding: utf-8 -*- +$:.push File.expand_path("../lib", __FILE__) +require "service/version" + +Gem::Specification.new do |s| + s.name = "cf-oauth2-service" + s.version = CF::UAA::OAuth2Service::VERSION + s.authors = ["Dave Syer"] + s.email = ["dsyer@vmware.com"] + s.homepage = "" + s.summary = %q{OAuth2 service for Cloud Foundry} + s.description = %q{OAuth2 service for Cloud Foundry using the kernel UAA as a provider} + + s.rubyforge_project = "cf-oauth2-service" + + s.files = `git ls-files`.split("\n") + s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") + s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } + s.require_paths = ["lib"] + + # specify any dependencies here; for example: + s.add_development_dependency "rspec" + s.add_runtime_dependency "vcap_common" + s.add_runtime_dependency 'vcap_logging' + s.add_runtime_dependency "vcap_services_base" + s.add_runtime_dependency "cf-uaa-client" +end diff --git a/spec/gateway_spec.rb b/spec/gateway_spec.rb new file mode 100644 index 0000000..d96e5aa --- /dev/null +++ b/spec/gateway_spec.rb @@ -0,0 +1,26 @@ +#-- +# Cloud Foundry 2012.02.03 Beta +# Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved. +# +# This product is licensed to you under the Apache License, Version 2.0 (the "License"). +# You may not use this product except in compliance with the License. +# +# This product includes a number of subcomponents with +# separate copyright notices and license terms. Your use of these +# subcomponents is subject to the terms and conditions of the +# subcomponent's license, as noted in the LICENSE file. +#++ + +require 'spec_helper' + +module CF::UAA::OAuth2Service + + describe Gateway do + + it "should have a default config file" do + Gateway.new().default_config_file.should_not be_nil + end + + end + +end diff --git a/spec/provisioner_spec.rb b/spec/provisioner_spec.rb new file mode 100644 index 0000000..6800379 --- /dev/null +++ b/spec/provisioner_spec.rb @@ -0,0 +1,168 @@ +#-- +# Cloud Foundry 2012.02.03 Beta +# Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved. +# +# This product is licensed to you under the Apache License, Version 2.0 (the "License"). +# You may not use this product except in compliance with the License. +# +# This product includes a number of subcomponents with +# separate copyright notices and license terms. Your use of these +# subcomponents is subject to the terms and conditions of the +# subcomponent's license, as noted in the LICENSE file. +#++ + +require 'spec_helper' + +module CF::UAA + + class TokenIssuer + def client_credentials_grant + Token.new(:token_type=>"Bearer", :access_token=>"FOO") + end + end + + class ClientReg + + class << self + attr_accessor :simulate_fail + end + + def create(info) + if ClientReg::simulate_fail + ClientReg::simulate_fail = false + raise TargetError + end + result = info.dup + result.delete(:client_secret) + result + end + + def delete(id) + end + + end + +end + +module CF::UAA::OAuth2Service + + describe Provisioner do + + include SpecHelper + + before :all do + EM.run do + @provisioner = Provisioner.new(:service=>service_config, :logger=>logger) + EM.stop + end + end + + subject { @provisioner } + + it "should have a service name" do + @provisioner.service_name.should_not be_nil + end + + it "should generate credentials" do + credentials = @provisioner.gen_credentials("foo") + credentials["client_id"].should == "foo" + end + + it "should recover from failure" do + CF::UAA::ClientReg.simulate_fail = true + credentials = @provisioner.gen_credentials("foo") + credentials["client_id"].should == "foo" + end + + context "when synchronous" do + + before :all do + config = service_config.dup + config[:async] = false + EM.run do + @provisioner = Provisioner.new(:service=>config, :logger=>logger) + EM.stop + end + end + + it "should not require an existing fiber" do + credentials = @provisioner.gen_credentials("foo") + credentials["client_id"].should == "foo" + end + + end + + context "when provisioning" do + + it "should create a new client with credentials" do + request = VCAP::Services::Api::GatewayProvisionRequest.new(:label=>"test-1.0", :name=>"test", :plan=>"free", :email=>"vcap_tester@vmware.com", :version=>"1.0") + @provisioner.provision_service(request) do |svc| + puts "Response: #{svc}" + svc["success"].should be_true + svc["response"][:configuration][:email].should == "vcap_tester@vmware.com" + svc["response"][:credentials].should_not be_nil + svc["response"][:credentials]["auth_server_url"].should_not be_nil + svc["response"][:credentials]["client_id"].should_not be_nil + end + end + + end + + context "when provisioned" do + + before :each do + + request = VCAP::Services::Api::GatewayProvisionRequest.new(:label=>"test-1.0", :name=>"test", :plan=>"free", :email=>"vcap_tester@vmware.com", :version=>"1.0") + + @instance_id == nil + @provisioner.provision_service(request) do |svc| + @instance_id = svc["response"][:service_id] + end + + @instance_id.should_not be_nil + + @handle_id == nil + @provisioner.provision_service(request) do |svc| + @handle_id = svc["response"][:service_id] + end + + @handle_id.should_not be_nil + + end + + it "should fail on non-existent client" do + @provisioner.unprovision_service("foo") do |svc| + svc["success"].should be_false + end + end + + it "should remove existing client successfully" do + + @provisioner.unprovision_service(@instance_id) do |svc| + svc["success"].should be_true + end + + end + + it "should be able to bind" do + + @provisioner.bind_instance(@instance_id, {}) do |svc| + svc["success"].should be_true + svc["response"][:configuration][:email].should == "vcap_tester@vmware.com" + end + + end + + it "should be able to unbind" do + + @provisioner.unbind_instance(@instance_id, @handle_id, {}) do |svc| + svc["success"].should be_true + end + + end + + end + + end + +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..4a9099f --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,31 @@ +#-- +# Cloud Foundry 2012.02.03 Beta +# Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved. +# +# This product is licensed to you under the Apache License, Version 2.0 (the "License"). +# You may not use this product except in compliance with the License. +# +# This product includes a number of subcomponents with +# separate copyright notices and license terms. Your use of these +# subcomponents is subject to the terms and conditions of the +# subcomponent's license, as noted in the LICENSE file. +#++ + +require 'rspec' +require 'service' +require "logger" + +module SpecHelper + + def logger + @logger ||= Logger.new(STDOUT) + end + + def service_config + config_file = ENV['CONFIG_FILE'] || CF::UAA::OAuth2Service::Gateway.new().default_config_file + @service_config ||= YAML.load_file(config_file) + @service_config.delete(:mbus) unless ENV['CONFIG_FILE'] + @service_config + end + +end