From f339848ac0c98188b0fc746935324f59215d26f2 Mon Sep 17 00:00:00 2001 From: Jamie Cobbett Date: Fri, 20 Jul 2012 11:49:15 +0100 Subject: [PATCH] First cut of code for pushing permissions to apps --- Gemfile | 3 ++ Gemfile.lock | 15 ++++++ app/models/enhancements/application.rb | 5 ++ config/initializers/gds_api_credentials.rb | 5 ++ lib/gds_api/sso.rb | 16 ++++++ lib/propagate_permissions.rb | 42 +++++++++++++++ test/test_helper.rb | 9 +++- test/unit/propagate_permissions_test.rb | 60 ++++++++++++++++++++++ 8 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 config/initializers/gds_api_credentials.rb create mode 100644 lib/gds_api/sso.rb create mode 100644 lib/propagate_permissions.rb create mode 100644 test/unit/propagate_permissions_test.rb diff --git a/Gemfile b/Gemfile index 399c2390b..5f367a678 100644 --- a/Gemfile +++ b/Gemfile @@ -22,6 +22,8 @@ gem 'passphrase_entropy', git: "git://github.com/alphagov/passphrase_entropy.git gem 'doorkeeper' +gem "gds-api-adapters", "0.2.1" + group :development do gem 'sqlite3' end @@ -31,5 +33,6 @@ group :test do gem 'database_cleaner' gem 'factory_girl_rails' gem 'shoulda' + gem 'webmock' end diff --git a/Gemfile.lock b/Gemfile.lock index 47f179fd0..ecec7d675 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,6 +7,7 @@ GIT GEM remote: https://rubygems.org/ specs: + PriorityQueue (0.1.2) actionmailer (3.1.3) actionpack (= 3.1.3) mail (~> 2.3.0) @@ -35,6 +36,7 @@ GEM activesupport (= 3.1.3) activesupport (3.1.3) multi_json (~> 1.0) + addressable (2.2.8) arel (2.2.3) aws-ses (0.4.4) builder @@ -52,6 +54,7 @@ GEM xpath (~> 0.1.4) childprocess (0.3.1) ffi (~> 1.0.6) + crack (0.3.1) cucumber (1.1.9) builder (>= 2.1.2) diff-lcs (>= 1.1.2) @@ -83,6 +86,10 @@ GEM factory_girl (~> 3.1.0) railties (>= 3.0.0) ffi (1.0.11) + gds-api-adapters (0.2.1) + lrucache (~> 0.1.1) + null_logger + plek gherkin (2.9.3) json (>= 1.4.6) hike (1.2.1) @@ -91,6 +98,8 @@ GEM railties (~> 3.0) thor (~> 0.14) json (1.6.6) + lrucache (0.1.3) + PriorityQueue (~> 0.1.2) macaddr (1.6.1) systemu (~> 2.5.0) mail (2.3.3) @@ -101,6 +110,7 @@ GEM multi_json (1.2.0) mysql2 (0.3.11) nokogiri (1.5.2) + null_logger (0.0.1) orm_adapter (0.0.7) plek (0.1.20) builder @@ -160,6 +170,9 @@ GEM macaddr (~> 1.0) warden (1.1.1) rack (>= 1.0) + webmock (1.8.7) + addressable (>= 2.2.7) + crack (>= 0.1.7) xml-simple (1.1.1) xpath (0.1.4) nokogiri (~> 1.3) @@ -176,6 +189,7 @@ DEPENDENCIES doorkeeper exception_notification factory_girl_rails + gds-api-adapters (= 0.2.1) jquery-rails mysql2 passphrase_entropy! @@ -186,3 +200,4 @@ DEPENDENCIES shoulda sqlite3 uuid + webmock diff --git a/app/models/enhancements/application.rb b/app/models/enhancements/application.rb index 725d177fd..5cd230417 100644 --- a/app/models/enhancements/application.rb +++ b/app/models/enhancements/application.rb @@ -11,4 +11,9 @@ def self.default_permission_strings def supported_permission_strings self.class.default_permission_strings + supported_permissions.order(:name).map(&:name) end + + def url_without_path + parsed_url = URI.parse(redirect_uri) + url_without_path = "#{parsed_url.scheme}://#{parsed_url.host}:#{parsed_url.port}" + end end \ No newline at end of file diff --git a/config/initializers/gds_api_credentials.rb b/config/initializers/gds_api_credentials.rb new file mode 100644 index 000000000..e7902eb24 --- /dev/null +++ b/config/initializers/gds_api_credentials.rb @@ -0,0 +1,5 @@ +GDS_API_CREDENTIALS = { + basic_auth: { + user: 'api', + password: 'defined_on_rollout_not' + }} diff --git a/lib/gds_api/sso.rb b/lib/gds_api/sso.rb new file mode 100644 index 000000000..7ff8634f8 --- /dev/null +++ b/lib/gds_api/sso.rb @@ -0,0 +1,16 @@ +require 'gds_api/base' + +class GdsApi::SSO < GdsApi::Base + def initialize(options) + super(nil, options) + end + + def update_user(user) + put_json!("#{base_url}/user", JSON.parse(user)) + end + + private + def base_url + "#{@endpoint}/auth/gds/api" + end +end \ No newline at end of file diff --git a/lib/propagate_permissions.rb b/lib/propagate_permissions.rb new file mode 100644 index 000000000..eba58000c --- /dev/null +++ b/lib/propagate_permissions.rb @@ -0,0 +1,42 @@ +class PropagatePermissions + def initialize(permissions) + @user = permissions.first.user + @applications = permissions.map(&:application) + end + + def attempt + results = { successes: [], failures: [] } + @applications.each do |application| + begin + update_application(@user, application) + results[:successes] << { application: application } + rescue URI::InvalidURIError + results[:failures] << { application: application, message: "Haven't got a valid URL for that app.", technical: "URL I have is: #{application.redirect_uri}" } + rescue GdsApi::EndpointNotFound, SocketError => e + results[:failures] << { application: application, message: "Couldn't find the app. Maybe the app is down?", technical: e.message } + rescue GdsApi::TimedOutException + results[:failures] << { application: application, message: "Timed out. Maybe the app is down?" } + rescue GdsApi::HTTPErrorResponse => e + message = case e.code + when 404 + "This app doesn't seem to support syncing of permissions." + when 502 + "Couldn't find the app. Maybe the app is down?" + else + e.message + end + results[:failures] << { application: application, message: message, technical: "HTTP status code was: #{e.code}" } + rescue GdsApi::BaseError, StandardError => e + results[:failures] << { application: application, message: e.message } + end + end + results + end + + private + def update_application(user, application) + options = { endpoint_url: application.url_without_path }.merge(GDS_API_CREDENTIALS) + api = GdsApi::SSO.new(options) + api.update_user(user.to_sensible_json) + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 8f73ec4c6..e0d135e08 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -3,15 +3,22 @@ require 'rails/test_help' require 'shoulda' +require 'webmock/minitest' + +WebMock.disable_net_connect!(:allow_localhost => true) class ActiveSupport::TestCase # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. # # Note: You'll currently still have to declare fixtures explicitly in integration tests # -- they do not yet inherit this setting - fixtures :all + # fixtures :all # Add more helper methods to be used by all tests here... + + teardown do + WebMock.reset! + end end class ActionController::TestCase diff --git a/test/unit/propagate_permissions_test.rb b/test/unit/propagate_permissions_test.rb new file mode 100644 index 000000000..55fcfd4e8 --- /dev/null +++ b/test/unit/propagate_permissions_test.rb @@ -0,0 +1,60 @@ +require 'test_helper' + +class PropagatePermissionsTest < ActiveSupport::TestCase + + def url_for_app(application) + url = URI.parse(application.redirect_uri) + "http://api:defined_on_rollout_not@#{url.host}/auth/gds/api/user" + end + + setup do + @user = FactoryGirl.create(:user) + @application = FactoryGirl.create(:application, redirect_uri: "http://app.com/callback") + @permission = FactoryGirl.create(:permission, + application: @application, + user: @user, + permissions: ["ba"]) + end + + should "send a PUT to the related app with the user.json as in the OAuth exchange" do + expected_body = @user.to_sensible_json + expected_url = + request = stub_request(:put, url_for_app(@application)).with(body: expected_body) + PropagatePermissions.new([@permission]).attempt + assert_requested request + end + + should "return a structure of successful and failed pushes" do + not_supported_yet_app = FactoryGirl.create(:application, redirect_uri: "http://not-supported-yet.com/callback") + FactoryGirl.create(:permission, + application: not_supported_yet_app, + user: @user, + permissions: ["ba"]) + + slow_app = FactoryGirl.create(:application, redirect_uri: "http://slow.com/callback") + FactoryGirl.create(:permission, + application: slow_app, + user: @user, + permissions: ["ba"]) + + stub_request(:put, url_for_app(@application)).to_return(status: 200) + stub_request(:put, url_for_app(not_supported_yet_app)).to_return(status: 404) + stub_request(:put, url_for_app(slow_app)).to_timeout + + results = PropagatePermissions.new(@user.permissions).attempt + + assert_equal [{ application: @application }], results[:successes] + expected_failures = [ + { + application: not_supported_yet_app, + message: "This app doesn't seem to support syncing of permissions.", + technical: "HTTP status code was: 404" + }, + { + application: slow_app, + message: "Timed out. Maybe the app is down?" + } + ] + assert_equal expected_failures, results[:failures] + end +end