Existing user session when requesting SSO session create endpoint will fail device check because of missing fingerprint param (which is required as soon as a user/session is present).

This commit is contained in:
Thorsten Eckel 2019-09-30 19:34:13 +02:00
parent 817cc72194
commit d1ed72a071
5 changed files with 38 additions and 78 deletions

View file

@ -143,10 +143,10 @@ module ApplicationController::Authenticates
User.lookup(login: login&.downcase) User.lookup(login: login&.downcase)
end end
raise Exceptions::NotAuthorized, 'no valid session' if !user raise Exceptions::NotAuthorized, 'Missing SSO ENV REMOTE_USER' if !user
session.delete(:switched_from_user_id) session.delete(:switched_from_user_id)
authentication_check_prerequesits(user, 'session', {}) authentication_check_prerequesits(user, 'SSO', {})
end end
def authentication_check_prerequesits(user, auth_type, auth_param) def authentication_check_prerequesits(user, auth_type, auth_param)

View file

@ -17,6 +17,7 @@ module ApplicationController::HandlesDevices
return true if switched_from_user_id return true if switched_from_user_id
return true if !user return true if !user
return true if !user.permissions?('user_preferences.device') return true if !user.permissions?('user_preferences.device')
return true if type == 'SSO'
time_to_check = true time_to_check = true
user_device_updated_at = session[:user_device_updated_at] user_device_updated_at = session[:user_device_updated_at]

View file

@ -3,6 +3,7 @@
class SessionsController < ApplicationController class SessionsController < ApplicationController
prepend_before_action :authentication_check, only: %i[switch_to_user list delete] prepend_before_action :authentication_check, only: %i[switch_to_user list delete]
skip_before_action :verify_csrf_token, only: %i[show destroy create_omniauth failure_omniauth] skip_before_action :verify_csrf_token, only: %i[show destroy create_omniauth failure_omniauth]
skip_before_action :user_device_check, only: %i[create_sso]
# "Create" a login, aka "log the user in" # "Create" a login, aka "log the user in"
def create def create
@ -14,15 +15,21 @@ class SessionsController < ApplicationController
json: SessionHelper.json_hash(user).merge(config: config_frontend) json: SessionHelper.json_hash(user).merge(config: config_frontend)
end end
def create_sso
authenticate_with_sso
redirect_to '/#'
end
def show def show
user = authentication_check_only || authenticate_with_sso user = authentication_check_only
raise Exceptions::NotAuthorized, 'no valid session' if user.blank?
initiate_session_for(user) initiate_session_for(user)
# return current session # return current session
render json: SessionHelper.json_hash(user).merge(config: config_frontend) render json: SessionHelper.json_hash(user).merge(config: config_frontend)
rescue Exceptions::NotAuthorized => e rescue Exceptions::NotAuthorized => e
raise if e.message != 'no valid session'
render json: { render json: {
error: e.message, error: e.message,
config: config_frontend, config: config_frontend,

View file

@ -5,6 +5,9 @@ Zammad::Application.routes.draw do
match '/auth/:provider/callback', to: 'sessions#create_omniauth', via: %i[post get puts delete] match '/auth/:provider/callback', to: 'sessions#create_omniauth', via: %i[post get puts delete]
match '/auth/failure', to: 'sessions#failure_omniauth', via: %i[post get] match '/auth/failure', to: 'sessions#failure_omniauth', via: %i[post get]
# sso
match '/auth/sso', to: 'sessions#create_sso', via: %i[get post]
# sessions # sessions
match api_path + '/signin', to: 'sessions#create', via: :post match api_path + '/signin', to: 'sessions#create', via: :post
match api_path + '/signshow', to: 'sessions#show', via: %i[get post] match api_path + '/signshow', to: 'sessions#show', via: %i[get post]

View file

@ -1,53 +1,38 @@
require 'rails_helper' require 'rails_helper'
RSpec.describe 'Sessions endpoints', type: :request do RSpec.describe 'Sessions endpoints', type: :request do
# The frontend sends a device fingerprint in the request parameters during authentication
# (as part of App.Auth.loginCheck() and App.WebSocket.auth()).
#
# Without this parameter, the controller will raise a 422 Unprocessable Entity error
# (in ApplicationController::HandlesDevices#user_device_log).
let(:fingerprint) { { fingerprint: 'foo' } }
describe 'GET /api/v1/signshow (single sign-on)' do describe 'GET /auth/sso (single sign-on)' do
context 'with invalid user login' do context 'with invalid user login' do
let(:login) { User.pluck(:login).max.next } let(:login) { User.pluck(:login).max.next }
context 'in "REMOTE_USER" request env var' do context 'in "REMOTE_USER" request env var' do
let(:env) { { 'REMOTE_USER' => login } } let(:env) { { 'REMOTE_USER' => login } }
it 'returns invalid session response' do it 'returns unauthorized response' do
get '/api/v1/signshow', as: :json, env: env, params: fingerprint get '/auth/sso', as: :json, env: env
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:unauthorized)
expect(json_response)
.to include('error' => 'no valid session')
.and not_include('session')
end end
end end
context 'in "HTTP_REMOTE_USER" request env var' do context 'in "HTTP_REMOTE_USER" request env var' do
let(:env) { { 'HTTP_REMOTE_USER' => login } } let(:env) { { 'HTTP_REMOTE_USER' => login } }
it 'returns invalid session response' do it 'returns unauthorized response' do
get '/api/v1/signshow', as: :json, env: env, params: fingerprint get '/auth/sso', as: :json, env: env
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:unauthorized)
expect(json_response)
.to include('error' => 'no valid session')
.and not_include('session')
end end
end end
context 'in "X-Forwarded-User" request header' do context 'in "X-Forwarded-User" request header' do
let(:headers) { { 'X-Forwarded-User' => login } } let(:headers) { { 'X-Forwarded-User' => login } }
it 'returns invalid session response' do it 'returns unauthorized response' do
get '/api/v1/signshow', as: :json, headers: headers, params: fingerprint get '/auth/sso', as: :json, headers: headers
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:unauthorized)
expect(json_response)
.to include('error' => 'no valid session')
.and not_include('session')
end end
end end
end end
@ -63,7 +48,7 @@ RSpec.describe 'Sessions endpoints', type: :request do
let(:env) { { 'REMOTE_USER' => login } } let(:env) { { 'REMOTE_USER' => login } }
it 'returns 401 unauthorized' do it 'returns 401 unauthorized' do
get '/api/v1/signshow', as: :json, env: env, params: fingerprint get '/auth/sso', as: :json, env: env
expect(response).to have_http_status(:unauthorized) expect(response).to have_http_status(:unauthorized)
expect(json_response).to include('error' => 'Maintenance mode enabled!') expect(json_response).to include('error' => 'Maintenance mode enabled!')
@ -74,7 +59,7 @@ RSpec.describe 'Sessions endpoints', type: :request do
let(:env) { { 'HTTP_REMOTE_USER' => login } } let(:env) { { 'HTTP_REMOTE_USER' => login } }
it 'returns 401 unauthorized' do it 'returns 401 unauthorized' do
get '/api/v1/signshow', as: :json, env: env, params: fingerprint get '/auth/sso', as: :json, env: env
expect(response).to have_http_status(:unauthorized) expect(response).to have_http_status(:unauthorized)
expect(json_response).to include('error' => 'Maintenance mode enabled!') expect(json_response).to include('error' => 'Maintenance mode enabled!')
@ -85,7 +70,7 @@ RSpec.describe 'Sessions endpoints', type: :request do
let(:headers) { { 'X-Forwarded-User' => login } } let(:headers) { { 'X-Forwarded-User' => login } }
it 'returns 401 unauthorized' do it 'returns 401 unauthorized' do
get '/api/v1/signshow', as: :json, headers: headers, params: fingerprint get '/auth/sso', as: :json, headers: headers
expect(response).to have_http_status(:unauthorized) expect(response).to have_http_status(:unauthorized)
expect(json_response).to include('error' => 'Maintenance mode enabled!') expect(json_response).to include('error' => 'Maintenance mode enabled!')
@ -97,81 +82,45 @@ RSpec.describe 'Sessions endpoints', type: :request do
let(:env) { { 'REMOTE_USER' => login } } let(:env) { { 'REMOTE_USER' => login } }
it 'returns a new user-session response' do it 'returns a new user-session response' do
get '/api/v1/signshow', as: :json, env: env, params: fingerprint get '/auth/sso', as: :json, env: env
expect(json_response) expect(response).to redirect_to('/#')
.to include('session' => hash_including('login' => login))
.and not_include('error')
end end
it 'sets the :user_id session parameter' do it 'sets the :user_id session parameter' do
expect { get '/api/v1/signshow', as: :json, env: env, params: fingerprint } expect { get '/auth/sso', as: :json, env: env }
.to change { request&.session&.fetch(:user_id) }.to(user.id) .to change { request&.session&.fetch(:user_id) }.to(user.id)
end end
it 'sets the :persistent session parameter' do
expect { get '/api/v1/signshow', as: :json, env: env, params: fingerprint }
.to change { request&.session&.fetch(:persistent) }.to(true)
end
it 'adds an activity stream entry for the users session' do
expect { get '/api/v1/signshow', as: :json, env: env, params: fingerprint }
.to change(ActivityStream, :count).by(1)
end
end end
context 'in "HTTP_REMOTE_USER" request env var' do context 'in "HTTP_REMOTE_USER" request env var' do
let(:env) { { 'HTTP_REMOTE_USER' => login } } let(:env) { { 'HTTP_REMOTE_USER' => login } }
it 'returns a new user-session response' do it 'returns a new user-session response' do
get '/api/v1/signshow', as: :json, env: env, params: fingerprint get '/auth/sso', as: :json, env: env
expect(json_response) expect(response).to redirect_to('/#')
.to include('session' => hash_including('login' => login))
.and not_include('error')
end end
it 'sets the :user_id session parameter' do it 'sets the :user_id session parameter' do
expect { get '/api/v1/signshow', as: :json, env: env, params: fingerprint } expect { get '/auth/sso', as: :json, env: env }
.to change { request&.session&.fetch(:user_id) }.to(user.id) .to change { request&.session&.fetch(:user_id) }.to(user.id)
end end
it 'sets the :persistent session parameter' do
expect { get '/api/v1/signshow', as: :json, env: env, params: fingerprint }
.to change { request&.session&.fetch(:persistent) }.to(true)
end
it 'adds an activity stream entry for the users session' do
expect { get '/api/v1/signshow', as: :json, env: env, params: fingerprint }
.to change(ActivityStream, :count).by(1)
end
end end
context 'in "X-Forwarded-User" request header' do context 'in "X-Forwarded-User" request header' do
let(:headers) { { 'X-Forwarded-User' => login } } let(:headers) { { 'X-Forwarded-User' => login } }
it 'returns a new user-session response' do it 'returns a new user-session response' do
get '/api/v1/signshow', as: :json, headers: headers, params: fingerprint get '/auth/sso', as: :json, headers: headers
expect(json_response) expect(response).to redirect_to('/#')
.to include('session' => hash_including('login' => login))
.and not_include('error')
end end
it 'sets the :user_id session parameter on the client' do it 'sets the :user_id session parameter on the client' do
expect { get '/api/v1/signshow', as: :json, headers: headers, params: fingerprint } expect { get '/auth/sso', as: :json, headers: headers }
.to change { request&.session&.fetch(:user_id) }.to(user.id) .to change { request&.session&.fetch(:user_id) }.to(user.id)
end end
it 'sets the :persistent session parameter' do
expect { get '/api/v1/signshow', as: :json, headers: headers, params: fingerprint }
.to change { request&.session&.fetch(:persistent) }.to(true)
end
it 'adds an activity stream entry for the users session' do
expect { get '/api/v1/signshow', as: :json, headers: headers, params: fingerprint }
.to change(ActivityStream, :count).by(1)
end
end end
end end
end end