5 use Digest::MD5 qw(md5_hex);
 
   7 use Time::HiRes qw(gettimeofday);
 
   8 use List::MoreUtils qw(uniq);
 
  11 use SL::Auth::ColumnInformation;
 
  12 use SL::Auth::Constants qw(:all);
 
  15 use SL::Auth::Password;
 
  16 use SL::Auth::SessionValue;
 
  26 use constant SESSION_KEY_ROOT_AUTH => 'session_auth_status_root';
 
  27 use constant SESSION_KEY_USER_AUTH => 'session_auth_status_user';
 
  30   $main::lxdebug->enter_sub();
 
  37   $self->_read_auth_config();
 
  40   $main::lxdebug->leave_sub();
 
  46   my ($self, %params) = @_;
 
  48   $self->{SESSION}            = { };
 
  49   $self->{FULL_RIGHTS}        = { };
 
  50   $self->{RIGHTS}             = { };
 
  51   $self->{unique_counter}     = 0;
 
  52   $self->{column_information} = SL::Auth::ColumnInformation->new(auth => $self);
 
  53   $self->{authenticator}->reset;
 
  57   my ($self, $login, %params) = @_;
 
  58   my $may_fail = delete $params{may_fail};
 
  60   my %user = $self->read_user(login => $login);
 
  61   my $dbh  = SL::DBConnect->connect(
 
  66       pg_enable_utf8 => $::locale->is_utf8,
 
  71   if (!$may_fail && !$dbh) {
 
  72     $::form->error($::locale->text('The connection to the authentication database failed:') . "\n" . $DBI::errstr);
 
  75   if ($user{dboptions} && $dbh) {
 
  76     $dbh->do($user{dboptions}) or $::form->dberror($user{dboptions});
 
  85   $self->{dbh}->disconnect() if ($self->{dbh});
 
  88 # form isn't loaded yet, so auth needs it's own error.
 
  90   $::lxdebug->show_backtrace();
 
  92   my ($self, @msg) = @_;
 
  93   if ($ENV{HTTP_USER_AGENT}) {
 
  94     print Form->create_http_response(content_type => 'text/html');
 
  95     print "<pre>", join ('<br>', @msg), "</pre>";
 
  97     print STDERR "Error: @msg\n";
 
 102 sub _read_auth_config {
 
 103   $main::lxdebug->enter_sub();
 
 107   map { $self->{$_} = $::lx_office_conf{authentication}->{$_} } keys %{ $::lx_office_conf{authentication} };
 
 109   # Prevent password leakage to log files when dumping Auth instances.
 
 110   $self->{admin_password} = sub { $::lx_office_conf{authentication}->{admin_password} };
 
 112   $self->{DB_config}   = $::lx_office_conf{'authentication/database'};
 
 113   $self->{LDAP_config} = $::lx_office_conf{'authentication/ldap'};
 
 115   if ($self->{module} eq 'DB') {
 
 116     $self->{authenticator} = SL::Auth::DB->new($self);
 
 118   } elsif ($self->{module} eq 'LDAP') {
 
 119     $self->{authenticator} = SL::Auth::LDAP->new($self);
 
 122   if (!$self->{authenticator}) {
 
 123     my $locale = Locale->new('en');
 
 124     $self->mini_error($locale->text('No or an unknown authenticantion module specified in "config/kivitendo.conf".'));
 
 127   my $cfg = $self->{DB_config};
 
 130     my $locale = Locale->new('en');
 
 131     $self->mini_error($locale->text('config/kivitendo.conf: Key "DB_config" is missing.'));
 
 134   if (!$cfg->{host} || !$cfg->{db} || !$cfg->{user}) {
 
 135     my $locale = Locale->new('en');
 
 136     $self->mini_error($locale->text('config/kivitendo.conf: Missing parameters in "authentication/database". Required parameters are "host", "db" and "user".'));
 
 139   $self->{authenticator}->verify_config();
 
 141   $self->{session_timeout} *= 1;
 
 142   $self->{session_timeout}  = 8 * 60 if (!$self->{session_timeout});
 
 144   $main::lxdebug->leave_sub();
 
 147 sub authenticate_root {
 
 148   $main::lxdebug->enter_sub();
 
 150   my ($self, $password) = @_;
 
 152   my $session_root_auth = $self->get_session_value(SESSION_KEY_ROOT_AUTH());
 
 153   if (defined $session_root_auth && $session_root_auth == OK) {
 
 154     $::lxdebug->leave_sub;
 
 158   if (!defined $password) {
 
 159     $::lxdebug->leave_sub;
 
 163   $password             = SL::Auth::Password->hash(login => 'root', password => $password);
 
 164   my $admin_password    = SL::Auth::Password->hash_if_unhashed(login => 'root', password => $self->{admin_password}->());
 
 166   my $result = $password eq $admin_password ? OK : ERR_PASSWORD;
 
 167   $self->set_session_value(SESSION_KEY_ROOT_AUTH() => $result);
 
 169   $::lxdebug->leave_sub;
 
 174   $main::lxdebug->enter_sub();
 
 176   my ($self, $login, $password) = @_;
 
 178   my $session_auth = $self->get_session_value(SESSION_KEY_USER_AUTH());
 
 179   if (defined $session_auth && $session_auth == OK) {
 
 180     $::lxdebug->leave_sub;
 
 184   if (!defined $password) {
 
 185     $::lxdebug->leave_sub;
 
 189   my $result = $login ? $self->{authenticator}->authenticate($login, $password) : ERR_USER;
 
 190   $self->set_session_value(SESSION_KEY_USER_AUTH() => $result, login => $login);
 
 192   $::lxdebug->leave_sub;
 
 196 sub punish_wrong_login {
 
 197   my $failed_login_penalty = ($::lx_office_conf{authentication} || {})->{failed_login_penalty};
 
 198   sleep $failed_login_penalty if $failed_login_penalty;
 
 201 sub get_stored_password {
 
 202   my ($self, $login) = @_;
 
 204   my $dbh            = $self->dbconnect;
 
 206   return undef unless $dbh;
 
 208   my $query             = qq|SELECT password FROM auth."user" WHERE login = ?|;
 
 209   my ($stored_password) = $dbh->selectrow_array($query, undef, $login);
 
 211   return $stored_password;
 
 215   $main::lxdebug->enter_sub(2);
 
 218   my $may_fail = shift;
 
 221     $main::lxdebug->leave_sub(2);
 
 225   my $cfg = $self->{DB_config};
 
 226   my $dsn = 'dbi:Pg:dbname=' . $cfg->{db} . ';host=' . $cfg->{host};
 
 229     $dsn .= ';port=' . $cfg->{port};
 
 232   $main::lxdebug->message(LXDebug->DEBUG1, "Auth::dbconnect DSN: $dsn");
 
 234   $self->{dbh} = SL::DBConnect->connect($dsn, $cfg->{user}, $cfg->{password}, { pg_enable_utf8 => $::locale->is_utf8, AutoCommit => 1 });
 
 236   if (!$may_fail && !$self->{dbh}) {
 
 237     $main::form->error($main::locale->text('The connection to the authentication database failed:') . "\n" . $DBI::errstr);
 
 240   $main::lxdebug->leave_sub(2);
 
 246   $main::lxdebug->enter_sub();
 
 251     $self->{dbh}->disconnect();
 
 255   $main::lxdebug->leave_sub();
 
 259   $main::lxdebug->enter_sub();
 
 261   my ($self, $dbh)    = @_;
 
 263   $dbh   ||= $self->dbconnect();
 
 264   my $query   = qq|SELECT COUNT(*) FROM pg_tables WHERE (schemaname = 'auth') AND (tablename = 'user')|;
 
 266   my ($count) = $dbh->selectrow_array($query);
 
 268   $main::lxdebug->leave_sub();
 
 274   $main::lxdebug->enter_sub();
 
 278   my $dbh  = $self->dbconnect(1);
 
 280   $main::lxdebug->leave_sub();
 
 285 sub create_database {
 
 286   $main::lxdebug->enter_sub();
 
 291   my $cfg    = $self->{DB_config};
 
 293   if (!$params{superuser}) {
 
 294     $params{superuser}          = $cfg->{user};
 
 295     $params{superuser_password} = $cfg->{password};
 
 298   $params{template} ||= 'template0';
 
 299   $params{template}   =~ s|[^a-zA-Z0-9_\-]||g;
 
 301   my $dsn = 'dbi:Pg:dbname=template1;host=' . $cfg->{host};
 
 304     $dsn .= ';port=' . $cfg->{port};
 
 307   $main::lxdebug->message(LXDebug->DEBUG1(), "Auth::create_database DSN: $dsn");
 
 309   my $charset    = $::lx_office_conf{system}->{dbcharset};
 
 310   $charset     ||= Common::DEFAULT_CHARSET;
 
 311   my $encoding   = $Common::charset_to_db_encoding{$charset};
 
 312   $encoding    ||= 'UNICODE';
 
 314   my $dbh        = SL::DBConnect->connect($dsn, $params{superuser}, $params{superuser_password}, { pg_enable_utf8 => scalar($charset =~ m/^utf-?8$/i) });
 
 317     $main::form->error($main::locale->text('The connection to the template database failed:') . "\n" . $DBI::errstr);
 
 320   my $query = qq|CREATE DATABASE "$cfg->{db}" OWNER "$cfg->{user}" TEMPLATE "$params{template}" ENCODING '$encoding'|;
 
 322   $main::lxdebug->message(LXDebug->DEBUG1(), "Auth::create_database query: $query");
 
 327     my $error = $dbh->errstr();
 
 329     $query                 = qq|SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = 'template0'|;
 
 330     my ($cluster_encoding) = $dbh->selectrow_array($query);
 
 332     if ($cluster_encoding && ($cluster_encoding =~ m/^(?:UTF-?8|UNICODE)$/i) && ($encoding !~ m/^(?:UTF-?8|UNICODE)$/i)) {
 
 333       $error = $main::locale->text('Your PostgreSQL installationen uses UTF-8 as its encoding. Therefore you have to configure Lx-Office to use UTF-8 as well.');
 
 338     $main::form->error($main::locale->text('The creation of the authentication database failed:') . "\n" . $error);
 
 343   $main::lxdebug->leave_sub();
 
 347   $main::lxdebug->enter_sub();
 
 350   my $dbh  = $self->dbconnect();
 
 352   my $charset    = $::lx_office_conf{system}->{dbcharset};
 
 353   $charset     ||= Common::DEFAULT_CHARSET;
 
 356   SL::DBUpgrade2->new(form => $::form)->process_query($dbh, 'sql/auth_db.sql', undef, $charset);
 
 358   $main::lxdebug->leave_sub();
 
 362   $main::lxdebug->enter_sub();
 
 368   my $form   = $main::form;
 
 370   my $dbh    = $self->dbconnect();
 
 372   my ($sth, $query, $user_id);
 
 376   $query     = qq|SELECT id FROM auth."user" WHERE login = ?|;
 
 377   ($user_id) = selectrow_query($form, $dbh, $query, $login);
 
 380     $query     = qq|SELECT nextval('auth.user_id_seq')|;
 
 381     ($user_id) = selectrow_query($form, $dbh, $query);
 
 383     $query     = qq|INSERT INTO auth."user" (id, login) VALUES (?, ?)|;
 
 384     do_query($form, $dbh, $query, $user_id, $login);
 
 387   $query = qq|DELETE FROM auth.user_config WHERE (user_id = ?)|;
 
 388   do_query($form, $dbh, $query, $user_id);
 
 390   $query = qq|INSERT INTO auth.user_config (user_id, cfg_key, cfg_value) VALUES (?, ?, ?)|;
 
 391   $sth   = prepare_query($form, $dbh, $query);
 
 393   while (my ($cfg_key, $cfg_value) = each %params) {
 
 394     next if ($cfg_key eq 'password');
 
 396     do_statement($form, $sth, $query, $user_id, $cfg_key, $cfg_value);
 
 401   $main::lxdebug->leave_sub();
 
 404 sub can_change_password {
 
 407   return $self->{authenticator}->can_change_password();
 
 410 sub change_password {
 
 411   $main::lxdebug->enter_sub();
 
 413   my ($self, $login, $new_password) = @_;
 
 415   my $result = $self->{authenticator}->change_password($login, $new_password);
 
 417   $main::lxdebug->leave_sub();
 
 423   $main::lxdebug->enter_sub();
 
 427   my $dbh   = $self->dbconnect();
 
 428   my $query = qq|SELECT u.id, u.login, cfg.cfg_key, cfg.cfg_value, s.mtime AS last_action
 
 430                  FROM auth."user" AS  u
 
 432                  LEFT JOIN auth.user_config AS cfg
 
 433                    ON (cfg.user_id = u.id)
 
 435                  LEFT JOIN auth.session_content AS sc_login
 
 436                    ON (sc_login.sess_key = 'login' AND sc_login.sess_value = ('--- ' \|\| u.login \|\| '\n'))
 
 438                  LEFT JOIN auth.session AS s
 
 439                    ON (s.id = sc_login.session_id)
 
 441   my $sth   = prepare_execute_query($main::form, $dbh, $query);
 
 445   while (my $ref = $sth->fetchrow_hashref()) {
 
 447     $users{$ref->{login}}                    ||= {
 
 448                                                 'login' => $ref->{login},
 
 450                                                 'last_action' => $ref->{last_action},
 
 452     $users{$ref->{login}}->{$ref->{cfg_key}}   = $ref->{cfg_value} if (($ref->{cfg_key} ne 'login') && ($ref->{cfg_key} ne 'id'));
 
 457   $main::lxdebug->leave_sub();
 
 463   $main::lxdebug->enter_sub();
 
 465   my ($self, %params) = @_;
 
 467   my $dbh   = $self->dbconnect();
 
 469   my (@where, @values);
 
 470   if ($params{login}) {
 
 471     push @where,  'u.login = ?';
 
 472     push @values, $params{login};
 
 475     push @where,  'u.id = ?';
 
 476     push @values, $params{id};
 
 478   my $where = join ' AND ', '1 = 1', @where;
 
 479   my $query = qq|SELECT u.id, u.login, cfg.cfg_key, cfg.cfg_value
 
 480                  FROM auth.user_config cfg
 
 481                  LEFT JOIN auth."user" u ON (cfg.user_id = u.id)
 
 483   my $sth   = prepare_execute_query($main::form, $dbh, $query, @values);
 
 487   while (my $ref = $sth->fetchrow_hashref()) {
 
 488     $user_data{$ref->{cfg_key}} = $ref->{cfg_value};
 
 489     @user_data{qw(id login)}    = @{$ref}{qw(id login)};
 
 492   # The XUL/XML backed menu has been removed.
 
 493   $user_data{menustyle} = 'v3' if lc($user_data{menustyle} || '') eq 'xml';
 
 495   # Set default language if selected language does not exist (anymore).
 
 496   $user_data{countrycode} = $::lx_office_conf{system}->{language} unless $user_data{countrycode} && -d "locale/$user_data{countrycode}";
 
 500   $main::lxdebug->leave_sub();
 
 506   $main::lxdebug->enter_sub();
 
 511   my $dbh   = $self->dbconnect();
 
 512   my ($id)  = selectrow_query($main::form, $dbh, qq|SELECT id FROM auth."user" WHERE login = ?|, $login);
 
 514   $main::lxdebug->leave_sub();
 
 520   $::lxdebug->enter_sub;
 
 525   my $dbh   = $self->dbconnect;
 
 526   my $id    = $self->get_user_id($login);
 
 529   $dbh->rollback and return $::lxdebug->leave_sub if (!$id);
 
 531   my $u_dbh = $self->get_user_dbh($login, may_fail => 1);
 
 532   $user_db_exists = $self->check_tables($u_dbh) if $u_dbh;
 
 534   $u_dbh->begin_work if $u_dbh && $user_db_exists;
 
 538   do_query($::form, $dbh, qq|DELETE FROM auth.user_group WHERE user_id = ?|, $id);
 
 539   do_query($::form, $dbh, qq|DELETE FROM auth.user_config WHERE user_id = ?|, $id);
 
 540   do_query($::form, $dbh, qq|DELETE FROM auth.user WHERE id = ?|, $id);
 
 541   do_query($::form, $u_dbh, qq|UPDATE employee SET deleted = 't' WHERE login = ?|, $login) if $u_dbh && $user_db_exists;
 
 544   $u_dbh->commit if $u_dbh && $user_db_exists;
 
 546   $::lxdebug->leave_sub;
 
 549 # --------------------------------------
 
 553 sub restore_session {
 
 554   $main::lxdebug->enter_sub();
 
 558   $session_id        =  $::request->{cgi}->cookie($self->get_session_cookie_name());
 
 559   $session_id        =~ s|[^0-9a-f]||g if $session_id;
 
 561   $self->{SESSION}   = { };
 
 564     $main::lxdebug->leave_sub();
 
 568   my ($dbh, $query, $sth, $cookie, $ref, $form);
 
 572   # Don't fail if the auth DB doesn't yet.
 
 573   if (!( $dbh = $self->dbconnect(1) )) {
 
 574     $::lxdebug->leave_sub;
 
 578   # Don't fail if the "auth" schema doesn't exist yet, e.g. if the
 
 579   # admin is creating the session tables at the moment.
 
 580   $query  = qq|SELECT *, (mtime < (now() - '$self->{session_timeout}m'::interval)) AS is_expired FROM auth.session WHERE id = ?|;
 
 582   if (!($sth = $dbh->prepare($query)) || !$sth->execute($session_id)) {
 
 583     $sth->finish if $sth;
 
 584     $::lxdebug->leave_sub;
 
 588   $cookie = $sth->fetchrow_hashref;
 
 591   if (!$cookie || $cookie->{is_expired} || ($cookie->{ip_address} ne $ENV{REMOTE_ADDR})) {
 
 592     $self->destroy_session();
 
 593     $main::lxdebug->leave_sub();
 
 594     return $cookie ? SESSION_EXPIRED : SESSION_NONE;
 
 597   if ($self->{column_information}->has('auto_restore')) {
 
 598     $self->_load_with_auto_restore_column($dbh, $session_id);
 
 600     $self->_load_without_auto_restore_column($dbh, $session_id);
 
 603   $main::lxdebug->leave_sub();
 
 608 sub _load_without_auto_restore_column {
 
 609   my ($self, $dbh, $session_id) = @_;
 
 612     SELECT sess_key, sess_value
 
 613     FROM auth.session_content
 
 614     WHERE (session_id = ?)
 
 616   my $sth = prepare_execute_query($::form, $dbh, $query, $session_id);
 
 618   while (my $ref = $sth->fetchrow_hashref) {
 
 619     my $value = SL::Auth::SessionValue->new(auth  => $self,
 
 620                                             key   => $ref->{sess_key},
 
 621                                             value => $ref->{sess_value},
 
 623     $self->{SESSION}->{ $ref->{sess_key} } = $value;
 
 625     next if defined $::form->{$ref->{sess_key}};
 
 627     my $data                    = $value->get;
 
 628     $::form->{$ref->{sess_key}} = $data if $value->{auto_restore} || !ref $data;
 
 632 sub _load_with_auto_restore_column {
 
 633   my ($self, $dbh, $session_id) = @_;
 
 635   my $auto_restore_keys = join ', ', map { "'${_}'" } qw(login password rpw);
 
 638     SELECT sess_key, sess_value, auto_restore
 
 639     FROM auth.session_content
 
 640     WHERE (session_id = ?)
 
 642            OR sess_key IN (${auto_restore_keys}))
 
 644   my $sth = prepare_execute_query($::form, $dbh, $query, $session_id);
 
 646   while (my $ref = $sth->fetchrow_hashref) {
 
 647     my $value = SL::Auth::SessionValue->new(auth         => $self,
 
 648                                             key          => $ref->{sess_key},
 
 649                                             value        => $ref->{sess_value},
 
 650                                             auto_restore => $ref->{auto_restore},
 
 652     $self->{SESSION}->{ $ref->{sess_key} } = $value;
 
 654     next if defined $::form->{$ref->{sess_key}};
 
 656     my $data                    = $value->get;
 
 657     $::form->{$ref->{sess_key}} = $data if $value->{auto_restore} || !ref $data;
 
 664     FROM auth.session_content
 
 665     WHERE (session_id = ?)
 
 666       AND NOT COALESCE(auto_restore, FALSE)
 
 667       AND (sess_key NOT IN (${auto_restore_keys}))
 
 669   $sth = prepare_execute_query($::form, $dbh, $query, $session_id);
 
 671   while (my $ref = $sth->fetchrow_hashref) {
 
 672     my $value = SL::Auth::SessionValue->new(auth => $self,
 
 673                                             key  => $ref->{sess_key});
 
 674     $self->{SESSION}->{ $ref->{sess_key} } = $value;
 
 678 sub destroy_session {
 
 679   $main::lxdebug->enter_sub();
 
 684     my $dbh = $self->dbconnect();
 
 688     do_query($main::form, $dbh, qq|DELETE FROM auth.session_content WHERE session_id = ?|, $session_id);
 
 689     do_query($main::form, $dbh, qq|DELETE FROM auth.session WHERE id = ?|, $session_id);
 
 693     SL::SessionFile->destroy_session($session_id);
 
 696     $self->{SESSION} = { };
 
 699   $main::lxdebug->leave_sub();
 
 702 sub expire_sessions {
 
 703   $main::lxdebug->enter_sub();
 
 707   $main::lxdebug->leave_sub and return if !$self->session_tables_present;
 
 709   my $dbh   = $self->dbconnect();
 
 711   my $query = qq|SELECT id
 
 713                  WHERE (mtime < (now() - '$self->{session_timeout}m'::interval))|;
 
 715   my @ids   = selectall_array_query($::form, $dbh, $query);
 
 720     SL::SessionFile->destroy_session($_) for @ids;
 
 722     $query = qq|DELETE FROM auth.session_content
 
 723                 WHERE session_id IN (| . join(', ', ('?') x scalar(@ids)) . qq|)|;
 
 724     do_query($main::form, $dbh, $query, @ids);
 
 726     $query = qq|DELETE FROM auth.session
 
 727                 WHERE id IN (| . join(', ', ('?') x scalar(@ids)) . qq|)|;
 
 728     do_query($main::form, $dbh, $query, @ids);
 
 733   $main::lxdebug->leave_sub();
 
 736 sub _create_session_id {
 
 737   $main::lxdebug->enter_sub();
 
 740   map { push @data, int(rand() * 255); } (1..32);
 
 742   my $id = md5_hex(pack 'C*', @data);
 
 744   $main::lxdebug->leave_sub();
 
 749 sub create_or_refresh_session {
 
 750   $session_id ||= shift->_create_session_id;
 
 754   $::lxdebug->enter_sub;
 
 756   my $provided_dbh = shift;
 
 758   my $dbh          = $provided_dbh || $self->dbconnect(1);
 
 760   $::lxdebug->leave_sub && return unless $dbh && $session_id;
 
 762   $dbh->begin_work unless $provided_dbh;
 
 764   # If this fails then the "auth" schema might not exist yet, e.g. if
 
 765   # the admin is just trying to create the auth database.
 
 766   if (!$dbh->do(qq|LOCK auth.session_content|)) {
 
 767     $dbh->rollback unless $provided_dbh;
 
 768     $::lxdebug->leave_sub;
 
 772   my @unfetched_keys = map     { $_->{key}        }
 
 773                        grep    { ! $_->{fetched}  }
 
 774                        values %{ $self->{SESSION} };
 
 775   # $::lxdebug->dump(0, "unfetched_keys", [ sort @unfetched_keys ]);
 
 776   # $::lxdebug->dump(0, "all keys", [ sort map { $_->{key} } values %{ $self->{SESSION} } ]);
 
 777   my $query          = qq|DELETE FROM auth.session_content WHERE (session_id = ?)|;
 
 778   $query            .= qq| AND (sess_key NOT IN (| . join(', ', ('?') x scalar @unfetched_keys) . qq|))| if @unfetched_keys;
 
 780   do_query($::form, $dbh, $query, $session_id, @unfetched_keys);
 
 782   my ($id) = selectrow_query($::form, $dbh, qq|SELECT id FROM auth.session WHERE id = ?|, $session_id);
 
 785     do_query($::form, $dbh, qq|UPDATE auth.session SET mtime = now() WHERE id = ?|, $session_id);
 
 787     do_query($::form, $dbh, qq|INSERT INTO auth.session (id, ip_address, mtime) VALUES (?, ?, now())|, $session_id, $ENV{REMOTE_ADDR});
 
 790   my @values_to_save = grep    { $_->{fetched} }
 
 791                        values %{ $self->{SESSION} };
 
 792   if (@values_to_save) {
 
 793     my ($columns, $placeholders) = ('', '');
 
 794     my $auto_restore             = $self->{column_information}->has('auto_restore');
 
 797       $columns      .= ', auto_restore';
 
 798       $placeholders .= ', ?';
 
 801     $query  = qq|INSERT INTO auth.session_content (session_id, sess_key, sess_value ${columns}) VALUES (?, ?, ? ${placeholders})|;
 
 802     my $sth = prepare_query($::form, $dbh, $query);
 
 804     foreach my $value (@values_to_save) {
 
 805       my @values = ($value->{key}, $value->get_dumped);
 
 806       push @values, $value->{auto_restore} if $auto_restore;
 
 808       do_statement($::form, $sth, $query, $session_id, @values);
 
 814   $dbh->commit() unless $provided_dbh;
 
 815   $::lxdebug->leave_sub;
 
 818 sub set_session_value {
 
 819   $main::lxdebug->enter_sub();
 
 824   $self->{SESSION} ||= { };
 
 827     my $key = shift @params;
 
 829     if (ref $key eq 'HASH') {
 
 830       $self->{SESSION}->{ $key->{key} } = SL::Auth::SessionValue->new(key          => $key->{key},
 
 831                                                                       value        => $key->{value},
 
 832                                                                       auto_restore => $key->{auto_restore});
 
 835       my $value = shift @params;
 
 836       $self->{SESSION}->{ $key } = SL::Auth::SessionValue->new(key   => $key,
 
 841   $main::lxdebug->leave_sub();
 
 846 sub delete_session_value {
 
 847   $main::lxdebug->enter_sub();
 
 851   $self->{SESSION} ||= { };
 
 852   delete @{ $self->{SESSION} }{ @_ };
 
 854   $main::lxdebug->leave_sub();
 
 859 sub get_session_value {
 
 860   $main::lxdebug->enter_sub();
 
 863   my $data = $self->{SESSION} && $self->{SESSION}->{ $_[0] } ? $self->{SESSION}->{ $_[0] }->get : undef;
 
 865   $main::lxdebug->leave_sub();
 
 870 sub create_unique_sesion_value {
 
 871   my ($self, $value, %params) = @_;
 
 873   $self->{SESSION} ||= { };
 
 875   my @now                   = gettimeofday();
 
 876   my $key                   = "$$-" . ($now[0] * 1000000 + $now[1]) . "-";
 
 877   $self->{unique_counter} ||= 0;
 
 881     $self->{unique_counter}++;
 
 882     $hashed_key = md5_hex($key . $self->{unique_counter});
 
 883   } while (exists $self->{SESSION}->{$hashed_key});
 
 885   $self->set_session_value($hashed_key => $value);
 
 890 sub save_form_in_session {
 
 891   my ($self, %params) = @_;
 
 893   my $form        = delete($params{form}) || $::form;
 
 894   my $non_scalars = delete $params{non_scalars};
 
 897   my %skip_keys   = map { ( $_ => 1 ) } (qw(login password stylesheet version titlebar), @{ $params{skip_keys} || [] });
 
 899   foreach my $key (grep { !$skip_keys{$_} } keys %{ $form }) {
 
 900     $data->{$key} = $form->{$key} if !ref($form->{$key}) || $non_scalars;
 
 903   return $self->create_unique_sesion_value($data, %params);
 
 906 sub restore_form_from_session {
 
 907   my ($self, $key, %params) = @_;
 
 909   my $data = $self->get_session_value($key);
 
 910   return $self unless $data;
 
 912   my $form    = delete($params{form}) || $::form;
 
 913   my $clobber = exists $params{clobber} ? $params{clobber} : 1;
 
 915   map { $form->{$_} = $data->{$_} if $clobber || !exists $form->{$_} } keys %{ $data };
 
 920 sub set_cookie_environment_variable {
 
 922   $ENV{HTTP_COOKIE} = $self->get_session_cookie_name() . "=${session_id}";
 
 925 sub get_session_cookie_name {
 
 928   return $self->{cookie_name} || 'lx_office_erp_session_id';
 
 935 sub session_tables_present {
 
 936   $main::lxdebug->enter_sub();
 
 940   # Only re-check for the presence of auth tables if either the check
 
 941   # hasn't been done before of if they weren't present.
 
 942   if ($self->{session_tables_present}) {
 
 943     $main::lxdebug->leave_sub();
 
 944     return $self->{session_tables_present};
 
 947   my $dbh  = $self->dbconnect(1);
 
 950     $main::lxdebug->leave_sub();
 
 957        WHERE (schemaname = 'auth')
 
 958          AND (tablename IN ('session', 'session_content'))|;
 
 960   my ($count) = selectrow_query($main::form, $dbh, $query);
 
 962   $self->{session_tables_present} = 2 == $count;
 
 964   $main::lxdebug->leave_sub();
 
 966   return $self->{session_tables_present};
 
 969 # --------------------------------------
 
 971 sub all_rights_full {
 
 972   my $locale = $main::locale;
 
 975     ["--crm",                          $locale->text("CRM optional software")],
 
 976     ["crm_search",                     $locale->text("CRM search")],
 
 977     ["crm_new",                        $locale->text("CRM create customers, vendors and contacts")],
 
 978     ["crm_service",                    $locale->text("CRM services")],
 
 979     ["crm_admin",                      $locale->text("CRM admin")],
 
 980     ["crm_adminuser",                  $locale->text("CRM user")],
 
 981     ["crm_adminstatus",                $locale->text("CRM status")],
 
 982     ["crm_email",                      $locale->text("CRM send email")],
 
 983     ["crm_termin",                     $locale->text("CRM termin")],
 
 984     ["crm_opportunity",                $locale->text("CRM opportunity")],
 
 985     ["crm_knowhow",                    $locale->text("CRM know how")],
 
 986     ["crm_follow",                     $locale->text("CRM follow up")],
 
 987     ["crm_notices",                    $locale->text("CRM notices")],
 
 988     ["crm_other",                      $locale->text("CRM other")],
 
 989     ["--master_data",                  $locale->text("Master Data")],
 
 990     ["customer_vendor_edit",           $locale->text("Create customers and vendors. Edit all vendors. Edit only customers where salesman equals employee (login)")],
 
 991     ["customer_vendor_all_edit",       $locale->text("Create customers and vendors. Edit all vendors. Edit all customers")],
 
 992     ["part_service_assembly_edit",     $locale->text("Create and edit parts, services, assemblies")],
 
 993     ["project_edit",                   $locale->text("Create and edit projects")],
 
 994     ["--ar",                           $locale->text("AR")],
 
 995     ["sales_quotation_edit",           $locale->text("Create and edit sales quotations")],
 
 996     ["sales_order_edit",               $locale->text("Create and edit sales orders")],
 
 997     ["sales_delivery_order_edit",      $locale->text("Create and edit sales delivery orders")],
 
 998     ["invoice_edit",                   $locale->text("Create and edit invoices and credit notes")],
 
 999     ["dunning_edit",                   $locale->text("Create and edit dunnings")],
 
1000     ["sales_all_edit",                 $locale->text("View/edit all employees sales documents")],
 
1001     ["edit_prices",                    $locale->text("Edit prices and discount (if not used, textfield is ONLY set readonly)")],
 
1002     ["--ap",                           $locale->text("AP")],
 
1003     ["request_quotation_edit",         $locale->text("Create and edit RFQs")],
 
1004     ["purchase_order_edit",            $locale->text("Create and edit purchase orders")],
 
1005     ["purchase_delivery_order_edit",   $locale->text("Create and edit purchase delivery orders")],
 
1006     ["vendor_invoice_edit",            $locale->text("Create and edit vendor invoices")],
 
1007     ["--warehouse_management",         $locale->text("Warehouse management")],
 
1008     ["warehouse_contents",             $locale->text("View warehouse content")],
 
1009     ["warehouse_management",           $locale->text("Warehouse management")],
 
1010     ["--general_ledger_cash",          $locale->text("General ledger and cash")],
 
1011     ["general_ledger",                 $locale->text("Transactions, AR transactions, AP transactions")],
 
1012     ["datev_export",                   $locale->text("DATEV Export")],
 
1013     ["cash",                           $locale->text("Receipt, payment, reconciliation")],
 
1014     ["--reports",                      $locale->text('Reports')],
 
1015     ["report",                         $locale->text('All reports')],
 
1016     ["advance_turnover_tax_return",    $locale->text('Advance turnover tax return')],
 
1017     ["--batch_printing",               $locale->text("Batch Printing")],
 
1018     ["batch_printing",                 $locale->text("Batch Printing")],
 
1019     ["--others",                       $locale->text("Others")],
 
1020     ["email_bcc",                      $locale->text("May set the BCC field when sending emails")],
 
1021     ["config",                         $locale->text("Change Lx-Office installation settings (all menu entries beneath 'System')")],
 
1022     ["admin",                          $locale->text("Administration (Used to access instance administration from user logins)")],
 
1029   return grep !/^--/, map { $_->[0] } all_rights_full();
 
1033   $main::lxdebug->enter_sub();
 
1037   my $form   = $main::form;
 
1039   my $dbh    = $self->dbconnect();
 
1041   my $query  = 'SELECT * FROM auth."group"';
 
1042   my $sth    = prepare_execute_query($form, $dbh, $query);
 
1046   while ($row = $sth->fetchrow_hashref()) {
 
1047     $groups->{$row->{id}} = $row;
 
1051   $query = 'SELECT * FROM auth.user_group WHERE group_id = ?';
 
1052   $sth   = prepare_query($form, $dbh, $query);
 
1054   foreach $group (values %{$groups}) {
 
1057     do_statement($form, $sth, $query, $group->{id});
 
1059     while ($row = $sth->fetchrow_hashref()) {
 
1060       push @members, $row->{user_id};
 
1062     $group->{members} = [ uniq @members ];
 
1066   $query = 'SELECT * FROM auth.group_rights WHERE group_id = ?';
 
1067   $sth   = prepare_query($form, $dbh, $query);
 
1069   foreach $group (values %{$groups}) {
 
1070     $group->{rights} = {};
 
1072     do_statement($form, $sth, $query, $group->{id});
 
1074     while ($row = $sth->fetchrow_hashref()) {
 
1075       $group->{rights}->{$row->{right}} |= $row->{granted};
 
1078     map { $group->{rights}->{$_} = 0 if (!defined $group->{rights}->{$_}); } all_rights();
 
1082   $main::lxdebug->leave_sub();
 
1088   $main::lxdebug->enter_sub();
 
1093   my $form  = $main::form;
 
1094   my $dbh   = $self->dbconnect();
 
1098   my ($query, $sth, $row, $rights);
 
1100   if (!$group->{id}) {
 
1101     ($group->{id}) = selectrow_query($form, $dbh, qq|SELECT nextval('auth.group_id_seq')|);
 
1103     $query = qq|INSERT INTO auth."group" (id, name, description) VALUES (?, '', '')|;
 
1104     do_query($form, $dbh, $query, $group->{id});
 
1107   do_query($form, $dbh, qq|UPDATE auth."group" SET name = ?, description = ? WHERE id = ?|, map { $group->{$_} } qw(name description id));
 
1109   do_query($form, $dbh, qq|DELETE FROM auth.user_group WHERE group_id = ?|, $group->{id});
 
1111   $query  = qq|INSERT INTO auth.user_group (user_id, group_id) VALUES (?, ?)|;
 
1112   $sth    = prepare_query($form, $dbh, $query);
 
1114   foreach my $user_id (uniq @{ $group->{members} }) {
 
1115     do_statement($form, $sth, $query, $user_id, $group->{id});
 
1119   do_query($form, $dbh, qq|DELETE FROM auth.group_rights WHERE group_id = ?|, $group->{id});
 
1121   $query = qq|INSERT INTO auth.group_rights (group_id, "right", granted) VALUES (?, ?, ?)|;
 
1122   $sth   = prepare_query($form, $dbh, $query);
 
1124   foreach my $right (keys %{ $group->{rights} }) {
 
1125     do_statement($form, $sth, $query, $group->{id}, $right, $group->{rights}->{$right} ? 't' : 'f');
 
1131   $main::lxdebug->leave_sub();
 
1135   $main::lxdebug->enter_sub();
 
1140   my $form = $main::form;
 
1142   my $dbh  = $self->dbconnect();
 
1145   do_query($form, $dbh, qq|DELETE FROM auth.user_group WHERE group_id = ?|, $id);
 
1146   do_query($form, $dbh, qq|DELETE FROM auth.group_rights WHERE group_id = ?|, $id);
 
1147   do_query($form, $dbh, qq|DELETE FROM auth."group" WHERE id = ?|, $id);
 
1151   $main::lxdebug->leave_sub();
 
1154 sub evaluate_rights_ary {
 
1155   $main::lxdebug->enter_sub(2);
 
1162   foreach my $el (@{$ary}) {
 
1163     if (ref $el eq "ARRAY") {
 
1164       if ($action eq '|') {
 
1165         $value |= evaluate_rights_ary($el);
 
1167         $value &= evaluate_rights_ary($el);
 
1170     } elsif (($el eq '&') || ($el eq '|')) {
 
1173     } elsif ($action eq '|') {
 
1182   $main::lxdebug->leave_sub(2);
 
1187 sub _parse_rights_string {
 
1188   $main::lxdebug->enter_sub(2);
 
1198   push @stack, $cur_ary;
 
1200   while ($access =~ m/^([a-z_0-9]+|\||\&|\(|\)|\s+)/) {
 
1202     substr($access, 0, length $1) = "";
 
1204     next if ($token =~ /\s/);
 
1206     if ($token eq "(") {
 
1207       my $new_cur_ary = [];
 
1208       push @stack, $new_cur_ary;
 
1209       push @{$cur_ary}, $new_cur_ary;
 
1210       $cur_ary = $new_cur_ary;
 
1212     } elsif ($token eq ")") {
 
1216         $main::lxdebug->leave_sub(2);
 
1220       $cur_ary = $stack[-1];
 
1222     } elsif (($token eq "|") || ($token eq "&")) {
 
1223       push @{$cur_ary}, $token;
 
1226       push @{$cur_ary}, $self->{RIGHTS}->{$login}->{$token} * 1;
 
1230   my $result = ($access || (1 < scalar @stack)) ? 0 : evaluate_rights_ary($stack[0]);
 
1232   $main::lxdebug->leave_sub(2);
 
1238   $main::lxdebug->enter_sub(2);
 
1243   my $default = shift;
 
1245   $self->{FULL_RIGHTS}           ||= { };
 
1246   $self->{FULL_RIGHTS}->{$login} ||= { };
 
1248   if (!defined $self->{FULL_RIGHTS}->{$login}->{$right}) {
 
1249     $self->{RIGHTS}           ||= { };
 
1250     $self->{RIGHTS}->{$login} ||= $self->load_rights_for_user($login);
 
1252     $self->{FULL_RIGHTS}->{$login}->{$right} = $self->_parse_rights_string($login, $right);
 
1255   my $granted = $self->{FULL_RIGHTS}->{$login}->{$right};
 
1256   $granted    = $default if (!defined $granted);
 
1258   $main::lxdebug->leave_sub(2);
 
1264   $::lxdebug->enter_sub(2);
 
1265   my ($self, $right, $dont_abort) = @_;
 
1267   if ($self->check_right($::myconfig{login}, $right)) {
 
1268     $::lxdebug->leave_sub(2);
 
1273     delete $::form->{title};
 
1274     $::form->show_generic_error($::locale->text("You do not have the permissions to access this function."));
 
1277   $::lxdebug->leave_sub(2);
 
1282 sub load_rights_for_user {
 
1283   $::lxdebug->enter_sub;
 
1285   my ($self, $login) = @_;
 
1286   my $dbh   = $self->dbconnect;
 
1287   my ($query, $sth, $row, $rights);
 
1289   $rights = { map { $_ => 0 } all_rights() };
 
1292     qq|SELECT gr."right", gr.granted
 
1293        FROM auth.group_rights gr
 
1296           FROM auth.user_group ug
 
1297           LEFT JOIN auth."user" u ON (ug.user_id = u.id)
 
1298           WHERE u.login = ?)|;
 
1300   $sth = prepare_execute_query($::form, $dbh, $query, $login);
 
1302   while ($row = $sth->fetchrow_hashref()) {
 
1303     $rights->{$row->{right}} |= $row->{granted};
 
1307   $::lxdebug->leave_sub;
 
1321 SL::Auth - Authentication and session handling
 
1327 =item C<set_session_value @values>
 
1329 =item C<set_session_value %values>
 
1331 Store all values of C<@values> or C<%values> in the session. Each
 
1332 member of C<@values> is tested if it is a hash reference. If it is
 
1333 then it must contain the keys C<key> and C<value> and can optionally
 
1334 contain the key C<auto_restore>. In this case C<value> is associated
 
1335 with C<key> and restored to C<$::form> upon the next request
 
1336 automatically if C<auto_restore> is trueish or if C<value> is a scalar
 
1339 If the current member of C<@values> is not a hash reference then it
 
1340 will be used as the C<key> and the next entry of C<@values> is used as
 
1341 the C<value> to store. In this case setting C<auto_restore> is not
 
1344 Therefore the following two invocations are identical:
 
1346   $::auth-E<gt>set_session_value(name =E<gt> "Charlie");
 
1347   $::auth-E<gt>set_session_value({ key =E<gt> "name", value =E<gt> "Charlie" });
 
1349 All of these values are copied back into C<$::form> for the next
 
1350 request automatically if they're scalar values or if they have
 
1351 C<auto_restore> set to trueish.
 
1353 The values can be any Perl structure. They are stored as YAML dumps.
 
1355 =item C<get_session_value $key>
 
1357 Retrieve a value from the session. Returns C<undef> if the value
 
1360 =item C<create_unique_sesion_value $value, %params>
 
1362 Create a unique key in the session and store C<$value>
 
1365 Returns the key created in the session.
 
1367 =item C<save_session>
 
1369 Stores the session values in the database. This is the only function
 
1370 that actually stores stuff in the database. Neither the various
 
1371 setters nor the deleter access the database.
 
1373 =item <save_form_in_session %params>
 
1375 Stores the content of C<$params{form}> (default: C<$::form>) in the
 
1376 session using L</create_unique_sesion_value>.
 
1378 If C<$params{non_scalars}> is trueish then non-scalar values will be
 
1379 stored as well. Default is to only store scalar values.
 
1381 The following keys will never be saved: C<login>, C<password>,
 
1382 C<stylesheet>, C<titlebar>, C<version>. Additional keys not to save
 
1383 can be given as an array ref in C<$params{skip_keys}>.
 
1385 Returns the unique key under which the form is stored.
 
1387 =item <restore_form_from_session $key, %params>
 
1389 Restores the form from the session into C<$params{form}> (default:
 
1392 If C<$params{clobber}> is falsish then existing values with the same
 
1393 key in C<$params{form}> will not be overwritten. C<$params{clobber}>
 
1406 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>