From 48c71a4b51f5359a7ab7e9bc51280baf9bc9b9b2 Mon Sep 17 00:00:00 2001 From: Moritz Bunkus Date: Mon, 29 Apr 2019 15:54:30 +0200 Subject: [PATCH] =?utf8?q?Auth:=20Unterst=C3=BCtzung=20f=C3=BCr=20multiple?= =?utf8?q?=20Authentifizierungsbackends?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Über den Parameter "module" kann man nun multiple Backends angeben, die nacheinander versucht werden, bis ein Erfolg gemeldet wird oder die Liste durchlaufen wurde. Zusätzlich kann man LDAP-Module mehrfach angeben. Damit unterschiedliche Konfigurationen für jede Modulinstanz benutzt werden können, wurde die Syntax erweitert: für "LDAP:Config-Abschnitts-Name" wird "[authentication/Config-Abschnitts-Name]" benutzt. Zwecks Rückwärtskompatibilität sucht "LDAP" ohne Angabe eines Namens nach dem bisher auch verwendeten Abschnitt "[authentication/ldap]". Nützlich ist das Ganze z.B., um einen LDAP-Fallback-Server angeben zu können, der benutzt wird, wenn der Hauptserver nicht erreichbar sein sollte. --- SL/Auth.pm | 59 ++++++++++++++++++++++++++--------- SL/Auth/LDAP.pm | 21 ++++++++----- config/kivitendo.conf.default | 23 ++++++++++++-- locale/de/all | 3 +- 4 files changed, 79 insertions(+), 27 deletions(-) diff --git a/SL/Auth.pm b/SL/Auth.pm index 830e7fb51..2898222aa 100644 --- a/SL/Auth.pm +++ b/SL/Auth.pm @@ -5,7 +5,7 @@ use DBI; use Digest::MD5 qw(md5_hex); use IO::File; use Time::HiRes qw(gettimeofday); -use List::MoreUtils qw(uniq); +use List::MoreUtils qw(any uniq); use YAML; use Regexp::IPv6 qw($IPv6_re); @@ -72,7 +72,7 @@ sub reset { delete $self->{column_information}; } - $self->{authenticator}->reset; + $_->reset for @{ $self->{authenticators} }; $self->client(undef); } @@ -145,16 +145,31 @@ sub _read_auth_config { $self->{DB_config} = $::lx_office_conf{'authentication/database'}; } - if ($self->{module} eq 'DB') { - $self->{authenticator} = SL::Auth::DB->new($self); + $self->{authenticators} = []; + $self->{module} ||= 'DB'; + $self->{module} =~ s{^ +| +$}{}g; - } elsif ($self->{module} eq 'LDAP') { - $self->{authenticator} = SL::Auth::LDAP->new($::lx_office_conf{'authentication/ldap'}); - } + foreach my $module (split m{ +}, $self->{module}) { + my $config_name; + ($module, $config_name) = split m{:}, $module, 2; + $config_name ||= $module eq 'DB' ? 'database' : lc($module); + my $config = $::lx_office_conf{'authentication/' . $config_name}; - if (!$self->{authenticator}) { - my $locale = Locale->new('en'); - $self->mini_error($locale->text('No or an unknown authenticantion module specified in "config/kivitendo.conf".')); + if (!$config) { + my $locale = Locale->new('en'); + $self->mini_error($locale->text('Missing configuration section "authentication/#1" in "config/kivitendo.conf".', $config_name)); + } + + if ($module eq 'DB') { + push @{ $self->{authenticators} }, SL::Auth::DB->new($self); + + } elsif ($module eq 'LDAP') { + push @{ $self->{authenticators} }, SL::Auth::LDAP->new($config); + + } else { + my $locale = Locale->new('en'); + $self->mini_error($locale->text('Unknown authenticantion module #1 specified in "config/kivitendo.conf".', $module)); + } } my $cfg = $self->{DB_config}; @@ -169,7 +184,7 @@ sub _read_auth_config { $self->mini_error($locale->text('config/kivitendo.conf: Missing parameters in "authentication/database". Required parameters are "host", "db" and "user".')); } - $self->{authenticator}->verify_config(); + $_->verify_config for @{ $self->{authenticators} }; $self->{session_timeout} *= 1; $self->{session_timeout} = 8 * 60 if (!$self->{session_timeout}); @@ -229,7 +244,14 @@ sub authenticate { return ERR_PASSWORD; } - my $result = $login ? $self->{authenticator}->authenticate($login, $password) : ERR_USER; + my $result = ERR_USER; + if ($login) { + foreach my $authenticator (@{ $self->{authenticators} }) { + $result = $authenticator->authenticate($login, $password); + last if $result == OK; + } + } + $self->set_session_value(SESSION_KEY_USER_AUTH() => $result, login => $login, client_id => $self->client->{id}); return $result; } @@ -414,15 +436,22 @@ sub save_user { sub can_change_password { my $self = shift; - return $self->{authenticator}->can_change_password(); + return any { $_->can_change_password } @{ $self->{authenticators} }; } sub change_password { my ($self, $login, $new_password) = @_; - my $result = $self->{authenticator}->change_password($login, $new_password); + my $overall_result = OK; - return $result; + foreach my $authenticator (@{ $self->{authenticators} }) { + next unless $authenticator->can_change_password; + + my $result = $authenticator->change_password($login, $new_password); + $overall_result = $result if $result != OK; + } + + return $overall_result; } sub read_all_users { diff --git a/SL/Auth/LDAP.pm b/SL/Auth/LDAP.pm index b42bf8702..2f651b3a7 100644 --- a/SL/Auth/LDAP.pm +++ b/SL/Auth/LDAP.pm @@ -32,27 +32,32 @@ sub _connect { return $self->{ldap} if $self->{ldap}; - my $port = $cfg->{port} || 389; - $self->{ldap} = Net::LDAP->new($cfg->{host}, 'port' => $port); + my $port = $cfg->{port} || 389; + my $ldap = Net::LDAP->new($cfg->{host}, port => $port, timeout => $cfg->{timeout} || 10); - if (!$self->{ldap}) { - $main::form->error($main::locale->text('The LDAP server "#1:#2" is unreachable. Please check config/kivitendo.conf.', $cfg->{host}, $port)); + if (!$ldap) { + $::lxdebug->warn($main::locale->text('The LDAP server "#1:#2" is unreachable. Please check config/kivitendo.conf.', $cfg->{host}, $port)); + return undef; } if ($cfg->{tls}) { - my $mesg = $self->{ldap}->start_tls('verify' => 'none'); + my $mesg = $ldap->start_tls(verify => $cfg->{verify} // 'require'); if ($mesg->is_error()) { - $main::form->error($main::locale->text('The connection to the LDAP server cannot be encrypted (SSL/TLS startup failure). Please check config/kivitendo.conf.')); + $::lxdebug->warn($main::locale->text('The connection to the LDAP server cannot be encrypted (SSL/TLS startup failure). Please check config/kivitendo.conf.')); + return undef; } } if ($cfg->{bind_dn}) { - my $mesg = $self->{ldap}->bind($cfg->{bind_dn}, 'password' => $cfg->{bind_password}); + my $mesg = $ldap->bind($cfg->{bind_dn}, 'password' => $cfg->{bind_password}); if ($mesg->is_error()) { - $main::form->error($main::locale->text('Binding to the LDAP server as "#1" failed. Please check config/kivitendo.conf.', $cfg->{bind_dn})); + $::lxdebug->warn($main::locale->text('Binding to the LDAP server as "#1" failed. Please check config/kivitendo.conf.', $cfg->{bind_dn})); + return undef; } } + $self->{ldap} = $ldap; + return $self->{ldap}; } diff --git a/config/kivitendo.conf.default b/config/kivitendo.conf.default index 0d88cfc28..cf2998495 100644 --- a/config/kivitendo.conf.default +++ b/config/kivitendo.conf.default @@ -4,9 +4,16 @@ # interface. admin_password = admin123 -# Which module to use for authentication. Valid values are 'DB' and -# 'LDAP'. If 'LDAP' is used then users cannot change their password -# via kivitendo. +# Which modules to use for authentication. Valid values are 'DB' and +# 'LDAP'. You can use multiple modules separated by spaces. +# +# Multiple LDAP modules with different configurations can be used by +# postfixing 'LDAP' with the name of the configuration section to use: +# 'LDAP:ldap_fallback' would use the data from +# '[authentication/ldap_fallback]'. The name defaults to 'ldap' if it +# isn't given. +# +# Note that the LDAP module doesn't support changing the password. module = DB # The cookie name can be changed if desired. @@ -43,6 +50,8 @@ password = # specified. # # tls: Activate encryption via TLS +# verify: If 'tls' is used, how to verify the server's certificate. +# Can be one of 'require' or 'none'. # attribute: Name of the LDAP attribute containing the user's login name # base_dn: Base DN the LDAP searches start from # filter: An optional LDAP filter specification. The string '<%login%>' @@ -51,6 +60,12 @@ password = # If searching the LDAP tree requires user credentials # (e.g. ActiveDirectory) then these two parameters specify # the user name and password to use. +# timeout: Timeout when connecting to the server in seconds. +# +# You can specify a fallback LDAP server to use in case the main one +# isn't reachable by duplicating this whole section as +# "[authentication/ldap_fallback]". +# host = localhost port = 389 tls = 0 @@ -59,6 +74,8 @@ base_dn = filter = bind_dn = bind_password = +timeout = 10 +verify = require [system] # Set language for login and admin forms. Currently "de" (German) diff --git a/locale/de/all b/locale/de/all index 27e76fbda..d1c69dd12 100755 --- a/locale/de/all +++ b/locale/de/all @@ -1907,6 +1907,7 @@ $self->{texts} = { 'Missing Method!' => 'Fehlender Voranmeldungszeitraum', 'Missing Tax Authoritys Preferences' => 'Fehlende Angaben zum Finanzamt!', 'Missing amount' => 'Fehlbetrag', + 'Missing configuration section "authentication/#1" in "config/kivitendo.conf".' => 'Fehlender Konfigurationsabschnitt "authentication/#1" in "config/kivitendo.conf".', 'Missing parameter #1 in call to sub #2.' => 'Fehlender Parameter \'#1\' in Funktionsaufruf \'#2\'.', 'Missing parameter (at least one of #1) in call to sub #2.' => 'Fehlernder Parameter (mindestens einer aus \'#1\') in Funktionsaufruf \'#2\'.', 'Missing parameter for WebDAV file copy' => 'Fehlender Parameter für WebDAV Datei kopieren', @@ -2018,7 +2019,6 @@ $self->{texts} = { 'No groups have been created yet.' => 'Es wurden noch keine Gruppen angelegt.', 'No internal phone extensions have been configured yet.' => 'Es wurden noch keine internen Durchwahlen konfiguriert.', 'No invoices have been selected.' => 'Es wurden keine Rechnungen ausgewählt.', - 'No or an unknown authenticantion module specified in "config/kivitendo.conf".' => 'Es wurde kein oder ein unbekanntes Authentifizierungsmodul in "config/kivitendo.conf" angegeben.', 'No part was selected.' => 'Es wurde kein Artikel ausgewählt', 'No payment term has been created yet.' => 'Es wurden noch keine Zahlungsbedingungen angelegt.', 'No picture has been uploaded' => 'Es wurde kein Bild hochgeladen', @@ -3711,6 +3711,7 @@ $self->{texts} = { 'Units that have already been used (e.g. for parts and services or in invoices or warehouse transactions) cannot be changed.' => 'Einheiten, die bereits in Benutzung sind (z.B. bei einer Warendefinition, einer Rechnung oder bei einer Lagerbuchung) können nachträglich nicht mehr verändert werden.', 'Unknown Category' => 'Unbekannte Kategorie', 'Unknown Link' => 'Unbekannte Verknüpfung', + 'Unknown authenticantion module #1 specified in "config/kivitendo.conf".' => 'Unbekanntes Authentifizierungsmodul #1 angegeben in "config/kivitendo.conf".', 'Unknown control fields: #1' => 'Unbekannte Kontrollfelder: #1', 'Unknown dependency \'%s\'.' => 'Unbekannte Abhängigkeit \'%s\'.', 'Unknown module: #1' => 'Unbekanntes Modul #1', -- 2.20.1