From f30f0cce6468f895c7d59f3aab6e2a5731fc9705 Mon Sep 17 00:00:00 2001 From: Moritz Bunkus Date: Tue, 10 Oct 2017 11:19:48 +0200 Subject: [PATCH] =?utf8?q?Datenbankupgrades:=20Unterst=C3=BCtzung=20f?= =?utf8?q?=C3=BCr=20einzelne=20Updates=20mit=20Superuser-Rechten?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Setzt man den neuen Tag `@superuser_privileges` auf 1, so schaut kivitendo vor dem Einspielen aller Upgrades nach, ob der konfigurierte Datenbankuser Superuser-Rechte hat. Falls nicht, wird die Benutzer*in nach entsprechenden Logindaten gefragt. Nur die Upgradescripte, die tatsächlich Superuser-Rechte benötigen, werden mit diesem User ausgeführt, alle anderen mit den normalen Rechten, um Besitzer-Wildwuchs bei neu angelegten Objekten zu vermeiden. --- SL/DBUpgrade2.pm | 2 +- SL/User.pm | 78 +++++++++++++++++++++-- locale/de/all | 5 ++ templates/webpages/dbupgrade/warning.html | 26 +++++++- 4 files changed, 104 insertions(+), 7 deletions(-) diff --git a/SL/DBUpgrade2.pm b/SL/DBUpgrade2.pm index b47e84ebb..3918ad40d 100644 --- a/SL/DBUpgrade2.pm +++ b/SL/DBUpgrade2.pm @@ -100,7 +100,7 @@ sub parse_dbupdate_controls { delete @{$control}{qw(depth applied)}; - my @unknown_keys = grep { !m{^ (?: depends | description | file | ignore | locales | may_fail | priority | tag ) $}x } keys %{ $control }; + my @unknown_keys = grep { !m{^ (?: depends | description | file | ignore | locales | may_fail | priority | superuser_privileges | tag ) $}x } keys %{ $control }; if (@unknown_keys) { _control_error($form, $file_name, sprintf($locale->text("Unknown control fields: #1", join(' ', sort({ lc $a cmp lc $b } @unknown_keys))))); } diff --git a/SL/User.pm b/SL/User.pm index f324e3629..a271e77df 100644 --- a/SL/User.pm +++ b/SL/User.pm @@ -36,7 +36,7 @@ package User; use IO::File; -use Fcntl qw(:seek); +use List::MoreUtils qw(any); use SL::DB; #use SL::Auth; @@ -103,6 +103,42 @@ sub country_codes { return %cc; } +sub _handle_superuser_privileges { + my ($self, $form) = @_; + + if ($form->{database_superuser_username}) { + $::auth->set_session_value("database_superuser_username" => $form->{database_superuser_username}, "database_superuser_password" => $form->{database_superuser_password}); + } + + my %dbconnect_form = %{ $form }; + my ($su_user, $su_password) = map { $::auth->get_session_value("database_superuser_$_") } qw(username password); + + if ($su_user) { + $dbconnect_form{dbuser} = $su_user; + $dbconnect_form{dbpasswd} = $su_password; + } + + dbconnect_vars(\%dbconnect_form, $form->{dbname}); + + my %result = ( + username => $dbconnect_form{dbuser}, + password => $dbconnect_form{dbpasswd}, + ); + + $::auth->set_session_value("database_superuser_username" => $dbconnect_form{dbuser}, "database_superuser_password" => $dbconnect_form{dbpasswd}); + + my $dbh = SL::DBConnect->connect($dbconnect_form{dbconnect}, $dbconnect_form{dbuser}, $dbconnect_form{dbpasswd}, SL::DBConnect->get_options); + return (%result, error => $::locale->text('The credentials (username & password) for connecting database are wrong.')) if !$dbh; + + my ($is_superuser) = $dbh->selectrow_array(qq|SELECT usesuper FROM pg_user WHERE usename = ?|, undef, $dbconnect_form{dbuser}); + + $dbh->disconnect; + + return (%result, have_privileges => 1) if $is_superuser; + return (%result) if !$su_user; # no error message if credentials weren't set by the user + return (%result, error => $::locale->text('The database user \'#1\' does not have superuser privileges.', $dbconnect_form{dbuser})); +} + sub login { my ($self, $form) = @_; @@ -149,8 +185,22 @@ sub login { $form->{dbupdate} = "db" . $::auth->client->{dbname}; - if ($form->{"show_dbupdate_warning"}) { - print $form->parse_html_template("dbupgrade/warning", { unapplied_scripts => \@unapplied_scripts }); + my $show_update_warning = $form->{"show_dbupdate_warning"}; + my %superuser = (need_privileges => (any { $_->{superuser_privileges} } @unapplied_scripts)); + + if ($superuser{need_privileges}) { + %superuser = ( + %superuser, + $self->_handle_superuser_privileges($form), + ); + $show_update_warning = 1 if !$superuser{have_privileges}; + } + + if ($show_update_warning) { + print $form->parse_html_template("dbupgrade/warning", { + unapplied_scripts => \@unapplied_scripts, + superuser => \%superuser, + }); $::dispatcher->end_request; } @@ -442,6 +492,21 @@ sub dbupdate2 { $self->create_schema_info_table($form, $dbh); my @upgradescripts = $dbupdater->unapplied_upgrade_scripts($dbh); + my $need_superuser = (any { $_->{superuser_privileges} } @upgradescripts); + my $superuser_dbh; + + if ($need_superuser) { + my %dbconnect_form = ( + %{ $form }, + dbuser => $::auth->get_session_value("database_superuser_username"), + dbpasswd => $::auth->get_session_value("database_superuser_password"), + ); + + if ($dbconnect_form{dbuser} ne $form->{dbuser}) { + dbconnect_vars(\%dbconnect_form, $db); + $superuser_dbh = SL::DBConnect->connect($dbconnect_form{dbconnect}, $dbconnect_form{dbuser}, $dbconnect_form{dbpasswd}, SL::DBConnect->get_options) or $form->dberror; + } + } $::lxdebug->log_time("DB upgrades commencing"); @@ -449,15 +514,18 @@ sub dbupdate2 { # Apply upgrade. Control will only return to us if the upgrade has # been applied correctly and if the update has not requested user # interaction. - $main::lxdebug->message(LXDebug->DEBUG2(), "Applying Update $control->{file}"); + my $script_dbh = $control->{superuser_privileges} ? ($superuser_dbh // $dbh) : $dbh; + + $::lxdebug->message(LXDebug->DEBUG2(), "Applying Update $control->{file}" . ($control->{superuser_privileges} ? " with superuser privileges" : "")); print $form->parse_html_template("dbupgrade/upgrade_message2", $control) unless $silent; - $dbupdater->process_file($dbh, "sql/Pg-upgrade2/$control->{file}", $control); + $dbupdater->process_file($script_dbh, "sql/Pg-upgrade2/$control->{file}", $control); } $::lxdebug->log_time("DB upgrades finished"); $dbh->disconnect; + $superuser_dbh->disconnect if $superuser_dbh; } sub data { diff --git a/locale/de/all b/locale/de/all index ae2e56b9e..cb5305029 100755 --- a/locale/de/all +++ b/locale/de/all @@ -852,6 +852,7 @@ $self->{texts} = { 'Database login (#1)' => 'Datenbankanmeldung (#1)', 'Database name' => 'Datenbankname', 'Database settings' => 'Datenbankeinstellungen', + 'Database superuser privileges are required for the update.' => 'Datenbank-Super-Benutzer-Rechte werden für das Update benötigt.', 'Database template' => 'Datenbankvorlage', 'Database update error:' => 'Fehler beim Datenbankupgrade:', 'Database user and password' => 'Datenbankbenutzer und -passwort', @@ -1222,6 +1223,7 @@ $self->{texts} = { 'Error when saving: #1' => 'Fehler beim Speichern: #1', 'Error with default taxzone' => 'Ungültige Standardsteuerzone', 'Error!' => 'Fehler!', + 'Error: #1' => 'Fehler: #1', 'Error: A negative target quantity is not allowed.' => 'Fehler: Eine negative Zielmenge ist nicht erlaubt.', 'Error: A quantity and a target quantity could not be given both.' => 'Fehler: Menge und Zielmenge können nicht beide angegeben werden.', 'Error: A quantity or a target quantity must be given.' => 'Fehler: Menge oder Zielmenge muss angegeben werden.', @@ -2237,6 +2239,7 @@ $self->{texts} = { 'Please install the below listed modules or ask your system administrator to.' => 'Bitte installieren Sie die unten aufgeführten Module, oder bitten Sie Ihren Administrator darum.', 'Please log in to the administration panel.' => 'Bitte melden Sie sich im Administrationsbereich an.', 'Please modify filename' => 'Bitte Dateinamen editieren', + 'Please provide corresponding credentials.' => 'Bitte geben Sie entsprechende Logindaten an.', 'Please re-run the analysis for broken general ledger entries by clicking this button:' => 'Bitte wiederholen Sie die Analyse der Hauptbucheinträge, indem Sie auf diesen Button klicken:', 'Please read the file' => 'Bitte lesen Sie die Datei', 'Please select a customer from the list below.' => 'Bitte einen Endkunden aus der Liste auswählen', @@ -3108,6 +3111,7 @@ $self->{texts} = { 'The connection was established successfully.' => 'Die Verbindung zur Datenbank wurde erfolgreich hergestellt.', 'The contact person attribute "birthday" is converted from a free-form text field into a date field.' => 'Das Kontaktpersonenfeld "Geburtstag" wird von einem freien Textfeld auf ein Datumsfeld umgestellt.', 'The creation of the authentication database failed:' => 'Das Anlegen der Authentifizierungsdatenbank schlug fehl:', + 'The credentials (username & password) for connecting database are wrong.' => 'Die Daten (Benutzername & Passwort) für das Login zur Datenbank sind falsch.', 'The custom variable has been created.' => 'Die benutzerdefinierte Variable wurde erfasst.', 'The custom variable has been deleted.' => 'Die benutzerdefinierte Variable wurde gelöscht.', 'The custom variable has been saved.' => 'Die benutzerdefinierte Variable wurde gespeichert.', @@ -3118,6 +3122,7 @@ $self->{texts} = { 'The database name is missing.' => 'Der Datenbankname fehlt.', 'The database port is missing.' => 'Der Datenbankport fehlt.', 'The database update/creation did not succeed. The file #1 contained the following error:' => 'Die Datenbankaktualisierung/erstellung schlug fehl. Die Datei #1 enthielt den folgenden Fehler:', + 'The database user \'#1\' does not have superuser privileges.' => 'Der Datenbankbenutzer »#1« hat keine Super-Benutzer-Rechte.', 'The database user is missing.' => 'Der Datenbankbenutzer fehlt.', 'The dataset #1 has been created.' => 'Die Datenbank #1 wurde angelegt.', 'The dataset #1 has been deleted.' => 'Die Datenbank #1 wurde gelöscht.', diff --git a/templates/webpages/dbupgrade/warning.html b/templates/webpages/dbupgrade/warning.html index 297ab06aa..cd8a44a58 100644 --- a/templates/webpages/dbupgrade/warning.html +++ b/templates/webpages/dbupgrade/warning.html @@ -1,6 +1,6 @@ [%- USE T8 %] [%- USE HTML %] -[%- USE LxERP %] +[%- USE LxERP %][%- USE L -%]
@@ -8,6 +8,30 @@

[% LxERP.t8('kivitendo is about to update the database [ #1 ].', dbname) | html %]

+ + [% IF superuser.need_privileges && !superuser.have_privileges %] +

+ [% LxERP.t8("Database superuser privileges are required for the update.") %] + [% LxERP.t8("Please provide corresponding credentials.") %] +

+ + [% IF superuser.error %] +

[% LxERP.t8("Error: #1", superuser.error) %]

+ [% END %] + + + + + + + + + + + +
[% LxERP.t8("User name") %]:[% L.input_tag("database_superuser_username", superuser.username) %]
[% LxERP.t8("Password") %]:[% L.input_tag("database_superuser_password", superuser.password, type="password") %]
+ [% END %] +

[% 'You should create a backup of the database before proceeding because the backup might not be reversible.' | $T8 %]

-- 2.20.1