gds-sso 14.3.0 → 16.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +23 -56
- data/Rakefile +11 -6
- data/app/controllers/api/user_controller.rb +30 -28
- data/app/controllers/authentications_controller.rb +4 -6
- data/app/views/layouts/unauthorised.html.erb +1 -1
- data/config/routes.rb +7 -6
- data/lib/gds-sso.rb +27 -24
- data/lib/gds-sso/api_access.rb +1 -1
- data/lib/gds-sso/bearer_token.rb +24 -24
- data/lib/gds-sso/config.rb +13 -12
- data/lib/gds-sso/controller_methods.rb +7 -8
- data/lib/gds-sso/failure_app.rb +8 -8
- data/lib/gds-sso/lint/user_spec.rb +27 -28
- data/lib/gds-sso/lint/user_test.rb +28 -28
- data/lib/gds-sso/railtie.rb +12 -0
- data/lib/gds-sso/user.rb +13 -13
- data/lib/gds-sso/version.rb +1 -1
- data/lib/gds-sso/warden_config.rb +21 -31
- data/spec/controller/api_user_controller_spec.rb +40 -37
- data/spec/controller/controller_methods_spec.rb +28 -42
- data/spec/internal/app/assets/config/manifest.js +0 -0
- data/spec/internal/app/controllers/application_controller.rb +1 -1
- data/spec/internal/app/controllers/example_controller.rb +1 -2
- data/spec/internal/config/initializers/gds-sso.rb +2 -2
- data/spec/internal/config/routes.rb +5 -2
- data/spec/internal/config/storage.yml +3 -0
- data/spec/internal/db/combustion_test.sqlite +0 -0
- data/spec/internal/db/schema.rb +9 -5
- data/spec/internal/log/test.log +1142 -1200
- data/spec/requests/end_to_end_spec.rb +45 -46
- data/spec/spec_helper.rb +12 -13
- data/spec/support/controller_spy.rb +14 -0
- data/spec/support/serializable_user.rb +3 -0
- data/spec/support/signon_integration_helpers.rb +10 -8
- data/spec/support/test_user.rb +29 -0
- data/spec/support/timecop.rb +1 -1
- data/spec/unit/api_access_spec.rb +7 -7
- data/spec/unit/bearer_token_spec.rb +14 -15
- data/spec/unit/config_spec.rb +5 -5
- data/spec/unit/mock_bearer_token_spec.rb +4 -4
- data/spec/unit/railtie_spec.rb +14 -0
- data/spec/unit/session_serialisation_spec.rb +5 -9
- data/spec/unit/user_spec.rb +20 -51
- metadata +94 -54
@@ -6,20 +6,19 @@ module GDS
|
|
6
6
|
|
7
7
|
def self.included(base)
|
8
8
|
base.rescue_from PermissionDeniedException do |e|
|
9
|
-
if GDS::SSO::Config.api_only
|
9
|
+
if GDS::SSO::Config.api_only
|
10
10
|
render json: { message: e.message }, status: :forbidden
|
11
11
|
else
|
12
12
|
render "authorisations/unauthorised", layout: "unauthorised", status: :forbidden, locals: { message: e.message }
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
-
unless GDS::SSO::Config.api_only
|
16
|
+
unless GDS::SSO::Config.api_only
|
17
17
|
base.helper_method :user_signed_in?
|
18
18
|
base.helper_method :current_user
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
|
23
22
|
def authorise_user!(permissions)
|
24
23
|
# Ensure that we're authenticated (and by extension that current_user is set).
|
25
24
|
# Otherwise current_user might be nil, and we'd error out
|
@@ -52,7 +51,7 @@ module GDS
|
|
52
51
|
end
|
53
52
|
|
54
53
|
def user_signed_in?
|
55
|
-
warden && warden.authenticated? && !
|
54
|
+
warden && warden.authenticated? && !warden.user.remotely_signed_out?
|
56
55
|
end
|
57
56
|
|
58
57
|
def current_user
|
@@ -64,22 +63,22 @@ module GDS
|
|
64
63
|
end
|
65
64
|
|
66
65
|
def warden
|
67
|
-
request.env[
|
66
|
+
request.env["warden"]
|
68
67
|
end
|
69
68
|
|
70
|
-
|
69
|
+
private
|
71
70
|
|
72
71
|
def authorise_user_with_at_least_one_of_permissions!(permissions)
|
73
72
|
if permissions.none? { |permission| current_user.has_permission?(permission) }
|
74
73
|
raise PermissionDeniedException,
|
75
|
-
|
74
|
+
"Sorry, you don't seem to have any of the permissions: #{permissions.to_sentence} for this app."
|
76
75
|
end
|
77
76
|
end
|
78
77
|
|
79
78
|
def authorise_user_with_all_permissions!(permissions)
|
80
79
|
unless permissions.all? { |permission| current_user.has_permission?(permission) }
|
81
80
|
raise PermissionDeniedException,
|
82
|
-
|
81
|
+
"Sorry, you don't seem to have all of the permissions: #{permissions.to_sentence} for this app."
|
83
82
|
end
|
84
83
|
end
|
85
84
|
end
|
data/lib/gds-sso/failure_app.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require "action_controller/metal"
|
2
|
-
require
|
2
|
+
require "rails"
|
3
3
|
|
4
4
|
# Failure application that will be called every time :warden is thrown from
|
5
5
|
# any strategy or hook.
|
@@ -18,7 +18,7 @@ module GDS
|
|
18
18
|
def self.call(env)
|
19
19
|
if GDS::SSO::ApiAccess.api_call?(env)
|
20
20
|
action(:api_invalid_token).call(env)
|
21
|
-
elsif GDS::SSO::Config.api_only
|
21
|
+
elsif GDS::SSO::Config.api_only
|
22
22
|
action(:api_missing_token).call(env)
|
23
23
|
else
|
24
24
|
action(:redirect).call(env)
|
@@ -27,15 +27,15 @@ module GDS
|
|
27
27
|
|
28
28
|
def redirect
|
29
29
|
store_location!
|
30
|
-
redirect_to
|
30
|
+
redirect_to "/auth/gds"
|
31
31
|
end
|
32
32
|
|
33
33
|
def api_invalid_token
|
34
|
-
api_unauthorized(
|
34
|
+
api_unauthorized("Bearer token does not appear to be valid", "invalid_token")
|
35
35
|
end
|
36
36
|
|
37
37
|
def api_missing_token
|
38
|
-
api_unauthorized(
|
38
|
+
api_unauthorized("No bearer token was provided", "invalid_request")
|
39
39
|
end
|
40
40
|
|
41
41
|
# Stores requested uri to redirect the user after signing in. We cannot use
|
@@ -45,13 +45,13 @@ module GDS
|
|
45
45
|
|
46
46
|
# TOTALLY NOT DOING THE SCOPE THING. PROBABLY SHOULD.
|
47
47
|
def store_location!
|
48
|
-
session["return_to"] = request.env[
|
48
|
+
session["return_to"] = request.env["warden.options"][:attempted_path] if request.get?
|
49
49
|
end
|
50
50
|
|
51
|
-
|
51
|
+
private
|
52
52
|
|
53
53
|
def api_unauthorized(message, bearer_error)
|
54
|
-
headers[
|
54
|
+
headers["WWW-Authenticate"] = %(Bearer error="#{bearer_error}")
|
55
55
|
render json: { message: message }, status: :unauthorized
|
56
56
|
end
|
57
57
|
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
RSpec.shared_examples "a gds-sso user class" do
|
2
|
-
subject { described_class.new(:
|
2
|
+
subject { described_class.new(uid: "12345") }
|
3
3
|
|
4
4
|
it "implements #where" do
|
5
5
|
expect(described_class).to respond_to(:where)
|
6
6
|
|
7
|
-
result = described_class.where(uid:
|
7
|
+
result = described_class.where(uid: "123")
|
8
8
|
expect(result).to respond_to(:first)
|
9
9
|
end
|
10
10
|
|
@@ -15,8 +15,8 @@ RSpec.shared_examples "a gds-sso user class" do
|
|
15
15
|
expect(subject).to be_remotely_signed_out
|
16
16
|
end
|
17
17
|
|
18
|
-
it "implements #
|
19
|
-
subject.
|
18
|
+
it "implements #update!" do
|
19
|
+
subject.update!(email: "ab@c.com")
|
20
20
|
expect(subject.email).to eq("ab@c.com")
|
21
21
|
end
|
22
22
|
|
@@ -30,50 +30,49 @@ RSpec.shared_examples "a gds-sso user class" do
|
|
30
30
|
|
31
31
|
describe "#has_all_permissions?" do
|
32
32
|
it "is false when there are no permissions" do
|
33
|
-
subject.
|
34
|
-
required_permissions = [
|
33
|
+
subject.update!(permissions: nil)
|
34
|
+
required_permissions = %w[signin]
|
35
35
|
expect(subject.has_all_permissions?(required_permissions)).to be_falsy
|
36
36
|
end
|
37
37
|
|
38
38
|
it "is false when it does not have all required permissions" do
|
39
|
-
subject.
|
40
|
-
required_permissions = [
|
39
|
+
subject.update!(permissions: %w[signin])
|
40
|
+
required_permissions = %w[signin not_granted_permission_one not_granted_permission_two]
|
41
41
|
expect(subject.has_all_permissions?(required_permissions)).to be false
|
42
42
|
end
|
43
43
|
|
44
44
|
it "is true when it has all required permissions" do
|
45
|
-
subject.
|
46
|
-
required_permissions = [
|
45
|
+
subject.update!(permissions: %w[signin internal_app])
|
46
|
+
required_permissions = %w[signin internal_app]
|
47
47
|
expect(subject.has_all_permissions?(required_permissions)).to be true
|
48
48
|
end
|
49
|
-
|
50
49
|
end
|
51
50
|
|
52
51
|
specify "the User class and GDS::SSO::User mixin work together" do
|
53
52
|
auth_hash = {
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
53
|
+
"uid" => "12345",
|
54
|
+
"info" => {
|
55
|
+
"name" => "Joe Smith",
|
56
|
+
"email" => "joe.smith@example.com",
|
57
|
+
},
|
58
|
+
"extra" => {
|
59
|
+
"user" => {
|
60
|
+
"disabled" => false,
|
61
|
+
"permissions" => %w[signin],
|
62
|
+
"organisation_slug" => "cabinet-office",
|
63
|
+
"organisation_content_id" => "91e57ad9-29a3-4f94-9ab4-5e9ae6d13588",
|
64
|
+
},
|
58
65
|
},
|
59
|
-
'extra' => {
|
60
|
-
'user' => {
|
61
|
-
'disabled' => false,
|
62
|
-
'permissions' => ['signin'],
|
63
|
-
'organisation_slug' => 'cabinet-office',
|
64
|
-
'organisation_content_id' => '91e57ad9-29a3-4f94-9ab4-5e9ae6d13588'
|
65
|
-
}
|
66
|
-
}
|
67
66
|
}
|
68
67
|
|
69
68
|
user = described_class.find_for_gds_oauth(auth_hash)
|
70
69
|
expect(user).to be_an_instance_of(described_class)
|
71
|
-
expect(user.uid).to eq(
|
70
|
+
expect(user.uid).to eq("12345")
|
72
71
|
expect(user.name).to eq("Joe Smith")
|
73
|
-
expect(user.email).to eq(
|
72
|
+
expect(user.email).to eq("joe.smith@example.com")
|
74
73
|
expect(user).not_to be_disabled
|
75
|
-
expect(user.permissions).to eq([
|
76
|
-
expect(user.organisation_slug).to eq(
|
77
|
-
expect(user.organisation_content_id).to eq(
|
74
|
+
expect(user.permissions).to eq(%w[signin])
|
75
|
+
expect(user.organisation_slug).to eq("cabinet-office")
|
76
|
+
expect(user.organisation_content_id).to eq("91e57ad9-29a3-4f94-9ab4-5e9ae6d13588")
|
78
77
|
end
|
79
78
|
end
|
@@ -17,58 +17,58 @@ module GDS
|
|
17
17
|
#
|
18
18
|
class UserTest < ActiveSupport::TestCase
|
19
19
|
def user_class
|
20
|
-
raise
|
20
|
+
raise "Reopen `GDS::SSO::Lint::UserTest` and add `#user_class` to return the class including `GDS::SSO::User`"
|
21
21
|
end
|
22
22
|
|
23
23
|
setup do
|
24
|
-
@lint_user = user_class.new(uid:
|
24
|
+
@lint_user = user_class.new(uid: "12345")
|
25
25
|
end
|
26
26
|
|
27
|
-
test
|
28
|
-
result = user_class.where(uid:
|
27
|
+
test "implement #where" do
|
28
|
+
result = user_class.where(uid: "123")
|
29
29
|
assert result.respond_to?(:first)
|
30
30
|
end
|
31
31
|
|
32
|
-
test
|
32
|
+
test "implement #update_attribute" do
|
33
33
|
@lint_user.update_attribute(:remotely_signed_out, true)
|
34
34
|
assert @lint_user.remotely_signed_out?
|
35
35
|
end
|
36
36
|
|
37
|
-
test
|
38
|
-
@lint_user.
|
39
|
-
assert_equal @lint_user.email,
|
37
|
+
test "implement #update!" do
|
38
|
+
@lint_user.update!(email: "test@example.com")
|
39
|
+
assert_equal @lint_user.email, "test@example.com"
|
40
40
|
end
|
41
41
|
|
42
|
-
test
|
42
|
+
test "implement #create!" do
|
43
43
|
assert user_class.respond_to?(:create!)
|
44
44
|
end
|
45
45
|
|
46
|
-
test
|
46
|
+
test "verify the User class and GDS::SSO::User work together" do
|
47
47
|
auth_hash = {
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
48
|
+
"uid" => "12345",
|
49
|
+
"info" => {
|
50
|
+
"name" => "Joe Smith",
|
51
|
+
"email" => "joe.smith@example.com",
|
52
|
+
},
|
53
|
+
"extra" => {
|
54
|
+
"user" => {
|
55
|
+
"disabled" => false,
|
56
|
+
"permissions" => %w[signin],
|
57
|
+
"organisation_slug" => "cabinet-office",
|
58
|
+
"organisation_content_id" => "91e57ad9-29a3-4f94-9ab4-5e9ae6d13588",
|
59
|
+
},
|
52
60
|
},
|
53
|
-
'extra' => {
|
54
|
-
'user' => {
|
55
|
-
'disabled' => false,
|
56
|
-
'permissions' => ['signin'],
|
57
|
-
'organisation_slug' => 'cabinet-office',
|
58
|
-
'organisation_content_id' => '91e57ad9-29a3-4f94-9ab4-5e9ae6d13588',
|
59
|
-
}
|
60
|
-
}
|
61
61
|
}
|
62
62
|
|
63
63
|
user = user_class.find_for_gds_oauth(auth_hash)
|
64
64
|
assert_equal user_class, user.class
|
65
|
-
assert_equal
|
66
|
-
assert_equal
|
67
|
-
assert_equal
|
65
|
+
assert_equal "12345", user.uid
|
66
|
+
assert_equal "Joe Smith", user.name
|
67
|
+
assert_equal "joe.smith@example.com", user.email
|
68
68
|
assert_equal false, user.disabled
|
69
|
-
assert_equal [
|
70
|
-
assert_equal
|
71
|
-
assert_equal
|
69
|
+
assert_equal %w[signin], user.permissions
|
70
|
+
assert_equal "cabinet-office", user.organisation_slug
|
71
|
+
assert_equal "91e57ad9-29a3-4f94-9ab4-5e9ae6d13588", user.organisation_content_id
|
72
72
|
end
|
73
73
|
end
|
74
74
|
end
|
data/lib/gds-sso/user.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "active_support/concern"
|
2
2
|
|
3
3
|
module GDS
|
4
4
|
module SSO
|
@@ -21,32 +21,32 @@ module GDS
|
|
21
21
|
|
22
22
|
def self.user_params_from_auth_hash(auth_hash)
|
23
23
|
{
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
24
|
+
"uid" => auth_hash["uid"],
|
25
|
+
"email" => auth_hash["info"]["email"],
|
26
|
+
"name" => auth_hash["info"]["name"],
|
27
|
+
"permissions" => auth_hash["extra"]["user"]["permissions"],
|
28
|
+
"organisation_slug" => auth_hash["extra"]["user"]["organisation_slug"],
|
29
|
+
"organisation_content_id" => auth_hash["extra"]["user"]["organisation_content_id"],
|
30
|
+
"disabled" => auth_hash["extra"]["user"]["disabled"],
|
31
31
|
}
|
32
32
|
end
|
33
33
|
|
34
34
|
def clear_remotely_signed_out!
|
35
|
-
|
35
|
+
update_attribute(:remotely_signed_out, false)
|
36
36
|
end
|
37
37
|
|
38
38
|
def set_remotely_signed_out!
|
39
|
-
|
39
|
+
update_attribute(:remotely_signed_out, true)
|
40
40
|
end
|
41
41
|
|
42
42
|
module ClassMethods
|
43
43
|
def find_for_gds_oauth(auth_hash)
|
44
44
|
user_params = GDS::SSO::User.user_params_from_auth_hash(auth_hash.to_hash)
|
45
|
-
user =
|
46
|
-
|
45
|
+
user = where(uid: user_params["uid"]).first ||
|
46
|
+
where(email: user_params["email"]).first
|
47
47
|
|
48
48
|
if user
|
49
|
-
user.
|
49
|
+
user.update!(user_params)
|
50
50
|
user
|
51
51
|
else # Create a new user.
|
52
52
|
create!(user_params)
|
data/lib/gds-sso/version.rb
CHANGED
@@ -1,63 +1,55 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require "warden"
|
2
|
+
require "warden-oauth2"
|
3
|
+
require "gds-sso/bearer_token"
|
4
4
|
|
5
5
|
def logger
|
6
|
-
|
7
|
-
Rails.logger
|
8
|
-
else
|
9
|
-
env['rack.logger']
|
10
|
-
end
|
6
|
+
Rails.logger || env["rack.logger"]
|
11
7
|
end
|
12
8
|
|
13
|
-
Warden::Manager.after_authentication do |user,
|
9
|
+
Warden::Manager.after_authentication do |user, _auth, _opts|
|
14
10
|
# We've successfully signed in.
|
15
11
|
# If they were remotely signed out, clear the flag as they're no longer suspended
|
16
12
|
user.clear_remotely_signed_out!
|
17
13
|
end
|
18
14
|
|
19
15
|
Warden::Manager.serialize_into_session do |user|
|
20
|
-
if user.respond_to?(:uid)
|
16
|
+
if user.respond_to?(:uid) && user.uid
|
21
17
|
[user.uid, Time.now.utc.iso8601]
|
22
|
-
else
|
23
|
-
nil
|
24
18
|
end
|
25
19
|
end
|
26
20
|
|
27
21
|
Warden::Manager.serialize_from_session do |(uid, auth_timestamp)|
|
28
22
|
# This will reject old sessions that don't have a previous login timestamp
|
29
23
|
if auth_timestamp.is_a?(String)
|
30
|
-
|
31
|
-
Time.parse(auth_timestamp)
|
24
|
+
begin
|
25
|
+
auth_timestamp = Time.parse(auth_timestamp)
|
32
26
|
rescue ArgumentError
|
33
|
-
nil
|
27
|
+
auth_timestamp = nil
|
34
28
|
end
|
35
29
|
end
|
36
30
|
|
37
|
-
if auth_timestamp
|
38
|
-
GDS::SSO::Config.user_klass.where(:
|
39
|
-
else
|
40
|
-
nil
|
31
|
+
if auth_timestamp && ((auth_timestamp + GDS::SSO::Config.auth_valid_for) > Time.now.utc)
|
32
|
+
GDS::SSO::Config.user_klass.where(uid: uid, remotely_signed_out: false).first
|
41
33
|
end
|
42
34
|
end
|
43
35
|
|
44
36
|
Warden::Strategies.add(:gds_sso) do
|
45
37
|
def valid?
|
46
|
-
|
38
|
+
!::GDS::SSO::ApiAccess.api_call?(env)
|
47
39
|
end
|
48
40
|
|
49
41
|
def authenticate!
|
50
42
|
logger.debug("Authenticating with gds_sso strategy")
|
51
43
|
|
52
|
-
if request.env[
|
44
|
+
if request.env["omniauth.auth"].nil?
|
53
45
|
fail!("No credentials, bub")
|
54
46
|
else
|
55
|
-
user = prep_user(request.env[
|
47
|
+
user = prep_user(request.env["omniauth.auth"])
|
56
48
|
success!(user)
|
57
49
|
end
|
58
50
|
end
|
59
51
|
|
60
|
-
|
52
|
+
private
|
61
53
|
|
62
54
|
def prep_user(auth_hash)
|
63
55
|
user = GDS::SSO::Config.user_klass.find_for_gds_oauth(auth_hash)
|
@@ -73,27 +65,25 @@ Warden::Strategies.add(:gds_bearer_token, Warden::OAuth2::Strategies::Bearer)
|
|
73
65
|
|
74
66
|
Warden::Strategies.add(:mock_gds_sso) do
|
75
67
|
def valid?
|
76
|
-
|
68
|
+
!::GDS::SSO::ApiAccess.api_call?(env)
|
77
69
|
end
|
78
70
|
|
79
71
|
def authenticate!
|
80
72
|
logger.warn("Authenticating with mock_gds_sso strategy")
|
81
73
|
|
82
74
|
test_user = GDS::SSO.test_user
|
83
|
-
test_user ||= ENV[
|
75
|
+
test_user ||= ENV["GDS_SSO_MOCK_INVALID"].present? ? nil : GDS::SSO::Config.user_klass.first
|
84
76
|
if test_user
|
85
77
|
# Brute force ensure test user has correct perms to signin
|
86
|
-
|
78
|
+
unless test_user.has_permission?("signin")
|
87
79
|
permissions = test_user.permissions || []
|
88
80
|
test_user.update_attribute(:permissions, permissions << "signin")
|
89
81
|
end
|
90
82
|
success!(test_user)
|
83
|
+
elsif Rails.env.test? && ENV["GDS_SSO_MOCK_INVALID"].present?
|
84
|
+
fail!(:invalid)
|
91
85
|
else
|
92
|
-
|
93
|
-
fail!(:invalid)
|
94
|
-
else
|
95
|
-
raise "GDS-SSO running in mock mode and no test user found. Normally we'd load the first user in the database. Create a user in the database."
|
96
|
-
end
|
86
|
+
raise "GDS-SSO running in mock mode and no test user found. Normally we'd load the first user in the database. Create a user in the database."
|
97
87
|
end
|
98
88
|
end
|
99
89
|
end
|