gds-sso 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|