gds-sso 20.0.0 → 21.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.
- checksums.yaml +4 -4
- data/README.md +14 -1
- data/config/routes.rb +4 -3
- data/lib/gds-sso/api_access.rb +27 -1
- data/lib/gds-sso/authorised_user_constraint.rb +2 -3
- data/lib/gds-sso/bearer_token.rb +6 -1
- data/lib/gds-sso/config.rb +7 -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/internal/config/routes.rb +1 -1
- data/spec/request/api/user_spec.rb +144 -0
- data/spec/request/authentication_spec.rb +77 -0
- data/spec/request/demo_app_spec.rb +264 -0
- data/spec/spec_helper.rb +16 -7
- data/spec/support/request_helpers.rb +45 -0
- data/spec/unit/api_access_spec.rb +80 -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 +55 -16
- data/spec/unit/session_serialisation_spec.rb +5 -4
- metadata +34 -30
- data/spec/controller/api_user_controller_spec.rb +0 -114
- data/spec/support/timecop.rb +0 -7
- data/spec/system/authentication_and_authorisation_spec.rb +0 -172
- /data/spec/{controller → unit}/controller_methods_spec.rb +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 72938e39cf2466b8a2dc2d22aca03d64ff33ab2a8c5d11c0c57f3710a2935205
|
4
|
+
data.tar.gz: 042b5c966919ab345d9ea3dc9e47197947fdd75e51ad263b0f0f038397c09fa9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fdca3de72981e79aa420da0b3d6c4d689fba034d3d3cda4874082efe58bcf056128242ab09a4266b8d11f91da500d47b665e434ccb805a3f6a799d6d29f765f4
|
7
|
+
data.tar.gz: ad252eb2aeaf3986b68b54a15f7f4622f4d56e730d509690e1e80c77be6411201ebf618ba68efebaff825fe731a111de186b439b6834a4c1de91782745658335
|
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/config/routes.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
Rails.application.routes.draw do
|
2
|
+
put "/auth/gds/api/users/:uid", to: "api/user#update"
|
3
|
+
post "/auth/gds/api/users/:uid/reauth", to: "api/user#reauth"
|
4
|
+
|
2
5
|
next if GDS::SSO::Config.api_only
|
3
6
|
|
4
7
|
get "/auth/gds/callback", to: "authentications#callback", as: :gds_sign_in
|
5
8
|
get "/auth/gds/sign_out", to: "authentications#sign_out", as: :gds_sign_out
|
6
|
-
get
|
7
|
-
put "/auth/gds/api/users/:uid", to: "api/user#update"
|
8
|
-
post "/auth/gds/api/users/:uid/reauth", to: "api/user#reauth"
|
9
|
+
get "/auth/failure", to: "authentications#failure", as: :auth_failure
|
9
10
|
end
|
data/lib/gds-sso/api_access.rb
CHANGED
@@ -1,8 +1,34 @@
|
|
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
|
+
request = Rack::Request.new(env)
|
12
|
+
|
13
|
+
gds_sso_api_request_matcher = GDS::SSO::Config.gds_sso_api_request_matcher
|
14
|
+
return true if gds_sso_api_request_matcher&.call(request)
|
15
|
+
|
16
|
+
return GDS::SSO::Config.api_request_matcher.call(request)
|
17
|
+
end
|
18
|
+
|
19
|
+
!bearer_token(env).nil?
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.bearer_token(env)
|
23
|
+
Rack::Auth::AbstractRequest::AUTHORIZATION_KEYS.each do |key|
|
24
|
+
next unless env.key?(key)
|
25
|
+
|
26
|
+
if (match = env[key].match(/\ABearer (.+)/))
|
27
|
+
return match[1]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
nil
|
6
32
|
end
|
7
33
|
end
|
8
34
|
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,10 @@ module GDS
|
|
56
58
|
|
57
59
|
module MockBearerToken
|
58
60
|
def self.locate(_token_string)
|
59
|
-
|
61
|
+
return unless ENV["GDS_SSO_MOCK_INVALID"].to_s.empty?
|
62
|
+
return GDS::SSO.test_user if GDS::SSO.test_user
|
63
|
+
|
64
|
+
dummy_api_user = GDS::SSO::Config.user_klass.where(email: "dummyapiuser@domain.com").first
|
60
65
|
if dummy_api_user.nil?
|
61
66
|
dummy_api_user = GDS::SSO::Config.user_klass.new
|
62
67
|
dummy_api_user.email = "dummyapiuser@domain.com"
|
data/lib/gds-sso/config.rb
CHANGED
@@ -29,6 +29,13 @@ module GDS
|
|
29
29
|
|
30
30
|
mattr_accessor :api_only
|
31
31
|
|
32
|
+
mattr_accessor :api_request_matcher
|
33
|
+
|
34
|
+
# A matcher for GDS SSO's own API for updating user details. Shouldn't
|
35
|
+
# need configuring unless you mount the API at a different location.
|
36
|
+
mattr_accessor :gds_sso_api_request_matcher
|
37
|
+
@@gds_sso_api_request_matcher = ->(request) { request.path.start_with?("/auth/gds/api/") }
|
38
|
+
|
32
39
|
mattr_accessor :intercept_401_responses
|
33
40
|
@@intercept_401_responses = true
|
34
41
|
|
@@ -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...
|
@@ -5,7 +5,7 @@ Rails.application.routes.draw do
|
|
5
5
|
get "/restricted" => "example#restricted"
|
6
6
|
get "/this-requires-execute-permission" => "example#this_requires_execute_permission"
|
7
7
|
|
8
|
-
constraints(GDS::SSO::AuthorisedUserConstraint.new("
|
8
|
+
constraints(GDS::SSO::AuthorisedUserConstraint.new("constraint")) do
|
9
9
|
get "/constraint-restricted" => "example#constraint_restricted"
|
10
10
|
end
|
11
11
|
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Api::UserController", type: :request do
|
4
|
+
shared_examples "rejects a request from an unauthenticated user" do |method, path|
|
5
|
+
it "rejects the request when a user is unauthenticated" do
|
6
|
+
stub_failed_signon_user_request
|
7
|
+
public_send(method, path, headers: { "Authorization" => "Bearer anything" })
|
8
|
+
expect(response).to have_http_status(:unauthorized)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
shared_examples "rejects a request from an authenticated user lacking permission" do |method, path|
|
13
|
+
it "rejects the request when a user is authenticated but lacking permission" do
|
14
|
+
stub_successful_signon_user_request(permissions: [])
|
15
|
+
public_send(method, path, headers: { "Authorization" => "Bearer anything" })
|
16
|
+
expect(response).to have_http_status(:forbidden)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
shared_examples "operates as an API endpoint if api_request_matcher doesn't match it" do |method, path|
|
21
|
+
it "doesn't redirect to /auth/gds because it's recognised as an API request" do
|
22
|
+
allow(GDS::SSO::Config)
|
23
|
+
.to receive(:api_request_matcher)
|
24
|
+
.and_return(->(_request) { false })
|
25
|
+
|
26
|
+
stub_failed_signon_user_request
|
27
|
+
public_send(method, path, headers: { "Authorization" => "Bearer anything" })
|
28
|
+
expect(response.media_type).to eq("application/json")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
shared_examples "redirects to /auth/gds if gds_sso_api_request_matcher is configured to not match" do |method, path|
|
33
|
+
it "fails if gds_sso_api_request_matcher is configured to not match" do
|
34
|
+
allow(GDS::SSO::Config)
|
35
|
+
.to receive(:api_request_matcher)
|
36
|
+
.and_return(->(_request) { false })
|
37
|
+
|
38
|
+
allow(GDS::SSO::Config)
|
39
|
+
.to receive(:gds_sso_api_request_matcher)
|
40
|
+
.and_return(nil)
|
41
|
+
|
42
|
+
public_send(method, path, headers: { "Authorization" => "Bearer anything" })
|
43
|
+
expect(response).to redirect_to("/auth/gds")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "PUT /auth/gds/api/users/:uid" do
|
48
|
+
shared_params = [:put, "/auth/gds/api/users/#{SecureRandom.uuid}"]
|
49
|
+
it_behaves_like "rejects a request from an unauthenticated user", *shared_params
|
50
|
+
it_behaves_like "rejects a request from an authenticated user lacking permission", *shared_params
|
51
|
+
it_behaves_like "operates as an API endpoint if api_request_matcher doesn't match it", *shared_params
|
52
|
+
it_behaves_like "redirects to /auth/gds if gds_sso_api_request_matcher is configured to not match", *shared_params
|
53
|
+
|
54
|
+
it "updates an existing user" do
|
55
|
+
stub_successful_signon_user_request(permissions: %w[user_update_permission])
|
56
|
+
|
57
|
+
user = User.create!(
|
58
|
+
uid: SecureRandom.uuid,
|
59
|
+
email: "user@example.com",
|
60
|
+
name: "Example User",
|
61
|
+
permissions: [],
|
62
|
+
)
|
63
|
+
|
64
|
+
put "/auth/gds/api/users/#{user.uid}",
|
65
|
+
headers: { "Authorization" => "Bearer anything" },
|
66
|
+
params: user_update_params(user, { "name" => "John Matrix" }),
|
67
|
+
as: :json
|
68
|
+
|
69
|
+
expect(response).to have_http_status(:success)
|
70
|
+
expect(response.body).to eq("")
|
71
|
+
expect(user.reload.name).to eq("John Matrix")
|
72
|
+
end
|
73
|
+
|
74
|
+
it "creates a new user if a user does not exist" do
|
75
|
+
stub_successful_signon_user_request(permissions: %w[user_update_permission])
|
76
|
+
|
77
|
+
user = User.new(
|
78
|
+
uid: SecureRandom.uuid,
|
79
|
+
email: "user@example.com",
|
80
|
+
name: "Example User",
|
81
|
+
permissions: [],
|
82
|
+
)
|
83
|
+
|
84
|
+
put "/auth/gds/api/users/#{user.uid}",
|
85
|
+
headers: { "Authorization" => "Bearer anything" },
|
86
|
+
params: user_update_params(user),
|
87
|
+
as: :json
|
88
|
+
|
89
|
+
expect(response).to have_http_status(:success)
|
90
|
+
expect(response.body).to eq("")
|
91
|
+
expect(User.last.uid).to eq(user.uid)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "POST /auth/gds/api/users/:uid/reauth" do
|
96
|
+
shared_params = [:post, "/auth/gds/api/users/#{SecureRandom.uuid}/reauth"]
|
97
|
+
it_behaves_like "rejects a request from an unauthenticated user", *shared_params
|
98
|
+
it_behaves_like "rejects a request from an authenticated user lacking permission", *shared_params
|
99
|
+
it_behaves_like "operates as an API endpoint if api_request_matcher doesn't match it", *shared_params
|
100
|
+
it_behaves_like "redirects to /auth/gds if gds_sso_api_request_matcher is configured to not match", *shared_params
|
101
|
+
|
102
|
+
it "flags a user that exists as remotely signed out" do
|
103
|
+
stub_successful_signon_user_request(permissions: %w[user_update_permission])
|
104
|
+
|
105
|
+
user = User.create!(
|
106
|
+
uid: SecureRandom.uuid,
|
107
|
+
email: "user@example.com",
|
108
|
+
name: "Example User",
|
109
|
+
permissions: [],
|
110
|
+
)
|
111
|
+
|
112
|
+
expect {
|
113
|
+
post "/auth/gds/api/users/#{user.uid}/reauth",
|
114
|
+
headers: { "Authorization" => "Bearer anything" }
|
115
|
+
}.to change { user.reload.remotely_signed_out }.to(true)
|
116
|
+
|
117
|
+
expect(response).to have_http_status(:success)
|
118
|
+
expect(response.body).to eq("")
|
119
|
+
end
|
120
|
+
|
121
|
+
it "responds successfully even if the user doesn't exist" do
|
122
|
+
stub_successful_signon_user_request(permissions: %w[user_update_permission])
|
123
|
+
|
124
|
+
user = User.new(
|
125
|
+
uid: SecureRandom.uuid,
|
126
|
+
email: "user@example.com",
|
127
|
+
name: "Example User",
|
128
|
+
permissions: [],
|
129
|
+
)
|
130
|
+
|
131
|
+
post "/auth/gds/api/users/#{user.uid}/reauth",
|
132
|
+
headers: { "Authorization" => "Bearer anything" }
|
133
|
+
|
134
|
+
expect(response).to have_http_status(:success)
|
135
|
+
expect(response.body).to eq("")
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def user_update_params(user, modifications = {})
|
140
|
+
fields = %i[uid name email permissions organisation_slug organisation_content_id disabled]
|
141
|
+
user_details = user.as_json(only: fields).merge(modifications)
|
142
|
+
{ "user" => user_details }
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "AuthenticationController", type: :request do
|
4
|
+
describe "GET /auth/gds/callback" do
|
5
|
+
it "fails without a valid state param" do
|
6
|
+
get "/auth/gds/callback"
|
7
|
+
|
8
|
+
expect(response).to redirect_to("/auth/failure?message=csrf_detected&strategy=gds")
|
9
|
+
end
|
10
|
+
|
11
|
+
it "redirects to the attempted url if a user was restricted earlier in the session" do
|
12
|
+
get "/restricted"
|
13
|
+
|
14
|
+
state = request_to_establish_oauth_state
|
15
|
+
|
16
|
+
stub_signon_oauth_token_request
|
17
|
+
stub_successful_signon_user_request
|
18
|
+
|
19
|
+
get "/auth/gds/callback?state=#{state}"
|
20
|
+
expect(response).to redirect_to("/restricted")
|
21
|
+
end
|
22
|
+
|
23
|
+
it "redirects to the root path if the user hadn't tried to access a restricted url" do
|
24
|
+
state = request_to_establish_oauth_state
|
25
|
+
|
26
|
+
stub_signon_oauth_token_request
|
27
|
+
stub_successful_signon_user_request
|
28
|
+
|
29
|
+
get "/auth/gds/callback?state=#{state}"
|
30
|
+
expect(response).to redirect_to("/")
|
31
|
+
end
|
32
|
+
|
33
|
+
it "uses the OAuth2 proof key for code exchange feature for increased security" do
|
34
|
+
get "/auth/gds"
|
35
|
+
|
36
|
+
expect(response).to have_http_status(:redirect)
|
37
|
+
location = URI.parse(response.location)
|
38
|
+
query = Rack::Utils.parse_query(location.query)
|
39
|
+
|
40
|
+
expect(location.path).to eq("/oauth/authorize")
|
41
|
+
expect(query).to include("code_challenge", "code_challenge_method")
|
42
|
+
|
43
|
+
token_request = stub_request(:post, "http://signon/oauth/access_token")
|
44
|
+
.with(body: hash_including("code_verifier"))
|
45
|
+
|
46
|
+
get "/auth/gds/callback?state=#{query['state']}"
|
47
|
+
|
48
|
+
expect(token_request).to have_been_made
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "GET /auth/failure" do
|
53
|
+
it "responds successfully" do
|
54
|
+
get "/auth/failure"
|
55
|
+
|
56
|
+
expect(response).to have_http_status(:success)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "GET /auth/gds/sign_out" do
|
61
|
+
it "redirects to signon sign out" do
|
62
|
+
get "/auth/gds/sign_out"
|
63
|
+
|
64
|
+
expect(response).to redirect_to("http://signon/users/sign_out")
|
65
|
+
end
|
66
|
+
|
67
|
+
it "logs an authenticated user out" do
|
68
|
+
authenticate_with_stub_signon
|
69
|
+
|
70
|
+
get "/auth/gds/sign_out"
|
71
|
+
|
72
|
+
# access a restricted route to assert we're logged out
|
73
|
+
get "/restricted"
|
74
|
+
expect(response).to redirect_to("/auth/gds")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|