From: Moritz Bunkus Date: Mon, 29 Apr 2019 13:54:30 +0000 (+0200) Subject: Auth: Unterstützung für multiple Authentifizierungsbackends X-Git-Tag: release-3.5.6.1~510 X-Git-Url: http://wagnertech.de/git?a=commitdiff_plain;h=48c71a4b51f5359a7ab7e9bc51280baf9bc9b9b2;p=kivitendo-erp.git Auth: Unterstützung für multiple Authentifizierungsbackends Ü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. --- 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',