gds-sso 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +4 -0
- data/README.md +35 -0
- data/Rakefile +10 -0
- data/lib/gds-sso.rb +41 -0
- data/lib/gds-sso/config.rb +23 -0
- data/lib/gds-sso/controller_methods.rb +30 -0
- data/lib/gds-sso/failure_app.rb +38 -0
- data/lib/gds-sso/omniauth_strategy.rb +42 -0
- data/lib/gds-sso/routes.rb +20 -0
- data/lib/gds-sso/user.rb +23 -0
- data/lib/gds-sso/warden_config.rb +43 -0
- data/test/test_helper.rb +5 -0
- data/test/test_omniauth_strategy.rb +21 -0
- data/test/test_user.rb +19 -0
- metadata +119 -0
data/Gemfile
ADDED
data/README.md
ADDED
@@ -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
|
data/Rakefile
ADDED
data/lib/gds-sso.rb
ADDED
@@ -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
|
data/lib/gds-sso/user.rb
ADDED
@@ -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
|
data/test/test_helper.rb
ADDED
@@ -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
|
data/test/test_user.rb
ADDED
@@ -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
|