gds-sso 20.0.0 → 21.0.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.
- checksums.yaml +4 -4
- data/README.md +14 -1
- data/lib/gds-sso/api_access.rb +22 -1
- data/lib/gds-sso/authorised_user_constraint.rb +2 -3
- data/lib/gds-sso/bearer_token.rb +5 -1
- data/lib/gds-sso/config.rb +2 -0
- data/lib/gds-sso/controller_methods.rb +1 -9
- data/lib/gds-sso/failure_app.rb +6 -7
- data/lib/gds-sso/version.rb +1 -1
- data/lib/gds-sso/warden_config.rb +30 -3
- data/lib/gds-sso.rb +6 -0
- data/spec/system/authentication_and_authorisation_spec.rb +74 -1
- data/spec/unit/api_access_spec.rb +62 -8
- data/spec/unit/authorised_user_constraint_spec.rb +8 -31
- data/spec/unit/bearer_token_spec.rb +8 -0
- data/spec/unit/gds_sso_spec.rb +37 -0
- data/spec/unit/mock_bearer_token_spec.rb +49 -16
- metadata +19 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1cce044b9dcc019806b1a70222fb30c99abce4df707245ae7c4d795b92bba8d7
|
4
|
+
data.tar.gz: 966b8b0c9ef7b95ee5ec5229c8f087f1870447d4281b1b7b6bcc5fc5627155a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3f3fe06419fd98b0018ff60ce33f45fe6a0173ac2c632009f2ece0d821539ae12503ad5f0a85716d2db16811e3e3bff4e234122ebff71a708fbcba13339bee78
|
7
|
+
data.tar.gz: eb36c389d730f589805c6e17bbdcdbf30f545cd2874431283082a437b0668312e5be7faa062f58149f9e5ea11f653c6ff52e67acace2213ad28f9383896912dc
|
data/README.md
CHANGED
@@ -90,7 +90,7 @@ Authorization: Bearer your-token-here
|
|
90
90
|
|
91
91
|
To avoid making these requests for each incoming request, this gem will [automatically cache a successful response](https://github.com/alphagov/gds-sso/blob/master/lib/gds-sso/bearer_token.rb), using the [Rails cache](https://github.com/alphagov/gds-sso/blob/master/lib/gds-sso/railtie.rb).
|
92
92
|
|
93
|
-
If you are using a Rails
|
93
|
+
If you are using a Rails app in
|
94
94
|
[api_only](http://guides.rubyonrails.org/api_app.html) mode this gem will
|
95
95
|
automatically disable the oauth layers which use session persistence. You can
|
96
96
|
configure this gem to be in api_only mode (or not) with:
|
@@ -103,6 +103,19 @@ GDS::SSO.config do |config|
|
|
103
103
|
end
|
104
104
|
```
|
105
105
|
|
106
|
+
For apps that have both usage of web and API you can configure a lambda to
|
107
|
+
match your API endpoints to ensure they don't support session auth and return
|
108
|
+
JSON error messages:
|
109
|
+
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
GDS::SSO.config do |config|
|
113
|
+
# ...
|
114
|
+
#
|
115
|
+
config.api_request_matcher = ->(request) { request.path.start_with?("/api/") }
|
116
|
+
end
|
117
|
+
```
|
118
|
+
|
106
119
|
### Use in production mode
|
107
120
|
|
108
121
|
To use gds-sso in production you will need to setup the following environment variables, which we look for in [the config](https://github.com/alphagov/gds-sso/blob/master/lib/gds-sso/config.rb). You will need to have admin access to Signon to get these.
|
data/lib/gds-sso/api_access.rb
CHANGED
@@ -1,8 +1,29 @@
|
|
1
|
+
require "rack/request"
|
2
|
+
|
1
3
|
module GDS
|
2
4
|
module SSO
|
3
5
|
class ApiAccess
|
4
6
|
def self.api_call?(env)
|
5
|
-
env["
|
7
|
+
return env["gds_sso.api_call"] unless env["gds_sso.api_call"].nil?
|
8
|
+
return true if GDS::SSO::Config.api_only
|
9
|
+
|
10
|
+
if GDS::SSO::Config.api_request_matcher
|
11
|
+
return GDS::SSO::Config.api_request_matcher.call(Rack::Request.new(env))
|
12
|
+
end
|
13
|
+
|
14
|
+
!bearer_token(env).nil?
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.bearer_token(env)
|
18
|
+
Rack::Auth::AbstractRequest::AUTHORIZATION_KEYS.each do |key|
|
19
|
+
next unless env.key?(key)
|
20
|
+
|
21
|
+
if (match = env[key].match(/\ABearer (.+)/))
|
22
|
+
return match[1]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
nil
|
6
27
|
end
|
7
28
|
end
|
8
29
|
end
|
@@ -6,10 +6,9 @@ module GDS
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def matches?(request)
|
9
|
-
|
10
|
-
warden.authenticate! if !warden.authenticated? || warden.user.remotely_signed_out?
|
9
|
+
user = GDS::SSO.authenticate_user!(request.env["warden"])
|
11
10
|
|
12
|
-
GDS::SSO::AuthoriseUser.call(
|
11
|
+
GDS::SSO::AuthoriseUser.call(user, permissions)
|
13
12
|
true
|
14
13
|
end
|
15
14
|
|
data/lib/gds-sso/bearer_token.rb
CHANGED
@@ -6,6 +6,8 @@ module GDS
|
|
6
6
|
module SSO
|
7
7
|
module BearerToken
|
8
8
|
def self.locate(token_string)
|
9
|
+
return if token_string.nil? || token_string.empty?
|
10
|
+
|
9
11
|
user_details = GDS::SSO::Config.cache.fetch(["api-user-cache", token_string], expires_in: 5.minutes) do
|
10
12
|
access_token = OAuth2::AccessToken.new(oauth_client, token_string)
|
11
13
|
response_body = access_token.get("/user.json?client_id=#{CGI.escape(GDS::SSO::Config.oauth_id)}").body
|
@@ -56,7 +58,9 @@ module GDS
|
|
56
58
|
|
57
59
|
module MockBearerToken
|
58
60
|
def self.locate(_token_string)
|
59
|
-
|
61
|
+
return GDS::SSO.test_user if GDS::SSO.test_user
|
62
|
+
|
63
|
+
dummy_api_user = GDS::SSO::Config.user_klass.where(email: "dummyapiuser@domain.com").first
|
60
64
|
if dummy_api_user.nil?
|
61
65
|
dummy_api_user = GDS::SSO::Config.user_klass.new
|
62
66
|
dummy_api_user.email = "dummyapiuser@domain.com"
|
data/lib/gds-sso/config.rb
CHANGED
@@ -4,17 +4,9 @@ module GDS
|
|
4
4
|
end
|
5
5
|
|
6
6
|
module ControllerMethods
|
7
|
-
# TODO: remove this for the next major release
|
8
|
-
class PermissionDeniedException < PermissionDeniedError
|
9
|
-
def initialize(...)
|
10
|
-
warn "GDS::SSO::ControllerMethods::PermissionDeniedException is deprecated, please replace with GDS::SSO::PermissionDeniedError"
|
11
|
-
super(...)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
7
|
def self.included(base)
|
16
8
|
base.rescue_from PermissionDeniedError do |e|
|
17
|
-
if GDS::SSO::
|
9
|
+
if GDS::SSO::ApiAccess.api_call?(request.env)
|
18
10
|
render json: { message: e.message }, status: :forbidden
|
19
11
|
else
|
20
12
|
render "authorisations/unauthorised", layout: "unauthorised", status: :forbidden, locals: { message: e.message }
|
data/lib/gds-sso/failure_app.rb
CHANGED
@@ -6,20 +6,19 @@ require "rails"
|
|
6
6
|
module GDS
|
7
7
|
module SSO
|
8
8
|
class FailureApp < ActionController::Metal
|
9
|
-
include ActionController::UrlFor
|
10
9
|
include ActionController::Redirecting
|
11
10
|
include AbstractController::Rendering
|
12
11
|
include ActionController::Rendering
|
13
12
|
include ActionController::Renderers
|
14
13
|
use_renderers :json
|
15
14
|
|
16
|
-
include Rails.application.routes.url_helpers
|
17
|
-
|
18
15
|
def self.call(env)
|
19
|
-
if
|
20
|
-
|
21
|
-
|
22
|
-
|
16
|
+
if env["gds_sso.api_call"]
|
17
|
+
if env["gds_sso.api_bearer_token_present"]
|
18
|
+
action(:api_invalid_token).call(env)
|
19
|
+
else
|
20
|
+
action(:api_missing_token).call(env)
|
21
|
+
end
|
23
22
|
else
|
24
23
|
action(:redirect).call(env)
|
25
24
|
end
|
data/lib/gds-sso/version.rb
CHANGED
@@ -6,6 +6,12 @@ def logger
|
|
6
6
|
Rails.logger || env["rack.logger"]
|
7
7
|
end
|
8
8
|
|
9
|
+
Warden::Manager.on_request do |proxy|
|
10
|
+
proxy.env["gds_sso.api_call"] ||= ::GDS::SSO::ApiAccess.api_call?(proxy.env)
|
11
|
+
proxy.env["gds_sso.api_bearer_token_present"] ||=
|
12
|
+
proxy.env["gds_sso.api_call"] && !::GDS::SSO::ApiAccess.bearer_token(proxy.env).nil?
|
13
|
+
end
|
14
|
+
|
9
15
|
Warden::Manager.after_authentication do |user, _auth, _opts|
|
10
16
|
# We've successfully signed in.
|
11
17
|
# If they were remotely signed out, clear the flag as they're no longer suspended
|
@@ -35,7 +41,7 @@ end
|
|
35
41
|
|
36
42
|
Warden::Strategies.add(:gds_sso) do
|
37
43
|
def valid?
|
38
|
-
|
44
|
+
!env["gds_sso.api_call"]
|
39
45
|
end
|
40
46
|
|
41
47
|
def authenticate!
|
@@ -61,11 +67,32 @@ end
|
|
61
67
|
Warden::OAuth2.configure do |config|
|
62
68
|
config.token_model = GDS::SSO::Config.use_mock_strategies? ? GDS::SSO::MockBearerToken : GDS::SSO::BearerToken
|
63
69
|
end
|
64
|
-
|
70
|
+
|
71
|
+
# We're using our own bearer token strategy rather than the one in Warden::OAuth2
|
72
|
+
# so that we can have all requests match either a bearer token or a session
|
73
|
+
# strategy. It also allows us to avoid multiple DB queries to locate a user.
|
74
|
+
Warden::Strategies.add(:gds_bearer_token, Class.new(Warden::OAuth2::Strategies::Token)) do
|
75
|
+
def valid?
|
76
|
+
env["gds_sso.api_call"]
|
77
|
+
end
|
78
|
+
|
79
|
+
def token_string
|
80
|
+
@token_string ||= GDS::SSO::ApiAccess.bearer_token(env)
|
81
|
+
end
|
82
|
+
|
83
|
+
def token
|
84
|
+
# Using a defined? based memo approach over @token ||= so that we can set
|
85
|
+
# @token to nil and not re-evaluate the assignment.
|
86
|
+
return @token if defined? @token
|
87
|
+
|
88
|
+
@token = Warden::OAuth2.config.token_model.locate(token_string)
|
89
|
+
@token
|
90
|
+
end
|
91
|
+
end
|
65
92
|
|
66
93
|
Warden::Strategies.add(:mock_gds_sso) do
|
67
94
|
def valid?
|
68
|
-
|
95
|
+
!env["gds_sso.api_call"]
|
69
96
|
end
|
70
97
|
|
71
98
|
def authenticate!
|
data/lib/gds-sso.rb
CHANGED
@@ -25,6 +25,12 @@ module GDS
|
|
25
25
|
yield GDS::SSO::Config
|
26
26
|
end
|
27
27
|
|
28
|
+
def self.authenticate_user!(warden)
|
29
|
+
warden.authenticate! if !warden.authenticated? || warden.user.remotely_signed_out?
|
30
|
+
|
31
|
+
warden.user
|
32
|
+
end
|
33
|
+
|
28
34
|
class Engine < ::Rails::Engine
|
29
35
|
# Force routes to be loaded if we are doing any eager load.
|
30
36
|
# TODO - check this one - Stolen from Devise because it looked sensible...
|
@@ -80,6 +80,16 @@ RSpec.describe "Authenication and authorisation" do
|
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
83
|
+
it "restricts access when the request doesn't match the api_request_matcher" do
|
84
|
+
allow(GDS::SSO::Config)
|
85
|
+
.to receive(:api_request_matcher)
|
86
|
+
.and_return(->(_request) { false })
|
87
|
+
|
88
|
+
visit "/restricted"
|
89
|
+
expect(page.status_code).to eql(302)
|
90
|
+
expect(page.response_headers["Location"]).to match("/auth/gds")
|
91
|
+
end
|
92
|
+
|
83
93
|
it "allows access when given a valid bearer token" do
|
84
94
|
stub_signon_user_request
|
85
95
|
page.driver.header("Authorization", "Bearer 123")
|
@@ -95,13 +105,57 @@ RSpec.describe "Authenication and authorisation" do
|
|
95
105
|
|
96
106
|
visit "/restricted"
|
97
107
|
expect(page.status_code).to eq(401)
|
108
|
+
expect(page.response_headers["WWW-Authenticate"]).to eq('Bearer error="invalid_token"')
|
109
|
+
expect_json_response({ "message" => "Bearer token does not appear to be valid" })
|
98
110
|
end
|
99
111
|
|
100
|
-
it "returns a 401 when a bearer token is missing and the app is api_only" do
|
112
|
+
it "returns a JSON 401 when a bearer token is missing and the app is api_only" do
|
101
113
|
allow(GDS::SSO::Config).to receive(:api_only).and_return(true)
|
102
114
|
|
103
115
|
visit "/restricted"
|
104
116
|
expect(page.status_code).to eq(401)
|
117
|
+
expect(page.response_headers["WWW-Authenticate"]).to eq('Bearer error="invalid_request"')
|
118
|
+
expect_json_response({ "message" => "No bearer token was provided" })
|
119
|
+
end
|
120
|
+
|
121
|
+
it "returns a JSON 401 when a bearer token is missing and the request matches the api_request_matcher" do
|
122
|
+
allow(GDS::SSO::Config)
|
123
|
+
.to receive(:api_request_matcher)
|
124
|
+
.and_return(->(request) { request.path == "/restricted" })
|
125
|
+
|
126
|
+
visit "/restricted"
|
127
|
+
expect(page.status_code).to eq(401)
|
128
|
+
expect(page.response_headers["WWW-Authenticate"]).to eq('Bearer error="invalid_request"')
|
129
|
+
expect_json_response({ "message" => "No bearer token was provided" })
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context "when accessing a route that requires authentication with the mock strategies" do
|
134
|
+
before do
|
135
|
+
# Using allow_any_instance_of because it's hard to access the instance
|
136
|
+
# of the class used within the Rails middleware
|
137
|
+
allow_any_instance_of(Warden::Config).to receive(:[]).and_call_original
|
138
|
+
allow_any_instance_of(Warden::Config)
|
139
|
+
.to receive(:[])
|
140
|
+
.with(:default_strategies)
|
141
|
+
.and_return({ _all: %i[mock_gds_sso gds_bearer_token] })
|
142
|
+
|
143
|
+
allow(Warden::OAuth2.config).to receive(:token_model).and_return(GDS::SSO::MockBearerToken)
|
144
|
+
allow(GDS::SSO).to receive(:test_user).and_return(TestUser.new)
|
145
|
+
end
|
146
|
+
|
147
|
+
it "allows access without being logged in" do
|
148
|
+
visit "/restricted"
|
149
|
+
expect(page.status_code).to eq(200)
|
150
|
+
expect(page.body).to have_content("restricted kablooie")
|
151
|
+
end
|
152
|
+
|
153
|
+
it "allows access to an API mock user" do
|
154
|
+
allow(GDS::SSO::Config).to receive(:api_only).and_return(true)
|
155
|
+
|
156
|
+
visit "/restricted"
|
157
|
+
expect(page.status_code).to eq(200)
|
158
|
+
expect(page.body).to have_content("restricted kablooie")
|
105
159
|
end
|
106
160
|
end
|
107
161
|
|
@@ -116,6 +170,20 @@ RSpec.describe "Authenication and authorisation" do
|
|
116
170
|
stub_signon_authenticated
|
117
171
|
visit "/this-requires-execute-permission"
|
118
172
|
expect(page.status_code).to eq(403)
|
173
|
+
expect(page).to have_content("Sorry, you don't seem to have the execute permission for this app.")
|
174
|
+
end
|
175
|
+
|
176
|
+
it "returns a JSON response when it's an API call" do
|
177
|
+
allow(GDS::SSO::Config)
|
178
|
+
.to receive(:api_request_matcher)
|
179
|
+
.and_return(->(request) { request.path == "/this-requires-execute-permission" })
|
180
|
+
|
181
|
+
stub_signon_user_request
|
182
|
+
page.driver.header("Authorization", "Bearer 123")
|
183
|
+
|
184
|
+
visit "/this-requires-execute-permission"
|
185
|
+
expect(page.status_code).to eq(403)
|
186
|
+
expect_json_response({ "message" => "Sorry, you don't seem to have the execute permission for this app." })
|
119
187
|
end
|
120
188
|
end
|
121
189
|
|
@@ -169,4 +237,9 @@ RSpec.describe "Authenication and authorisation" do
|
|
169
237
|
headers: { content_type: "application/json" },
|
170
238
|
)
|
171
239
|
end
|
240
|
+
|
241
|
+
def expect_json_response(json_match)
|
242
|
+
expect(page.response_headers["content-type"]).to match(/application\/json/)
|
243
|
+
expect(JSON.parse(page.body)).to match(json_match)
|
244
|
+
end
|
172
245
|
end
|
@@ -1,17 +1,71 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
require "gds-sso/api_access"
|
3
|
+
require "rack/mock_request"
|
3
4
|
|
4
5
|
describe GDS::SSO::ApiAccess do
|
5
|
-
|
6
|
-
|
7
|
-
"
|
8
|
-
|
9
|
-
|
6
|
+
describe ".api_call?" do
|
7
|
+
it "returns true if the rack env has already been tagged as an api_call" do
|
8
|
+
expect(described_class.api_call?({ "gds_sso.api_call" => true })).to be(true)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "returns false if the rack env has already been tagged as not an api_call" do
|
12
|
+
expect(described_class.api_call?({ "gds_sso.api_call" => false })).to be(false)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "returns true if GDS::SSO has been configured as api_only" do
|
16
|
+
allow(GDS::SSO::Config).to receive(:api_only).and_return(true)
|
17
|
+
|
18
|
+
expect(described_class.api_call?({})).to be(true)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "returns true if the request matches the api_request_matcher" do
|
22
|
+
allow(GDS::SSO::Config)
|
23
|
+
.to receive(:api_request_matcher)
|
24
|
+
.and_return(->(request) { request.path == "/api" })
|
25
|
+
|
26
|
+
env = Rack::MockRequest.env_for("/api")
|
27
|
+
expect(described_class.api_call?(env)).to be(true)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "returns false if the request doesn't match the api_request_matcher" do
|
31
|
+
allow(GDS::SSO::Config)
|
32
|
+
.to receive(:api_request_matcher)
|
33
|
+
.and_return(->(request) { request.path == "/api" })
|
34
|
+
|
35
|
+
env = Rack::MockRequest.env_for("/other")
|
36
|
+
expect(described_class.api_call?(env)).to be(false)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "returns true if a bearer token is present" do
|
40
|
+
env = { "HTTP_AUTHORIZATION" => "Bearer 1234:5678" }
|
41
|
+
expect(described_class.api_call?(env)).to be(true)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "returns false otherwise" do
|
45
|
+
expect(described_class.api_call?({})).to be(false)
|
46
|
+
end
|
10
47
|
end
|
11
48
|
|
12
|
-
|
13
|
-
it "
|
14
|
-
|
49
|
+
describe ".bearer_token" do
|
50
|
+
it "returns a bearer token set in a HTTP_AUTHORIZATION header" do
|
51
|
+
env = { "HTTP_AUTHORIZATION" => "Bearer 1234:5678" }
|
52
|
+
expect(described_class.bearer_token(env)).to eq("1234:5678")
|
53
|
+
end
|
54
|
+
|
55
|
+
it "returns nil for an empty bearer token in the HTTP_AUTHORIZATION header" do
|
56
|
+
env = { "HTTP_AUTHORIZATION" => "Bearer " }
|
57
|
+
expect(described_class.bearer_token(env)).to be_nil
|
58
|
+
end
|
59
|
+
|
60
|
+
it "supports all the authorization headers configured in Rack::Auth::AbstractRequest::AUTHORIZATION_KEYS" do
|
61
|
+
Rack::Auth::AbstractRequest::AUTHORIZATION_KEYS.each do |header|
|
62
|
+
env = { header => "Bearer 1234" }
|
63
|
+
expect(described_class.bearer_token(env)).to eq("1234")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
it "returns nil if a bearer token isn't set" do
|
68
|
+
expect(described_class.bearer_token({})).to be_nil
|
15
69
|
end
|
16
70
|
end
|
17
71
|
end
|
@@ -3,48 +3,25 @@ require "gds-sso/authorised_user_constraint"
|
|
3
3
|
|
4
4
|
describe GDS::SSO::AuthorisedUserConstraint do
|
5
5
|
before do
|
6
|
+
allow(GDS::SSO).to receive(:authenticate_user!).and_return(user)
|
6
7
|
allow(GDS::SSO::AuthoriseUser).to receive(:call).and_return(true)
|
7
8
|
end
|
8
9
|
|
9
10
|
describe "#matches?" do
|
10
|
-
let(:user) {
|
11
|
-
let(:warden)
|
12
|
-
double(
|
13
|
-
"warden",
|
14
|
-
authenticated?: user_authenticated,
|
15
|
-
user:,
|
16
|
-
authenticate!: nil,
|
17
|
-
)
|
18
|
-
end
|
19
|
-
let(:user_authenticated) { true }
|
20
|
-
let(:remotely_signed_out) { false }
|
11
|
+
let(:user) { TestUser.new }
|
12
|
+
let(:warden) { instance_double("Warden::Proxy") }
|
21
13
|
let(:request) { double("request", env: { "warden" => warden }) }
|
22
14
|
|
23
|
-
it "
|
24
|
-
expect(GDS::SSO
|
25
|
-
expect(warden).not_to receive(:authenticate!)
|
15
|
+
it "authenticates the user" do
|
16
|
+
expect(GDS::SSO).to receive(:authenticate_user!).with(warden)
|
26
17
|
|
27
18
|
described_class.new(%w[signin]).matches?(request)
|
28
19
|
end
|
29
20
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
it "authenticates the user" do
|
34
|
-
expect(warden).to receive(:authenticate!)
|
35
|
-
|
36
|
-
described_class.new(%w[signin]).matches?(request)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
context "when the user is remotely signed out" do
|
41
|
-
let(:remotely_signed_out) { true }
|
42
|
-
|
43
|
-
it "authenticates the user" do
|
44
|
-
expect(warden).to receive(:authenticate!)
|
21
|
+
it "authorises the user" do
|
22
|
+
expect(GDS::SSO::AuthoriseUser).to receive(:call).with(user, %w[signin])
|
45
23
|
|
46
|
-
|
47
|
-
end
|
24
|
+
described_class.new(%w[signin]).matches?(request)
|
48
25
|
end
|
49
26
|
end
|
50
27
|
end
|
@@ -25,5 +25,13 @@ describe GDS::SSO::BearerToken do
|
|
25
25
|
|
26
26
|
expect(same_user_again.id).to eql(created_user.id)
|
27
27
|
end
|
28
|
+
|
29
|
+
it "returns nil for a nil token string" do
|
30
|
+
expect(described_class.locate(nil)).to be_nil
|
31
|
+
end
|
32
|
+
|
33
|
+
it "returns nil for an empty token string" do
|
34
|
+
expect(described_class.locate("")).to be_nil
|
35
|
+
end
|
28
36
|
end
|
29
37
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe GDS::SSO do
|
4
|
+
describe "#authenticate_user!" do
|
5
|
+
let(:user) { TestUser.new }
|
6
|
+
let(:warden) do
|
7
|
+
instance_double("Warden::Proxy",
|
8
|
+
authenticate!: true,
|
9
|
+
authenticated?: false,
|
10
|
+
user:)
|
11
|
+
end
|
12
|
+
|
13
|
+
context "when a user is not already authenticated" do
|
14
|
+
it "authenticates the user and returns the user object" do
|
15
|
+
expect(described_class.authenticate_user!(warden)).to be(user)
|
16
|
+
expect(warden).to have_received(:authenticate!)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "when a user is already authenticated and not remotely signed out" do
|
21
|
+
it "doesn't reauthenticate the user" do
|
22
|
+
allow(warden).to receive(:authenticated?).and_return(true)
|
23
|
+
expect(described_class.authenticate_user!(warden)).to be(user)
|
24
|
+
|
25
|
+
expect(warden).not_to have_received(:authenticate!)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "when a user is already authenticated and remotely signed out" do
|
30
|
+
it "authenticates the user again" do
|
31
|
+
user.remotely_signed_out = true
|
32
|
+
expect(described_class.authenticate_user!(warden)).to be(user)
|
33
|
+
expect(warden).to have_received(:authenticate!)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -2,21 +2,54 @@ require "spec_helper"
|
|
2
2
|
require "gds-sso/bearer_token"
|
3
3
|
|
4
4
|
describe GDS::SSO::MockBearerToken do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
GDS::SSO
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
5
|
+
describe ".locate" do
|
6
|
+
it "returns a GDS::SSO.test_user if one is set" do
|
7
|
+
test_user = TestUser.new
|
8
|
+
allow(GDS::SSO).to receive(:test_user).and_return(test_user)
|
9
|
+
|
10
|
+
expect(described_class.locate("anything")).to be(test_user)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "doesn't modify the permissions of GDS::SSO.test_user" do
|
14
|
+
test_user = TestUser.new(permissions: [])
|
15
|
+
allow(GDS::SSO).to receive(:test_user).and_return(test_user)
|
16
|
+
allow(GDS::SSO::Config).to receive(:permissions_for_dummy_api_user)
|
17
|
+
.and_return(%w[signin extra_permission])
|
18
|
+
|
19
|
+
expect(described_class.locate("anything")).to be(test_user)
|
20
|
+
expect(test_user.permissions).not_to include("extra_permission")
|
21
|
+
end
|
22
|
+
|
23
|
+
it "returns a user with dummyapiuser@domain.com if one exists" do
|
24
|
+
test_user = TestUser.new
|
25
|
+
allow(GDS::SSO::Config).to receive(:user_klass).and_return(TestUser)
|
26
|
+
allow(TestUser).to receive(:where).and_return([test_user])
|
27
|
+
|
28
|
+
expect(described_class.locate("anything")).to be(test_user)
|
29
|
+
expect(TestUser).to have_received(:where).with(email: "dummyapiuser@domain.com")
|
30
|
+
end
|
31
|
+
|
32
|
+
it "creates a user with dummyapiuser@domain.com if one does not exist" do
|
33
|
+
allow(GDS::SSO::Config).to receive(:user_klass).and_return(TestUser)
|
34
|
+
allow(GDS::SSO::Config).to receive(:additional_mock_permissions_required).and_return(nil)
|
35
|
+
allow(TestUser).to receive(:where).and_return([])
|
36
|
+
|
37
|
+
test_user = described_class.locate("anything")
|
38
|
+
expect(test_user).to be_an_instance_of(TestUser)
|
39
|
+
expect(test_user).to have_attributes(email: "dummyapiuser@domain.com",
|
40
|
+
name: "Dummy API user created by gds-sso",
|
41
|
+
permissions: %w[signin])
|
42
|
+
end
|
43
|
+
|
44
|
+
it "uses GDS::SSO::Config to overwrite any existing permissions" do
|
45
|
+
test_user = TestUser.new(permissions: %w[signin other_permission])
|
46
|
+
allow(GDS::SSO::Config).to receive(:user_klass).and_return(TestUser)
|
47
|
+
allow(TestUser).to receive(:where).and_return([test_user])
|
48
|
+
allow(GDS::SSO::Config).to receive(:permissions_for_dummy_api_user)
|
49
|
+
.and_return(%w[signin extra_permission])
|
50
|
+
|
51
|
+
test_user = described_class.locate("anything")
|
52
|
+
expect(test_user.permissions).to match_array(%w[signin extra_permission])
|
53
|
+
end
|
21
54
|
end
|
22
55
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gds-sso
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 21.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GOV.UK Dev
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: oauth2
|
@@ -65,6 +65,20 @@ dependencies:
|
|
65
65
|
- - ">="
|
66
66
|
- !ruby/object:Gem::Version
|
67
67
|
version: '5'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: rack
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
type: :runtime
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
68
82
|
- !ruby/object:Gem::Dependency
|
69
83
|
name: rails
|
70
84
|
requirement: !ruby/object:Gem::Requirement
|
@@ -271,6 +285,7 @@ files:
|
|
271
285
|
- spec/unit/authorised_user_constraint_spec.rb
|
272
286
|
- spec/unit/bearer_token_spec.rb
|
273
287
|
- spec/unit/config_spec.rb
|
288
|
+
- spec/unit/gds_sso_spec.rb
|
274
289
|
- spec/unit/mock_bearer_token_spec.rb
|
275
290
|
- spec/unit/railtie_spec.rb
|
276
291
|
- spec/unit/session_serialisation_spec.rb
|
@@ -293,7 +308,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
293
308
|
- !ruby/object:Gem::Version
|
294
309
|
version: '0'
|
295
310
|
requirements: []
|
296
|
-
rubygems_version: 3.
|
311
|
+
rubygems_version: 3.7.1
|
297
312
|
specification_version: 4
|
298
313
|
summary: Client for GDS' OAuth 2-based SSO
|
299
314
|
test_files:
|
@@ -318,6 +333,7 @@ test_files:
|
|
318
333
|
- spec/unit/authorised_user_constraint_spec.rb
|
319
334
|
- spec/unit/bearer_token_spec.rb
|
320
335
|
- spec/unit/config_spec.rb
|
336
|
+
- spec/unit/gds_sso_spec.rb
|
321
337
|
- spec/unit/mock_bearer_token_spec.rb
|
322
338
|
- spec/unit/railtie_spec.rb
|
323
339
|
- spec/unit/session_serialisation_spec.rb
|