gds-sso 15.0.0 → 16.0.2
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.
- 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/config/routes.rb +7 -6
- data/lib/gds-sso.rb +29 -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 +24 -25
- data/lib/gds-sso/lint/user_test.rb +28 -28
- data/lib/gds-sso/railtie.rb +12 -0
- data/lib/gds-sso/user.rb +12 -12
- 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/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 +2 -2
- data/spec/internal/db/combustion_test.sqlite +0 -0
- data/spec/internal/db/schema.rb +5 -5
- data/spec/internal/log/test.log +1131 -1123
- data/spec/requests/end_to_end_spec.rb +44 -45
- 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 +104 -61
@@ -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
|
|
@@ -31,49 +31,48 @@ RSpec.shared_examples "a gds-sso user class" do
|
|
31
31
|
describe "#has_all_permissions?" do
|
32
32
|
it "is false when there are no permissions" do
|
33
33
|
subject.update!(permissions: nil)
|
34
|
-
required_permissions = [
|
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.update!(permissions: [
|
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.update!(permissions: [
|
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.update!(email:
|
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,29 +21,29 @@ 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
49
|
user.update!(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
|