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