Fixes #3128 - Add SSO login button to login page.

This commit is contained in:
Marcel Herrguth 2020-08-26 15:21:17 +02:00 committed by Thorsten Eckel
parent f6c2d810bc
commit 8d140037e4
15 changed files with 172 additions and 16 deletions

View file

@ -688,5 +688,10 @@
"author": "Felix Niklas", "author": "Felix Niklas",
"url": "", "url": "",
"license": "MIT" "license": "MIT"
},
"sso-button.svg": {
"author": "Tanu Doank",
"url": "https://thenounproject.com/term/key/1247931/",
"license": "CC 3.0 Attribution"
} }
} }

View file

@ -111,4 +111,9 @@ App.Config.set('auth_provider_all', {
name: 'SAML' name: 'SAML'
config: 'auth_saml' config: 'auth_saml'
class: 'saml' class: 'saml'
sso:
url: '/auth/sso'
name: 'SSO'
config: 'auth_sso'
class: 'sso'
}) })

View file

@ -114,6 +114,7 @@
.icon-sms { width: 17px; height: 17px; } .icon-sms { width: 17px; height: 17px; }
.icon-spinner-small { width: 15px; height: 15px; } .icon-spinner-small { width: 15px; height: 15px; }
.icon-split { width: 16px; height: 17px; } .icon-split { width: 16px; height: 17px; }
.icon-sso-button { width: 29px; height: 24px; }
.icon-status-modified-outer-circle { width: 16px; height: 16px; } .icon-status-modified-outer-circle { width: 16px; height: 16px; }
.icon-status { width: 16px; height: 16px; } .icon-status { width: 16px; height: 16px; }
.icon-stopwatch { width: 77px; height: 83px; } .icon-stopwatch { width: 77px; height: 83px; }

View file

@ -3199,6 +3199,10 @@ ol.tabs li {
background: hsl(0,0%,27%); background: hsl(0,0%,27%);
} }
&.auth-provider--sso {
background: #454545;
}
.provider-name { .provider-name {
flex: 1; flex: 1;
} }
@ -3399,6 +3403,10 @@ ol.tabs li {
fill: white; fill: white;
} }
.icon-sso-button {
fill: white;
}
/* /*
* removed margin of forms to not break the layout with submit buttons within <form></form> area e. g. for modal dialogs * removed margin of forms to not break the layout with submit buttons within <form></form> area e. g. for modal dialogs

View file

@ -141,21 +141,6 @@ module ApplicationController::Authenticates
authentication_check_prerequesits(user, 'session', {}) authentication_check_prerequesits(user, 'session', {})
end end
def authenticate_with_sso
user = begin
login = request.env['REMOTE_USER'] ||
request.env['HTTP_REMOTE_USER'] ||
request.headers['X-Forwarded-User']
User.lookup(login: login&.downcase)
end
raise Exceptions::NotAuthorized, 'Missing SSO ENV REMOTE_USER' if login.blank?
raise Exceptions::NotAuthorized, "No such user #{login} from ENV REMOTE_USER" if !user
session.delete(:switched_from_user_id)
authentication_check_prerequesits(user, 'SSO', {})
end
def authentication_check_prerequesits(user, auth_type, auth_param) def authentication_check_prerequesits(user, auth_type, auth_param)
raise Exceptions::NotAuthorized, 'Maintenance mode enabled!' if in_maintenance_mode?(user) raise Exceptions::NotAuthorized, 'Maintenance mode enabled!' if in_maintenance_mode?(user)

View file

@ -16,7 +16,21 @@ class SessionsController < ApplicationController
end end
def create_sso def create_sso
authenticate_with_sso raise Exceptions::NotAuthorized, 'SSO authentication disabled!' if !Setting.get('auth_sso')
user = begin
login = request.env['REMOTE_USER'] ||
request.env['HTTP_REMOTE_USER'] ||
request.headers['X-Forwarded-User']
User.lookup(login: login&.downcase)
end
raise Exceptions::NotAuthorized, 'Missing SSO ENV REMOTE_USER or X-Forwarded-User header' if login.blank?
raise Exceptions::NotAuthorized, "No such user '#{login}' found!" if user.blank?
session.delete(:switched_from_user_id)
authentication_check_prerequesits(user, 'SSO', {})
redirect_to '/#' redirect_to '/#'
end end

View file

@ -31,6 +31,9 @@
ProxyPass /ws ws://127.0.0.1:6042/ ProxyPass /ws ws://127.0.0.1:6042/
ProxyPass / http://127.0.0.1:3000/ ProxyPass / http://127.0.0.1:3000/
# change this line in an SSO setup
RequestHeader unset X-Forwarded-User
DocumentRoot "/opt/zammad/public" DocumentRoot "/opt/zammad/public"
<Directory /> <Directory />

View file

@ -54,6 +54,9 @@
ProxyPass /ws ws://127.0.0.1:6042/ ProxyPass /ws ws://127.0.0.1:6042/
ProxyPass / http://127.0.0.1:3000/ ProxyPass / http://127.0.0.1:3000/
# change this line in an SSO setup
RequestHeader unset X-Forwarded-User
# Use settings below if proxying does not work and you receive HTTP-Errror 404 # Use settings below if proxying does not work and you receive HTTP-Errror 404
# if you use the settings below, make sure to comment out the above two options # if you use the settings below, make sure to comment out the above two options
# This may not apply to all systems, applies to openSuse # This may not apply to all systems, applies to openSuse

View file

@ -46,6 +46,10 @@ server {
proxy_set_header CLIENT_IP $remote_addr; proxy_set_header CLIENT_IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
# Change this line in an SSO setup
proxy_set_header X-Forwarded-User "";
proxy_read_timeout 300; proxy_read_timeout 300;
proxy_pass http://zammad-railsserver; proxy_pass http://zammad-railsserver;

View file

@ -100,6 +100,10 @@ server {
proxy_set_header Host $http_host; proxy_set_header Host $http_host;
proxy_set_header CLIENT_IP $remote_addr; proxy_set_header CLIENT_IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# change this line in an SSO setup
proxy_set_header X-Forwarded-User "";
proxy_read_timeout 180; proxy_read_timeout 180;
proxy_pass http://zammad-railsserver; proxy_pass http://zammad-railsserver;

View file

@ -0,0 +1,38 @@
class Issue3128AddSso < ActiveRecord::Migration[5.2]
def change
# return if it's a new setup
return if !Setting.exists?(name: 'system_init_done')
Setting.create_if_not_exists(
title: 'Authentication via %s',
name: 'auth_sso',
area: 'Security::ThirdPartyAuthentication',
description: 'Enables button for user authentication via %s. The button will redirect to /auth/sso on user interaction.',
options: {
form: [
{
display: '',
null: true,
name: 'auth_sso',
tag: 'boolean',
options: {
true => 'yes',
false => 'no',
},
},
],
},
preferences: {
controller: 'SettingsAreaSwitch',
sub: {},
title_i18n: ['SSO'],
description_i18n: ['SSO', 'Button for Single Sign On.'],
permission: ['admin.security'],
},
state: false,
frontend: true
)
end
end

View file

@ -4613,3 +4613,33 @@ Setting.create_if_not_exists(
}, },
frontend: true, frontend: true,
) )
Setting.create_if_not_exists(
title: 'Authentication via %s',
name: 'auth_sso',
area: 'Security::ThirdPartyAuthentication',
description: 'Enables button for user authentication via %s. The button will redirect to /auth/sso on user interaction.',
options: {
form: [
{
display: '',
null: true,
name: 'auth_sso',
tag: 'boolean',
options: {
true => 'yes',
false => 'no',
},
},
],
},
preferences: {
controller: 'SettingsAreaSwitch',
sub: {},
title_i18n: ['SSO'],
description_i18n: ['SSO', 'Button for Single Sign On.'],
permission: ['admin.security'],
},
state: false,
frontend: true
)

View file

@ -763,6 +763,9 @@
split split
</title> </title>
<path d="M4.443 6.218l.42-.343C6.415 4.589 7 3.514 7 1h2c0 2.514.584 3.589 2.138 4.875l.42.343C13.293 7.64 14 8.773 14 11h-4l3 3.333L16 11h-4c0-1.526-.393-2.156-1.71-3.235.038.031-.327-.266-.428-.35C8.945 6.655 8.5 6.5 8 5.4c-.5 1.1-.945 1.256-1.862 2.015-.101.084-.466.381-.428.35C4.393 8.845 4 9.474 4 11H2h4l-3 3.333L0 11h2c0-2.227.706-3.36 2.443-4.782z" fill-rule="evenodd"/> <path d="M4.443 6.218l.42-.343C6.415 4.589 7 3.514 7 1h2c0 2.514.584 3.589 2.138 4.875l.42.343C13.293 7.64 14 8.773 14 11h-4l3 3.333L16 11h-4c0-1.526-.393-2.156-1.71-3.235.038.031-.327-.266-.428-.35C8.945 6.655 8.5 6.5 8 5.4c-.5 1.1-.945 1.256-1.862 2.015-.101.084-.466.381-.428.35C4.393 8.845 4 9.474 4 11H2h4l-3 3.333L0 11h2c0-2.227.706-3.36 2.443-4.782z" fill-rule="evenodd"/>
</symbol><symbol id="icon-sso-button" viewBox="0 0 96.57 79.92">
<path d="M10.41 41.942c0-1.657 1.325-2.983 3.314-2.983a2.97 2.97 0 0 1 2.982 2.983 2.97 2.97 0 0 1-2.982 2.982c-1.989 0-3.314-1.325-3.314-2.982zm21.872-4.64c0-1.325.994-2.32 2.32-2.32 1.325 0 2.32.995 2.32 2.32 0 1.326-.995 1.988-2.32 1.988-1.326 0-2.32-.662-2.32-1.988zm-7.622 0c0-.663.662-1.325 1.657-1.325.662 0 1.325.662 1.325 1.325 0 .663-.663 1.326-1.325 1.326-.995 0-1.657-.663-1.657-1.326zm18.226 1.988c-.994 0-1.988-.662-1.988-1.988 0-1.325.994-2.32 1.988-2.32 1.326 0 2.32.995 2.32 2.32 0 1.326-.994 1.988-2.32 1.988zm-8.285 5.634c-.662 0-1.325-.663-1.325-1.325 0-.995.663-1.657 1.325-1.657.663 0 1.326.662 1.326 1.657 0 .662-.663 1.325-1.326 1.325zm20.547-4.64c0-2.65.662-5.302 1.325-7.621.663-1.326.994-2.651 1.657-3.646h-8.616c-.663 0-.994.663-.994 1.326v2.32H13.06c-4.97 0-9.61 2.65-12.593 7.622 2.983 4.64 7.622 7.29 12.593 7.29H48.52v2.651c0 .663.331.995.994.995h8.616c-.663-.995-.994-2.32-1.657-3.646-.663-2.32-1.325-4.64-1.325-7.29z"/>
<path d="M86.63 40.285c0 2.32-2.32 4.64-4.64 4.64-2.65 0-4.97-2.32-4.97-4.64 0-2.652 2.32-4.64 4.97-4.64 2.32 0 4.64 1.988 4.64 4.64zm9.61 0c0-10.936-8.616-19.552-19.22-19.552-7.291 0-13.588 3.976-16.902 9.61-1.656 2.982-2.65 6.296-2.65 9.942 0 3.314.994 6.627 2.65 9.61 3.314 5.634 9.61 9.61 16.901 9.61 10.605 0 19.221-8.616 19.221-19.22z"/>
</symbol><symbol id="icon-status-modified-outer-circle" viewBox="0 0 16 16"> </symbol><symbol id="icon-status-modified-outer-circle" viewBox="0 0 16 16">
<title> <title>
status-modified-outer-circle status-modified-outer-circle

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
clip-rule="evenodd"
fill-rule="evenodd"
y="0px"
x="0px"
viewBox="0 0 96.57 79.92"
style="image-rendering:optimizeQuality;shape-rendering:geometricPrecision;text-rendering:geometricPrecision"
version="1.1"
xml:space="preserve"
fill="#50e3c2"
width="29"
height="24">
<defs
id="defs24" />
<g
transform="matrix(0.33139046,0,0,0.33139046,-6.8223822,-15.05759)"
id="g30"
fill="#50e3c2">
<path
id="path26"
d="m 52,172 c 0,-5 4,-9 10,-9 5,0 9,4 9,9 0,5 -4,9 -9,9 -6,0 -10,-4 -10,-9 z m 66,-14 c 0,-4 3,-7 7,-7 4,0 7,3 7,7 0,4 -3,6 -7,6 -4,0 -7,-2 -7,-6 z m -23,0 c 0,-2 2,-4 5,-4 2,0 4,2 4,4 0,2 -2,4 -4,4 -3,0 -5,-2 -5,-4 z m 55,6 c -3,0 -6,-2 -6,-6 0,-4 3,-7 6,-7 4,0 7,3 7,7 0,4 -3,6 -7,6 z m -25,17 c -2,0 -4,-2 -4,-4 0,-3 2,-5 4,-5 2,0 4,2 4,5 0,2 -2,4 -4,4 z m 62,-14 c 0,-8 2,-16 4,-23 2,-4 3,-8 5,-11 h -26 c -2,0 -3,2 -3,4 v 7 H 60 c -15,0 -29,8 -38,23 9,14 23,22 38,22 h 107 v 8 c 0,2 1,3 3,3 h 26 c -2,-3 -3,-7 -5,-11 -2,-7 -4,-14 -4,-22 z" />
<path
id="path28"
d="m 282,167 c 0,7 -7,14 -14,14 -8,0 -15,-7 -15,-14 0,-8 7,-14 15,-14 7,0 14,6 14,14 z m 29,0 c 0,-33 -26,-59 -58,-59 -22,0 -41,12 -51,29 -5,9 -8,19 -8,30 0,10 3,20 8,29 10,17 29,29 51,29 32,0 58,-26 58,-58 z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -30,6 +30,27 @@ RSpec.describe 'Sessions endpoints', type: :request do
end end
describe 'GET /auth/sso (single sign-on)' do describe 'GET /auth/sso (single sign-on)' do
before do
Setting.set('auth_sso', true)
end
context 'when SSO is disabled' do
before do
Setting.set('auth_sso', false)
end
let(:headers) { { 'X-Forwarded-User' => login } }
let(:login) { User.last.login }
it 'returns a new user-session response' do
get '/auth/sso', as: :json, headers: headers
expect(response).to have_http_status(:unauthorized)
end
end
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 }