gds-sso 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in gds-sso.gemspec
4
+ gemspec
@@ -0,0 +1,35 @@
1
+ ## Introduction
2
+
3
+ GDS-SSO provides everything needed to integrate an application with the sign-on-o-tron single-sign-on
4
+ (https://github.com/alphagov/sign-on-o-tron) as used by the Government Digital Service, though it
5
+ will probably also work with a range of other oauth2 providers.
6
+
7
+ It is a wrapper around omniauth that adds a 'strategy' for oAuth2 integration against sign-on-o-tron,
8
+ and the necessary controller to support that request flow.
9
+
10
+ For more details on OmniAuth and oAuth2 integration see https://github.com/intridea/omniauth
11
+
12
+
13
+ ## Integration with a Rails 3+ app
14
+
15
+ To use gds-sso tou will need an oauth client ID and secret for sign-on-o-tron or a compatible system.
16
+ These can be provided by one of the team with admin access to sign-on-o-tron.
17
+
18
+ Then include the gem in your Gemfile:
19
+
20
+ gem 'gds-sso', :git => 'https://github.com/alphagov/gds-sso.git'
21
+
22
+ Create a `config/initializers/gds-sso.rb` that looks like:
23
+
24
+ GDS::SSO.config do |config|
25
+ config.user_model = 'User'
26
+ # set up ID and Secret in a way which doesn't require it to be checked in to source control...
27
+ config.oauth_id = ENV['OAUTH_ID']
28
+ config.oauth_secret = ENV['OAUTH_SECRET']
29
+ # optional config for location of sign-on-o-tron
30
+ config.oauth_root_url = "http://localhost:3001"
31
+ end
32
+
33
+ The user model needs to respond to klass.find_by_uid(uid), and must include the GDS::SSO::User module.
34
+
35
+ You also need to include `GDS::SSO::ControllerMethods` in your ApplicationController
@@ -0,0 +1,10 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake/testtask'
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << "test"
8
+ t.test_files = FileList['test/test*.rb']
9
+ t.verbose = true
10
+ end
@@ -0,0 +1,41 @@
1
+ require 'rails'
2
+
3
+ require 'gds-sso/config'
4
+ require 'gds-sso/omniauth_strategy'
5
+ require 'gds-sso/warden_config'
6
+ require 'gds-sso/routes'
7
+
8
+ module GDS
9
+ module SSO
10
+ autoload :FailureApp, 'gds-sso/failure_app'
11
+ autoload :ControllerMethods, 'gds-sso/controller_methods'
12
+ autoload :User, 'gds-sso/user'
13
+
14
+ def self.config
15
+ yield GDS::SSO::Config
16
+ end
17
+
18
+ def self.default_strategy
19
+ if ['development', 'test'].include?(Rails.env) && ENV['GDS_SSO_STRATEGY'] != 'real'
20
+ :mock_gds_sso
21
+ else
22
+ :gds_sso
23
+ end
24
+ end
25
+
26
+ class Engine < ::Rails::Engine
27
+ # Force routes to be loaded if we are doing any eager load.
28
+ # TODO - check this one - Stolen from Devise because it looked sensible...
29
+ config.before_eager_load { |app| app.reload_routes! }
30
+
31
+ config.app_middleware.use ::OmniAuth::Builder do
32
+ provider :gds, GDS::SSO::Config.oauth_id, GDS::SSO::Config.oauth_secret
33
+ end
34
+
35
+ config.app_middleware.use Warden::Manager do |manager|
36
+ manager.default_strategies GDS::SSO.default_strategy
37
+ manager.failure_app = GDS::SSO::FailureApp
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,23 @@
1
+ module GDS
2
+ module SSO
3
+ module Config
4
+ # Name of the User class
5
+ mattr_accessor :user_model
6
+ @@user_model = "User"
7
+
8
+ # OAuth ID
9
+ mattr_accessor :oauth_id
10
+
11
+ # OAuth Secret
12
+ mattr_accessor :oauth_secret
13
+
14
+ # Location of the OAuth server
15
+ mattr_accessor :oauth_root_url
16
+ @@oauth_root_url = "http://localhost:3001"
17
+
18
+ def self.user_klass
19
+ user_model.to_s.constantize
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,30 @@
1
+ module GDS
2
+ module SSO
3
+ module ControllerMethods
4
+ def authenticate_user!
5
+ warden.authenticate!
6
+ end
7
+
8
+ def user_signed_in?
9
+ warden.authenticated?
10
+ end
11
+
12
+ def current_user
13
+ warden.user if user_signed_in?
14
+ end
15
+
16
+ def log_out
17
+ warden.log_out
18
+ end
19
+
20
+ def warden
21
+ request.env['warden']
22
+ end
23
+
24
+ def self.included(base)
25
+ base.helper_method :user_signed_in?
26
+ base.helper_method :current_user
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,38 @@
1
+ require "action_controller/metal"
2
+ require 'rails'
3
+
4
+ # Failure application that will be called every time :warden is thrown from
5
+ # any strategy or hook.
6
+ module GDS
7
+ module SSO
8
+ class FailureApp < ActionController::Metal
9
+ include ActionController::RackDelegation
10
+ include ActionController::UrlFor
11
+ include ActionController::Redirecting
12
+ include Rails.application.routes.url_helpers
13
+
14
+ def self.call(env)
15
+ action(:respond).call(env)
16
+ end
17
+
18
+ def respond
19
+ redirect
20
+ end
21
+
22
+ def redirect
23
+ store_location!
24
+ redirect_to '/auth/gds'
25
+ end
26
+
27
+ # Stores requested uri to redirect the user after signing in. We cannot use
28
+ # scoped session provided by warden here, since the user is not authenticated
29
+ # yet, but we still need to store the uri based on scope, so different scopes
30
+ # would never use the same uri to redirect.
31
+
32
+ # TOTALLY NOT DOING THE SCOPE THING. PROBABLY SHOULD.
33
+ def store_location!
34
+ session["return_to"] = env['warden.options'][:attempted_path] if request.get?
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,42 @@
1
+ require 'omniauth/oauth'
2
+ require 'multi_json'
3
+
4
+ # Authenticate to GDS with OAuth 2.0 and retrieve
5
+ # basic user information.
6
+ #
7
+ # @example Basic Usage
8
+ # use OmniAuth::Builder :gds, 'API Key', 'Secret Key'
9
+
10
+ class OmniAuth::Strategies::Gds < OmniAuth::Strategies::OAuth2
11
+ # @param [Rack Application] app standard middleware application parameter
12
+ # @param [String] api_key the application id as [provided by GDS]
13
+ # @param [String] secret_key the application secret as [provided by Bitly]
14
+ def initialize(app, api_key = nil, secret_key = nil, options = {}, &block)
15
+ client_options = {
16
+ :site => "#{GDS::SSO::Config.oauth_root_url}/",
17
+ :authorize_url => "#{GDS::SSO::Config.oauth_root_url}/oauth/authorize",
18
+ :token_url => "#{GDS::SSO::Config.oauth_root_url}/oauth/access_token",
19
+ :access_token_url => "#{GDS::SSO::Config.oauth_root_url}/oauth/access_token"
20
+ }
21
+
22
+ super(app, :gds, api_key, secret_key, client_options, options, &block)
23
+ end
24
+
25
+ protected
26
+
27
+ def fetch_user_data
28
+ @access_token.get('/user.json')
29
+ end
30
+
31
+ def user_hash
32
+ @user_hash ||= MultiJson.decode(fetch_user_data)['user']
33
+ end
34
+
35
+ def build_auth_hash
36
+ {'uid' => user_hash['uid'], 'user_info' => {'name' => user_hash['name'], 'email' => user_hash['email']}, 'extra' => {'user_hash' => user_hash}}
37
+ end
38
+
39
+ def auth_hash
40
+ OmniAuth::Utils.deep_merge(super, build_auth_hash)
41
+ end
42
+ end
@@ -0,0 +1,20 @@
1
+ module ActionDispatch::Routing
2
+ class Mapper
3
+ # Allow you to add authentication request from the router:
4
+ #
5
+ # authenticate(:user) do
6
+ # resources :post
7
+ # end
8
+ #
9
+ # Stolen from devise
10
+ def authenticate(scope)
11
+ constraint = lambda do |request|
12
+ request.env["warden"].authenticate!(:scope => scope)
13
+ end
14
+
15
+ constraints(constraint) do
16
+ yield
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,23 @@
1
+ require 'active_support/concern'
2
+
3
+ module GDS
4
+ module SSO
5
+ module User
6
+ def self.user_params_from_auth_hash(auth_hash)
7
+ {'uid' => auth_hash['uid'], 'email' => auth_hash['user_info']['email'], 'name' => auth_hash['user_info']['name'], 'version' => auth_hash['extra']['user_hash']['version']}
8
+ end
9
+
10
+ extend ActiveSupport::Concern
11
+
12
+ module ClassMethods
13
+ def find_for_gds_oauth(auth_hash)
14
+ if user = self.find_by_uid(auth_hash["uid"])
15
+ user
16
+ else # Create a new user.
17
+ self.create!(GDS::SSO::User.user_params_from_auth_hash(auth_hash))
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,43 @@
1
+ require 'warden'
2
+ require 'omniauth/oauth'
3
+
4
+ Warden::Manager.serialize_into_session do |user|
5
+ user.uid
6
+ end
7
+
8
+ Warden::Manager.serialize_from_session do |uid|
9
+ GDS::SSO::Config.user_klass.find_by_uid(uid)
10
+ end
11
+
12
+ Warden::Strategies.add(:gds_sso) do
13
+ def valid?
14
+ true
15
+ end
16
+
17
+ def authenticate!
18
+ if request.env['omniauth.auth'].nil?
19
+ fail!("No credentials, bub")
20
+ else
21
+ user = prep_user(request.env['omniauth.auth'])
22
+ success!(user)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def prep_user(auth_hash)
29
+ user = GDS::SSO::Config.user_klass.find_for_gds_oauth(auth_hash)
30
+ fail!("Couldn't process credentials") unless user
31
+ user
32
+ end
33
+ end
34
+
35
+ Warden::Strategies.add(:mock_gds_sso) do
36
+ def valid?
37
+ true
38
+ end
39
+
40
+ def authenticate!
41
+ success!(GDS::SSO::Config.user_klass.first)
42
+ end
43
+ end
@@ -0,0 +1,5 @@
1
+ require 'bundler'
2
+ Bundler.setup :default, :development, :test
3
+
4
+ require 'test/unit'
5
+ require 'mocha'
@@ -0,0 +1,21 @@
1
+ require 'test_helper'
2
+ require 'json'
3
+ require 'gds-sso'
4
+ require 'gds-sso/omniauth_strategy'
5
+
6
+ class TestOmniAuthStrategy < Test::Unit::TestCase
7
+ def setup
8
+ @strategy = OmniAuth::Strategies::Gds.new(:gds, 'client_id', 'client_secret')
9
+ @strategy.stubs(:fetch_user_data).returns({'user' => {'uid' => 'abcde', 'version' => 1, 'name' => 'Matt Patterson', 'email' => 'matt@alphagov.co.uk', 'github' => 'fidothe', 'twitter' => 'fidothe'}}.to_json)
10
+ end
11
+
12
+ def test_basic_auth_hash_structure
13
+ assert_equal 'Matt Patterson', @strategy.send(:build_auth_hash)['user_info']['name']
14
+ assert_equal 'matt@alphagov.co.uk', @strategy.send(:build_auth_hash)['user_info']['email']
15
+ end
16
+
17
+ def test_extra_auth_hash_structure
18
+ expected = {'uid' => 'abcde', 'version' => 1, 'name' => 'Matt Patterson', 'email' => 'matt@alphagov.co.uk', 'github' => 'fidothe', 'twitter' => 'fidothe'}
19
+ assert_equal expected, @strategy.send(:build_auth_hash)['extra']['user_hash']
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ require 'test_helper'
2
+ require 'gds-sso/user'
3
+
4
+ class TestUser < Test::Unit::TestCase
5
+ def setup
6
+ @auth_hash = {
7
+ 'provider' => 'gds',
8
+ 'uid' => 'abcde',
9
+ 'credentials' => {'token' => 'abcdefg', 'secret' => 'abcdefg'},
10
+ 'user_info' => {'name' => 'Matt Patterson', 'email' => 'matt@alphagov.co.uk'},
11
+ 'extra' => {'user_hash' => {'uid' => 'abcde', 'version' => 1, 'name' => 'Matt Patterson', 'email' => 'matt@alphagov.co.uk', 'github' => 'fidothe', 'twitter' => 'fidothe'}}
12
+ }
13
+ end
14
+
15
+ def test_user_params_creation
16
+ expected = {'uid' => 'abcde', 'version' => 1, 'name' => 'Matt Patterson', 'email' => 'matt@alphagov.co.uk'}
17
+ assert_equal expected, GDS::SSO::User.user_params_from_auth_hash(@auth_hash)
18
+ end
19
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gds-sso
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Matt Patterson
9
+ - James Stewart
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2011-11-01 00:00:00.000000000Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rails
17
+ requirement: &70320597236800 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: 3.0.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *70320597236800
26
+ - !ruby/object:Gem::Dependency
27
+ name: warden
28
+ requirement: &70320597236420 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *70320597236420
37
+ - !ruby/object:Gem::Dependency
38
+ name: oa-oauth
39
+ requirement: &70320597236000 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ type: :runtime
46
+ prerelease: false
47
+ version_requirements: *70320597236000
48
+ - !ruby/object:Gem::Dependency
49
+ name: rake
50
+ requirement: &70320597235500 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: 0.9.2
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: *70320597235500
59
+ - !ruby/object:Gem::Dependency
60
+ name: mocha
61
+ requirement: &70320597235040 !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ~>
65
+ - !ruby/object:Gem::Version
66
+ version: 0.9.0
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: *70320597235040
70
+ description: Client for GDS' OAuth 2-based SSO
71
+ email:
72
+ - matt@constituentparts.com
73
+ - james.stewart@digital.cabinet-office.gov.uk
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - lib/gds-sso/config.rb
79
+ - lib/gds-sso/controller_methods.rb
80
+ - lib/gds-sso/failure_app.rb
81
+ - lib/gds-sso/omniauth_strategy.rb
82
+ - lib/gds-sso/routes.rb
83
+ - lib/gds-sso/user.rb
84
+ - lib/gds-sso/warden_config.rb
85
+ - lib/gds-sso.rb
86
+ - README.md
87
+ - Gemfile
88
+ - Rakefile
89
+ - test/test_helper.rb
90
+ - test/test_omniauth_strategy.rb
91
+ - test/test_user.rb
92
+ homepage: https://github.com/alphagov/gds-sso
93
+ licenses: []
94
+ post_install_message:
95
+ rdoc_options: []
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubyforge_project: gds-sso
112
+ rubygems_version: 1.8.10
113
+ signing_key:
114
+ specification_version: 3
115
+ summary: Client for GDS' OAuth 2-based SSO
116
+ test_files:
117
+ - test/test_helper.rb
118
+ - test/test_omniauth_strategy.rb
119
+ - test/test_user.rb