main
1# frozen_string_literal: true
2
3require 'rails_helper'
4
5describe "/sessions" do
6 let(:registry) { Saml::Kit::DefaultRegistry.new }
7 let(:issuer) { Saml::Kit.configuration.entity_id }
8 let(:sp_metadata) do
9 Saml::Kit::ServiceProviderMetadata.build do |x|
10 x.add_assertion_consumer_service(FFaker::Internet.uri("https"), binding: :http_post)
11 x.add_single_logout_service(FFaker::Internet.uri("https"), binding: :http_post)
12 end
13 end
14
15 before { Saml::Kit.configuration.registry = registry }
16
17 def session_id_from(response)
18 cookies = response.headers['Set-Cookie']
19 return if cookies.nil?
20
21 cookies.split("\;")[0].split("=")[1]
22 end
23
24 describe "POST /session/new" do
25 let(:post_binding) { Saml::Kit::Bindings::HttpPost.new(location: new_session_url) }
26
27 context "when the user is already logged in" do
28 let(:user) { create(:user) }
29
30 before { http_login(user) }
31
32 context "when a registered SAML request is provided" do
33 before do
34 allow(registry).to receive(:metadata_for).with(issuer).and_return(sp_metadata)
35 url, saml_params = post_binding.serialize(Saml::Kit::AuthenticationRequest.builder)
36 post url, params: saml_params
37 follow_redirect!
38 end
39
40 specify { expect(response).to have_http_status(:ok) }
41 specify { expect(response.body).to include("Sending Response to Service Provider") }
42 end
43
44 context "when an unregistered SAML request is provided" do
45 before do
46 url, saml_params = post_binding.serialize(Saml::Kit::AuthenticationRequest.builder)
47 post url, params: saml_params
48 end
49
50 specify { expect(response).to have_http_status(:forbidden) }
51 end
52
53 context "when a SAML request is not provided" do
54 before { post '/session/new' }
55
56 specify { expect(response).to redirect_to(my_dashboard_path) }
57 end
58 end
59
60 context "when the user is not logged in" do
61 context "when a registered SAML request is provided" do
62 before do
63 allow(registry).to receive(:metadata_for).with(issuer).and_return(sp_metadata)
64 url, saml_params = post_binding.serialize(Saml::Kit::AuthenticationRequest.builder)
65 post url, params: saml_params
66 end
67
68 specify { expect(response).to have_http_status(:ok) }
69 specify { expect(session[:saml]).to be_present }
70 specify { expect(session[:saml][:params]).to be_present }
71 specify { expect(session[:saml][:xml]).to be_present }
72 end
73
74 context "when an unregistered SAML request is provided" do
75 before do
76 url, saml_params = post_binding.serialize(Saml::Kit::AuthenticationRequest.builder)
77 post url, params: saml_params
78 end
79
80 specify { expect(response).to have_http_status(:forbidden) }
81 end
82
83 context "when a SAML request is not provided" do
84 before { post '/session/new' }
85
86 specify { expect(response).to have_http_status(:ok) }
87 specify { expect(response.body).to include("Login") }
88 end
89 end
90 end
91
92 describe "GET /session" do
93 let(:user) { create(:user) }
94
95 before { http_login(user) }
96
97 context 'when logged in' do
98 before { get '/session' }
99
100 specify { expect(response).to have_http_status(:ok) }
101 specify { expect(response.body).to include(I18n.t('sessions.show.logout')) }
102 end
103 end
104
105 describe "GET /session/new" do
106 let(:redirect_binding) { Saml::Kit::Bindings::HttpRedirect.new(location: new_session_url) }
107
108 context "when the user is already logged in" do
109 before { http_login(create(:user)) }
110
111 context "when a registered SAML request is provided" do
112 before do
113 allow(registry).to receive(:metadata_for).with(issuer).and_return(sp_metadata)
114 get redirect_binding.serialize(Saml::Kit::AuthenticationRequest.builder)[0]
115 follow_redirect!
116 end
117
118 specify { expect(response).to have_http_status(:ok) }
119 specify { expect(response.body).to include("Sending Response to Service Provider") }
120 end
121
122 context "when an unregistered SAML request is provided" do
123 before { get redirect_binding.serialize(Saml::Kit::AuthenticationRequest.builder)[0] }
124
125 specify { expect(response).to have_http_status(:forbidden) }
126 end
127
128 context "when a SAML request is not provided" do
129 before { get '/session/new' }
130
131 specify { expect(response).to redirect_to(my_dashboard_path) }
132 end
133 end
134
135 context "when the user is not logged in" do
136 context "when a registered SAML request is provided" do
137 before do
138 allow(registry).to receive(:metadata_for).with(issuer).and_return(sp_metadata)
139 get redirect_binding.serialize(Saml::Kit::AuthenticationRequest.builder)[0]
140 end
141
142 specify { expect(response).to have_http_status(:ok) }
143 specify { expect(session[:saml]).to be_present }
144 specify { expect(session[:saml][:params]).to be_present }
145 specify { expect(session[:saml][:xml]).to be_present }
146 end
147
148 context "when an unregistered SAML request is provided" do
149 before { get redirect_binding.serialize(Saml::Kit::AuthenticationRequest.builder)[0] }
150
151 specify { expect(response).to have_http_status(:forbidden) }
152 end
153
154 context "when a SAML request is not provided" do
155 before { get '/session/new' }
156
157 specify { expect(response).to have_http_status(:ok) }
158 specify { expect(response.body).to include("Login") }
159 end
160 end
161 end
162
163 describe "POST /session" do
164 let(:user) { create(:user) }
165 let(:password) { user.password }
166
167 context "when a SAMLRequest is not present" do
168 context "when the credentials are correct" do
169 before { post '/session', params: { user: { email: user.email, password: password } } }
170
171 specify { expect(response).to redirect_to(response_path) }
172 end
173
174 context "when the credentials are incorrect" do
175 before { post '/session', params: { user: { email: user.email, password: 'incorrect' } } }
176
177 specify { expect(response).to redirect_to(new_session_path) }
178 specify { expect(flash[:error]).to include('Invalid Credentials') }
179 end
180 end
181
182 context "when a SAMLRequest is found in session" do
183 let(:redirect_binding) { Saml::Kit::Bindings::HttpRedirect.new(location: new_session_url) }
184 let(:relay_state) { SecureRandom.uuid }
185
186 before do
187 allow(registry).to receive(:metadata_for).with(issuer).and_return(sp_metadata)
188 get redirect_binding.serialize(Saml::Kit::AuthenticationRequest.builder, relay_state: relay_state)[0]
189 end
190
191 context "when the credentials are correct" do
192 before { post '/session', params: { user: { email: user.email, password: password } } }
193
194 specify { expect(response).to redirect_to(response_path) }
195 specify { expect(session[:saml]).to be_present }
196 specify { expect(session[:saml][:params][:RelayState]).to eql(relay_state) }
197 end
198
199 context "when the credentials are incorrect" do
200 before { post '/session', params: { user: { email: user.email, password: 'incorrect' } } }
201
202 specify { expect(response).to redirect_to(new_session_path) }
203 specify { expect(flash[:error]).to include('Invalid Credentials') }
204 end
205 end
206 end
207
208 describe "DELETE /session" do
209 let(:post_binding) { Saml::Kit::Bindings::HttpPost.new(location: "/session/logout") }
210 let(:user) { create(:user) }
211
212 context "when receiving a logout request" do
213 let(:session_id) { session_id_from(response) }
214
215 before do
216 http_login(user)
217 session_id
218
219 allow(registry).to receive(:metadata_for).with(issuer).and_return(sp_metadata)
220 builder = Saml::Kit::LogoutRequest.builder(user) do |x|
221 x.issuer = issuer
222 x.embed_signature = false
223 end
224 url, saml_params = post_binding.serialize(builder)
225 post url, params: saml_params
226 follow_redirect!
227 end
228
229 specify { expect(response).to have_http_status(:ok) }
230 specify { expect(response.body).to include("SAMLResponse") }
231 specify { expect(response.body).to include(sp_metadata.single_logout_service_for(binding: :http_post).location) }
232 specify { expect(session_id_from(response)).to be_present }
233 specify { expect(session_id_from(response)).not_to eql(session_id) }
234 end
235
236 context "when receiving a logout response" do
237 before do
238 allow(registry).to receive(:metadata_for).with(issuer).and_return(sp_metadata)
239 builder = Saml::Kit::LogoutResponse.builder(Saml::Kit::AuthenticationRequest.build) do |x|
240 x.issuer = issuer
241 x.embed_signature = false
242 end
243 url, saml_params = post_binding.serialize(builder)
244 post url, params: saml_params
245 end
246
247 specify { expect(response).to redirect_to(new_session_url) }
248 end
249
250 context "when logging out of the IDP only" do
251 let(:user) { create(:user) }
252 let(:session_id) { session_id_from(response) }
253
254 before do
255 http_login(user)
256 session_id
257 delete session_path
258 end
259
260 specify { expect(session_id_from(response)).not_to eql(session_id) }
261 specify { expect(session_id_from(response)).to be_present }
262 specify { expect(response).to redirect_to(new_session_path) }
263 end
264 end
265end