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;
 
  27   $main::lxdebug->enter_sub();
 
  34   $self->_read_auth_config();
 
  37   $main::lxdebug->leave_sub();
 
  43   my ($self, %params) = @_;
 
  45   $self->{SESSION}            = { };
 
  46   $self->{FULL_RIGHTS}        = { };
 
  47   $self->{RIGHTS}             = { };
 
  48   $self->{unique_counter}     = 0;
 
  49   $self->{column_information} = SL::Auth::ColumnInformation->new(auth => $self);
 
  50   $self->{authenticator}->reset;
 
  54   my ($self, $login, %params) = @_;
 
  55   my $may_fail = delete $params{may_fail};
 
  57   my %user = $self->read_user($login);
 
  58   my $dbh  = SL::DBConnect->connect(
 
  63       pg_enable_utf8 => $::locale->is_utf8,
 
  68   if (!$may_fail && !$dbh) {
 
  69     $::form->error($::locale->text('The connection to the authentication database failed:') . "\n" . $DBI::errstr);
 
  72   if ($user{dboptions} && $dbh) {
 
  73     $dbh->do($user{dboptions}) or $::form->dberror($user{dboptions});
 
  82   $self->{dbh}->disconnect() if ($self->{dbh});
 
  85 # form isn't loaded yet, so auth needs it's own error.
 
  87   $::lxdebug->show_backtrace();
 
  89   my ($self, @msg) = @_;
 
  90   if ($ENV{HTTP_USER_AGENT}) {
 
  91     print Form->create_http_response(content_type => 'text/html');
 
  92     print "<pre>", join ('<br>', @msg), "</pre>";
 
  94     print STDERR "Error: @msg\n";
 
  99 sub _read_auth_config {
 
 100   $main::lxdebug->enter_sub();
 
 104   map { $self->{$_} = $::lx_office_conf{authentication}->{$_} } keys %{ $::lx_office_conf{authentication} };
 
 105   $self->{DB_config}   = $::lx_office_conf{'authentication/database'};
 
 106   $self->{LDAP_config} = $::lx_office_conf{'authentication/ldap'};
 
 108   if ($self->{module} eq 'DB') {
 
 109     $self->{authenticator} = SL::Auth::DB->new($self);
 
 111   } elsif ($self->{module} eq 'LDAP') {
 
 112     $self->{authenticator} = SL::Auth::LDAP->new($self);
 
 115   if (!$self->{authenticator}) {
 
 116     my $locale = Locale->new('en');
 
 117     $self->mini_error($locale->text('No or an unknown authenticantion module specified in "config/lx_office.conf".'));
 
 120   my $cfg = $self->{DB_config};
 
 123     my $locale = Locale->new('en');
 
 124     $self->mini_error($locale->text('config/lx_office.conf: Key "DB_config" is missing.'));
 
 127   if (!$cfg->{host} || !$cfg->{db} || !$cfg->{user}) {
 
 128     my $locale = Locale->new('en');
 
 129     $self->mini_error($locale->text('config/lx_office.conf: Missing parameters in "authentication/database". Required parameters are "host", "db" and "user".'));
 
 132   $self->{authenticator}->verify_config();
 
 134   $self->{session_timeout} *= 1;
 
 135   $self->{session_timeout}  = 8 * 60 if (!$self->{session_timeout});
 
 137   $main::lxdebug->leave_sub();
 
 140 sub authenticate_root {
 
 141   $main::lxdebug->enter_sub();
 
 143   my ($self, $password) = @_;
 
 145   $password             = SL::Auth::Password->hash_if_unhashed(login => 'root', password => $password);
 
 146   my $admin_password    = SL::Auth::Password->hash_if_unhashed(login => 'root', password => $self->{admin_password});
 
 148   $main::lxdebug->leave_sub();
 
 150   return OK if $password eq $admin_password;
 
 156   $main::lxdebug->enter_sub();
 
 158   my ($self, $login, $password) = @_;
 
 160   $main::lxdebug->leave_sub();
 
 162   my $result = $login ? $self->{authenticator}->authenticate($login, $password) : ERR_USER;
 
 163   return OK if $result eq OK;
 
 168 sub store_credentials_in_session {
 
 169   my ($self, %params) = @_;
 
 171   if (!$self->{authenticator}->requires_cleartext_password) {
 
 172     $params{password} = SL::Auth::Password->hash_if_unhashed(login             => $params{login},
 
 173                                                              password          => $params{password},
 
 174                                                              look_up_algorithm => 1,
 
 178   $self->set_session_value(login => $params{login}, password => $params{password});
 
 181 sub store_root_credentials_in_session {
 
 182   my ($self, $rpw) = @_;
 
 184   $self->set_session_value(rpw => SL::Auth::Password->hash_if_unhashed(login => 'root', password => $rpw));
 
 187 sub get_stored_password {
 
 188   my ($self, $login) = @_;
 
 190   my $dbh            = $self->dbconnect;
 
 192   return undef unless $dbh;
 
 194   my $query             = qq|SELECT password FROM auth."user" WHERE login = ?|;
 
 195   my ($stored_password) = $dbh->selectrow_array($query, undef, $login);
 
 197   return $stored_password;
 
 201   $main::lxdebug->enter_sub(2);
 
 204   my $may_fail = shift;
 
 207     $main::lxdebug->leave_sub(2);
 
 211   my $cfg = $self->{DB_config};
 
 212   my $dsn = 'dbi:Pg:dbname=' . $cfg->{db} . ';host=' . $cfg->{host};
 
 215     $dsn .= ';port=' . $cfg->{port};
 
 218   $main::lxdebug->message(LXDebug->DEBUG1, "Auth::dbconnect DSN: $dsn");
 
 220   $self->{dbh} = SL::DBConnect->connect($dsn, $cfg->{user}, $cfg->{password}, { pg_enable_utf8 => $::locale->is_utf8, AutoCommit => 1 });
 
 222   if (!$may_fail && !$self->{dbh}) {
 
 223     $main::form->error($main::locale->text('The connection to the authentication database failed:') . "\n" . $DBI::errstr);
 
 226   $main::lxdebug->leave_sub(2);
 
 232   $main::lxdebug->enter_sub();
 
 237     $self->{dbh}->disconnect();
 
 241   $main::lxdebug->leave_sub();
 
 245   $main::lxdebug->enter_sub();
 
 249   my $dbh     = $self->dbconnect();
 
 250   my $query   = qq|SELECT COUNT(*) FROM pg_tables WHERE (schemaname = 'auth') AND (tablename = 'user')|;
 
 252   my ($count) = $dbh->selectrow_array($query);
 
 254   $main::lxdebug->leave_sub();
 
 260   $main::lxdebug->enter_sub();
 
 264   my $dbh  = $self->dbconnect(1);
 
 266   $main::lxdebug->leave_sub();
 
 271 sub create_database {
 
 272   $main::lxdebug->enter_sub();
 
 277   my $cfg    = $self->{DB_config};
 
 279   if (!$params{superuser}) {
 
 280     $params{superuser}          = $cfg->{user};
 
 281     $params{superuser_password} = $cfg->{password};
 
 284   $params{template} ||= 'template0';
 
 285   $params{template}   =~ s|[^a-zA-Z0-9_\-]||g;
 
 287   my $dsn = 'dbi:Pg:dbname=template1;host=' . $cfg->{host};
 
 290     $dsn .= ';port=' . $cfg->{port};
 
 293   $main::lxdebug->message(LXDebug->DEBUG1(), "Auth::create_database DSN: $dsn");
 
 295   my $charset    = $::lx_office_conf{system}->{dbcharset};
 
 296   $charset     ||= Common::DEFAULT_CHARSET;
 
 297   my $encoding   = $Common::charset_to_db_encoding{$charset};
 
 298   $encoding    ||= 'UNICODE';
 
 300   my $dbh        = SL::DBConnect->connect($dsn, $params{superuser}, $params{superuser_password}, { pg_enable_utf8 => scalar($charset =~ m/^utf-?8$/i) });
 
 303     $main::form->error($main::locale->text('The connection to the template database failed:') . "\n" . $DBI::errstr);
 
 306   my $query = qq|CREATE DATABASE "$cfg->{db}" OWNER "$cfg->{user}" TEMPLATE "$params{template}" ENCODING '$encoding'|;
 
 308   $main::lxdebug->message(LXDebug->DEBUG1(), "Auth::create_database query: $query");
 
 313     my $error = $dbh->errstr();
 
 315     $query                 = qq|SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = 'template0'|;
 
 316     my ($cluster_encoding) = $dbh->selectrow_array($query);
 
 318     if ($cluster_encoding && ($cluster_encoding =~ m/^(?:UTF-?8|UNICODE)$/i) && ($encoding !~ m/^(?:UTF-?8|UNICODE)$/i)) {
 
 319       $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.');
 
 324     $main::form->error($main::locale->text('The creation of the authentication database failed:') . "\n" . $error);
 
 329   $main::lxdebug->leave_sub();
 
 333   $main::lxdebug->enter_sub();
 
 336   my $dbh  = $self->dbconnect();
 
 338   my $charset    = $::lx_office_conf{system}->{dbcharset};
 
 339   $charset     ||= Common::DEFAULT_CHARSET;
 
 342   SL::DBUpgrade2->new(form => $::form)->process_query($dbh, 'sql/auth_db.sql', undef, $charset);
 
 344   $main::lxdebug->leave_sub();
 
 348   $main::lxdebug->enter_sub();
 
 354   my $form   = $main::form;
 
 356   my $dbh    = $self->dbconnect();
 
 358   my ($sth, $query, $user_id);
 
 362   $query     = qq|SELECT id FROM auth."user" WHERE login = ?|;
 
 363   ($user_id) = selectrow_query($form, $dbh, $query, $login);
 
 366     $query     = qq|SELECT nextval('auth.user_id_seq')|;
 
 367     ($user_id) = selectrow_query($form, $dbh, $query);
 
 369     $query     = qq|INSERT INTO auth."user" (id, login) VALUES (?, ?)|;
 
 370     do_query($form, $dbh, $query, $user_id, $login);
 
 373   $query = qq|DELETE FROM auth.user_config WHERE (user_id = ?)|;
 
 374   do_query($form, $dbh, $query, $user_id);
 
 376   $query = qq|INSERT INTO auth.user_config (user_id, cfg_key, cfg_value) VALUES (?, ?, ?)|;
 
 377   $sth   = prepare_query($form, $dbh, $query);
 
 379   while (my ($cfg_key, $cfg_value) = each %params) {
 
 380     next if ($cfg_key eq 'password');
 
 382     do_statement($form, $sth, $query, $user_id, $cfg_key, $cfg_value);
 
 387   $main::lxdebug->leave_sub();
 
 390 sub can_change_password {
 
 393   return $self->{authenticator}->can_change_password();
 
 396 sub change_password {
 
 397   $main::lxdebug->enter_sub();
 
 399   my ($self, $login, $new_password) = @_;
 
 401   my $result = $self->{authenticator}->change_password($login, $new_password);
 
 403   $self->store_credentials_in_session(login             => $login,
 
 404                                       password          => $new_password,
 
 405                                       look_up_algorithm => 1,
 
 408   $main::lxdebug->leave_sub();
 
 414   $main::lxdebug->enter_sub();
 
 418   my $dbh   = $self->dbconnect();
 
 419   my $query = qq|SELECT u.id, u.login, cfg.cfg_key, cfg.cfg_value
 
 420                  FROM auth.user_config cfg
 
 421                  LEFT JOIN auth."user" u ON (cfg.user_id = u.id)|;
 
 422   my $sth   = prepare_execute_query($main::form, $dbh, $query);
 
 426   while (my $ref = $sth->fetchrow_hashref()) {
 
 427     $users{$ref->{login}}                    ||= { 'login' => $ref->{login}, 'id' => $ref->{id} };
 
 428     $users{$ref->{login}}->{$ref->{cfg_key}}   = $ref->{cfg_value} if (($ref->{cfg_key} ne 'login') && ($ref->{cfg_key} ne 'id'));
 
 433   $main::lxdebug->leave_sub();
 
 439   $main::lxdebug->enter_sub();
 
 444   my $dbh   = $self->dbconnect();
 
 445   my $query = qq|SELECT u.id, u.login, cfg.cfg_key, cfg.cfg_value
 
 446                  FROM auth.user_config cfg
 
 447                  LEFT JOIN auth."user" u ON (cfg.user_id = u.id)
 
 448                  WHERE (u.login = ?)|;
 
 449   my $sth   = prepare_execute_query($main::form, $dbh, $query, $login);
 
 453   while (my $ref = $sth->fetchrow_hashref()) {
 
 454     $user_data{$ref->{cfg_key}} = $ref->{cfg_value};
 
 455     @user_data{qw(id login)}    = @{$ref}{qw(id login)};
 
 458   # The XUL/XML backed menu has been removed.
 
 459   $user_data{menustyle} = 'v3' if lc($user_data{menustyle} || '') eq 'xml';
 
 463   $main::lxdebug->leave_sub();
 
 469   $main::lxdebug->enter_sub();
 
 474   my $dbh   = $self->dbconnect();
 
 475   my ($id)  = selectrow_query($main::form, $dbh, qq|SELECT id FROM auth."user" WHERE login = ?|, $login);
 
 477   $main::lxdebug->leave_sub();
 
 483   $::lxdebug->enter_sub;
 
 488   my $u_dbh = $self->get_user_dbh($login, may_fail => 1);
 
 489   my $dbh   = $self->dbconnect;
 
 493   my $query = qq|SELECT id FROM auth."user" WHERE login = ?|;
 
 495   my ($id)  = selectrow_query($::form, $dbh, $query, $login);
 
 497   $dbh->rollback and return $::lxdebug->leave_sub if (!$id);
 
 499   do_query($::form, $dbh, qq|DELETE FROM auth.user_group WHERE user_id = ?|, $id);
 
 500   do_query($::form, $dbh, qq|DELETE FROM auth.user_config WHERE user_id = ?|, $id);
 
 501   do_query($::form, $u_dbh, qq|UPDATE employee SET deleted = 't' WHERE login = ?|, $login) if $u_dbh;
 
 504   $u_dbh->commit if $u_dbh;
 
 506   $::lxdebug->leave_sub;
 
 509 # --------------------------------------
 
 513 sub restore_session {
 
 514   $main::lxdebug->enter_sub();
 
 518   $session_id        =  $::request->{cgi}->cookie($self->get_session_cookie_name());
 
 519   $session_id        =~ s|[^0-9a-f]||g if $session_id;
 
 521   $self->{SESSION}   = { };
 
 524     $main::lxdebug->leave_sub();
 
 528   my ($dbh, $query, $sth, $cookie, $ref, $form);
 
 532   # Don't fail if the auth DB doesn't yet.
 
 533   if (!( $dbh = $self->dbconnect(1) )) {
 
 534     $::lxdebug->leave_sub;
 
 538   # Don't fail if the "auth" schema doesn't exist yet, e.g. if the
 
 539   # admin is creating the session tables at the moment.
 
 540   $query  = qq|SELECT *, (mtime < (now() - '$self->{session_timeout}m'::interval)) AS is_expired FROM auth.session WHERE id = ?|;
 
 542   if (!($sth = $dbh->prepare($query)) || !$sth->execute($session_id)) {
 
 543     $sth->finish if $sth;
 
 544     $::lxdebug->leave_sub;
 
 548   $cookie = $sth->fetchrow_hashref;
 
 551   if (!$cookie || $cookie->{is_expired} || ($cookie->{ip_address} ne $ENV{REMOTE_ADDR})) {
 
 552     $self->destroy_session();
 
 553     $main::lxdebug->leave_sub();
 
 554     return $cookie ? SESSION_EXPIRED : SESSION_NONE;
 
 557   if ($self->{column_information}->has('auto_restore')) {
 
 558     $self->_load_with_auto_restore_column($dbh, $session_id);
 
 560     $self->_load_without_auto_restore_column($dbh, $session_id);
 
 563   $main::lxdebug->leave_sub();
 
 568 sub _load_without_auto_restore_column {
 
 569   my ($self, $dbh, $session_id) = @_;
 
 572     SELECT sess_key, sess_value
 
 573     FROM auth.session_content
 
 574     WHERE (session_id = ?)
 
 576   my $sth = prepare_execute_query($::form, $dbh, $query, $session_id);
 
 578   while (my $ref = $sth->fetchrow_hashref) {
 
 579     my $value = SL::Auth::SessionValue->new(auth  => $self,
 
 580                                             key   => $ref->{sess_key},
 
 581                                             value => $ref->{sess_value},
 
 583     $self->{SESSION}->{ $ref->{sess_key} } = $value;
 
 585     next if defined $::form->{$ref->{sess_key}};
 
 587     my $data                    = $value->get;
 
 588     $::form->{$ref->{sess_key}} = $data if $value->{auto_restore} || !ref $data;
 
 592 sub _load_with_auto_restore_column {
 
 593   my ($self, $dbh, $session_id) = @_;
 
 595   my $auto_restore_keys = join ', ', map { "'${_}'" } qw(login password rpw);
 
 598     SELECT sess_key, sess_value, auto_restore
 
 599     FROM auth.session_content
 
 600     WHERE (session_id = ?)
 
 602            OR sess_key IN (${auto_restore_keys}))
 
 604   my $sth = prepare_execute_query($::form, $dbh, $query, $session_id);
 
 606   while (my $ref = $sth->fetchrow_hashref) {
 
 607     my $value = SL::Auth::SessionValue->new(auth         => $self,
 
 608                                             key          => $ref->{sess_key},
 
 609                                             value        => $ref->{sess_value},
 
 610                                             auto_restore => $ref->{auto_restore},
 
 612     $self->{SESSION}->{ $ref->{sess_key} } = $value;
 
 614     next if defined $::form->{$ref->{sess_key}};
 
 616     my $data                    = $value->get;
 
 617     $::form->{$ref->{sess_key}} = $data if $value->{auto_restore} || !ref $data;
 
 624     FROM auth.session_content
 
 625     WHERE (session_id = ?)
 
 626       AND NOT COALESCE(auto_restore, FALSE)
 
 627       AND (sess_key NOT IN (${auto_restore_keys}))
 
 629   $sth = prepare_execute_query($::form, $dbh, $query, $session_id);
 
 631   while (my $ref = $sth->fetchrow_hashref) {
 
 632     my $value = SL::Auth::SessionValue->new(auth => $self,
 
 633                                             key  => $ref->{sess_key});
 
 634     $self->{SESSION}->{ $ref->{sess_key} } = $value;
 
 638 sub destroy_session {
 
 639   $main::lxdebug->enter_sub();
 
 644     my $dbh = $self->dbconnect();
 
 648     do_query($main::form, $dbh, qq|DELETE FROM auth.session_content WHERE session_id = ?|, $session_id);
 
 649     do_query($main::form, $dbh, qq|DELETE FROM auth.session WHERE id = ?|, $session_id);
 
 653     SL::SessionFile->destroy_session($session_id);
 
 656     $self->{SESSION} = { };
 
 659   $main::lxdebug->leave_sub();
 
 662 sub expire_sessions {
 
 663   $main::lxdebug->enter_sub();
 
 667   $main::lxdebug->leave_sub and return if !$self->session_tables_present;
 
 669   my $dbh   = $self->dbconnect();
 
 671   my $query = qq|SELECT id
 
 673                  WHERE (mtime < (now() - '$self->{session_timeout}m'::interval))|;
 
 675   my @ids   = selectall_array_query($::form, $dbh, $query);
 
 680     SL::SessionFile->destroy_session($_) for @ids;
 
 682     $query = qq|DELETE FROM auth.session_content
 
 683                 WHERE session_id IN (| . join(', ', ('?') x scalar(@ids)) . qq|)|;
 
 684     do_query($main::form, $dbh, $query, @ids);
 
 686     $query = qq|DELETE FROM auth.session
 
 687                 WHERE id IN (| . join(', ', ('?') x scalar(@ids)) . qq|)|;
 
 688     do_query($main::form, $dbh, $query, @ids);
 
 693   $main::lxdebug->leave_sub();
 
 696 sub _create_session_id {
 
 697   $main::lxdebug->enter_sub();
 
 700   map { push @data, int(rand() * 255); } (1..32);
 
 702   my $id = md5_hex(pack 'C*', @data);
 
 704   $main::lxdebug->leave_sub();
 
 709 sub create_or_refresh_session {
 
 710   $session_id ||= shift->_create_session_id;
 
 714   $::lxdebug->enter_sub;
 
 716   my $provided_dbh = shift;
 
 718   my $dbh          = $provided_dbh || $self->dbconnect(1);
 
 720   $::lxdebug->leave_sub && return unless $dbh && $session_id;
 
 722   $dbh->begin_work unless $provided_dbh;
 
 724   # If this fails then the "auth" schema might not exist yet, e.g. if
 
 725   # the admin is just trying to create the auth database.
 
 726   if (!$dbh->do(qq|LOCK auth.session_content|)) {
 
 727     $dbh->rollback unless $provided_dbh;
 
 728     $::lxdebug->leave_sub;
 
 732   my @unfetched_keys = map     { $_->{key}        }
 
 733                        grep    { ! $_->{fetched}  }
 
 734                        values %{ $self->{SESSION} };
 
 735   # $::lxdebug->dump(0, "unfetched_keys", [ sort @unfetched_keys ]);
 
 736   # $::lxdebug->dump(0, "all keys", [ sort map { $_->{key} } values %{ $self->{SESSION} } ]);
 
 737   my $query          = qq|DELETE FROM auth.session_content WHERE (session_id = ?)|;
 
 738   $query            .= qq| AND (sess_key NOT IN (| . join(', ', ('?') x scalar @unfetched_keys) . qq|))| if @unfetched_keys;
 
 740   do_query($::form, $dbh, $query, $session_id, @unfetched_keys);
 
 742   my ($id) = selectrow_query($::form, $dbh, qq|SELECT id FROM auth.session WHERE id = ?|, $session_id);
 
 745     do_query($::form, $dbh, qq|UPDATE auth.session SET mtime = now() WHERE id = ?|, $session_id);
 
 747     do_query($::form, $dbh, qq|INSERT INTO auth.session (id, ip_address, mtime) VALUES (?, ?, now())|, $session_id, $ENV{REMOTE_ADDR});
 
 750   my @values_to_save = grep    { $_->{fetched} }
 
 751                        values %{ $self->{SESSION} };
 
 752   if (@values_to_save) {
 
 753     my ($columns, $placeholders) = ('', '');
 
 754     my $auto_restore             = $self->{column_information}->has('auto_restore');
 
 757       $columns      .= ', auto_restore';
 
 758       $placeholders .= ', ?';
 
 761     $query  = qq|INSERT INTO auth.session_content (session_id, sess_key, sess_value ${columns}) VALUES (?, ?, ? ${placeholders})|;
 
 762     my $sth = prepare_query($::form, $dbh, $query);
 
 764     foreach my $value (@values_to_save) {
 
 765       my @values = ($value->{key}, $value->get_dumped);
 
 766       push @values, $value->{auto_restore} if $auto_restore;
 
 768       do_statement($::form, $sth, $query, $session_id, @values);
 
 774   $dbh->commit() unless $provided_dbh;
 
 775   $::lxdebug->leave_sub;
 
 778 sub set_session_value {
 
 779   $main::lxdebug->enter_sub();
 
 784   $self->{SESSION} ||= { };
 
 787     my $key = shift @params;
 
 789     if (ref $key eq 'HASH') {
 
 790       $self->{SESSION}->{ $key->{key} } = SL::Auth::SessionValue->new(key          => $key->{key},
 
 791                                                                       value        => $key->{value},
 
 792                                                                       auto_restore => $key->{auto_restore});
 
 795       my $value = shift @params;
 
 796       $self->{SESSION}->{ $key } = SL::Auth::SessionValue->new(key   => $key,
 
 801   $main::lxdebug->leave_sub();
 
 806 sub delete_session_value {
 
 807   $main::lxdebug->enter_sub();
 
 811   $self->{SESSION} ||= { };
 
 812   delete @{ $self->{SESSION} }{ @_ };
 
 814   $main::lxdebug->leave_sub();
 
 819 sub get_session_value {
 
 820   $main::lxdebug->enter_sub();
 
 823   my $data = $self->{SESSION} && $self->{SESSION}->{ $_[0] } ? $self->{SESSION}->{ $_[0] }->get : undef;
 
 825   $main::lxdebug->leave_sub();
 
 830 sub create_unique_sesion_value {
 
 831   my ($self, $value, %params) = @_;
 
 833   $self->{SESSION} ||= { };
 
 835   my @now                   = gettimeofday();
 
 836   my $key                   = "$$-" . ($now[0] * 1000000 + $now[1]) . "-";
 
 837   $self->{unique_counter} ||= 0;
 
 841     $self->{unique_counter}++;
 
 842     $hashed_key = md5_hex($key . $self->{unique_counter});
 
 843   } while (exists $self->{SESSION}->{$hashed_key});
 
 845   $self->set_session_value($hashed_key => $value);
 
 850 sub save_form_in_session {
 
 851   my ($self, %params) = @_;
 
 853   my $form        = delete($params{form}) || $::form;
 
 854   my $non_scalars = delete $params{non_scalars};
 
 857   my %skip_keys   = map { ( $_ => 1 ) } (qw(login password stylesheet version titlebar), @{ $params{skip_keys} || [] });
 
 859   foreach my $key (grep { !$skip_keys{$_} } keys %{ $form }) {
 
 860     $data->{$key} = $form->{$key} if !ref($form->{$key}) || $non_scalars;
 
 863   return $self->create_unique_sesion_value($data, %params);
 
 866 sub restore_form_from_session {
 
 867   my ($self, $key, %params) = @_;
 
 869   my $data = $self->get_session_value($key);
 
 870   return $self unless $data;
 
 872   my $form    = delete($params{form}) || $::form;
 
 873   my $clobber = exists $params{clobber} ? $params{clobber} : 1;
 
 875   map { $form->{$_} = $data->{$_} if $clobber || !exists $form->{$_} } keys %{ $data };
 
 880 sub set_cookie_environment_variable {
 
 882   $ENV{HTTP_COOKIE} = $self->get_session_cookie_name() . "=${session_id}";
 
 885 sub get_session_cookie_name {
 
 888   return $self->{cookie_name} || 'lx_office_erp_session_id';
 
 895 sub session_tables_present {
 
 896   $main::lxdebug->enter_sub();
 
 900   # Only re-check for the presence of auth tables if either the check
 
 901   # hasn't been done before of if they weren't present.
 
 902   if ($self->{session_tables_present}) {
 
 903     $main::lxdebug->leave_sub();
 
 904     return $self->{session_tables_present};
 
 907   my $dbh  = $self->dbconnect(1);
 
 910     $main::lxdebug->leave_sub();
 
 917        WHERE (schemaname = 'auth')
 
 918          AND (tablename IN ('session', 'session_content'))|;
 
 920   my ($count) = selectrow_query($main::form, $dbh, $query);
 
 922   $self->{session_tables_present} = 2 == $count;
 
 924   $main::lxdebug->leave_sub();
 
 926   return $self->{session_tables_present};
 
 929 # --------------------------------------
 
 931 sub all_rights_full {
 
 932   my $locale = $main::locale;
 
 935     ["--crm",                          $locale->text("CRM optional software")],
 
 936     ["crm_search",                     $locale->text("CRM search")],
 
 937     ["crm_new",                        $locale->text("CRM create customers, vendors and contacts")],
 
 938     ["crm_service",                    $locale->text("CRM services")],
 
 939     ["crm_admin",                      $locale->text("CRM admin")],
 
 940     ["crm_adminuser",                  $locale->text("CRM user")],
 
 941     ["crm_adminstatus",                $locale->text("CRM status")],
 
 942     ["crm_email",                      $locale->text("CRM send email")],
 
 943     ["crm_termin",                     $locale->text("CRM termin")],
 
 944     ["crm_opportunity",                $locale->text("CRM opportunity")],
 
 945     ["crm_knowhow",                    $locale->text("CRM know how")],
 
 946     ["crm_follow",                     $locale->text("CRM follow up")],
 
 947     ["crm_notices",                    $locale->text("CRM notices")],
 
 948     ["crm_other",                      $locale->text("CRM other")],
 
 949     ["--master_data",                  $locale->text("Master Data")],
 
 950     ["customer_vendor_edit",           $locale->text("Create customers and vendors. Edit all vendors. Edit only customers where salesman equals employee (login)")],
 
 951     ["customer_vendor_all_edit",       $locale->text("Create customers and vendors. Edit all vendors. Edit all customers")],
 
 952     ["part_service_assembly_edit",     $locale->text("Create and edit parts, services, assemblies")],
 
 953     ["project_edit",                   $locale->text("Create and edit projects")],
 
 954     ["--ar",                           $locale->text("AR")],
 
 955     ["sales_quotation_edit",           $locale->text("Create and edit sales quotations")],
 
 956     ["sales_order_edit",               $locale->text("Create and edit sales orders")],
 
 957     ["sales_delivery_order_edit",      $locale->text("Create and edit sales delivery orders")],
 
 958     ["invoice_edit",                   $locale->text("Create and edit invoices and credit notes")],
 
 959     ["dunning_edit",                   $locale->text("Create and edit dunnings")],
 
 960     ["sales_all_edit",                 $locale->text("View/edit all employees sales documents")],
 
 961     ["edit_prices",                    $locale->text("Edit prices and discount (if not used, textfield is ONLY set readonly)")],
 
 962     ["--ap",                           $locale->text("AP")],
 
 963     ["request_quotation_edit",         $locale->text("Create and edit RFQs")],
 
 964     ["purchase_order_edit",            $locale->text("Create and edit purchase orders")],
 
 965     ["purchase_delivery_order_edit",   $locale->text("Create and edit purchase delivery orders")],
 
 966     ["vendor_invoice_edit",            $locale->text("Create and edit vendor invoices")],
 
 967     ["--warehouse_management",         $locale->text("Warehouse management")],
 
 968     ["warehouse_contents",             $locale->text("View warehouse content")],
 
 969     ["warehouse_management",           $locale->text("Warehouse management")],
 
 970     ["--general_ledger_cash",          $locale->text("General ledger and cash")],
 
 971     ["general_ledger",                 $locale->text("Transactions, AR transactions, AP transactions")],
 
 972     ["datev_export",                   $locale->text("DATEV Export")],
 
 973     ["cash",                           $locale->text("Receipt, payment, reconciliation")],
 
 974     ["--reports",                      $locale->text('Reports')],
 
 975     ["report",                         $locale->text('All reports')],
 
 976     ["advance_turnover_tax_return",    $locale->text('Advance turnover tax return')],
 
 977     ["--batch_printing",               $locale->text("Batch Printing")],
 
 978     ["batch_printing",                 $locale->text("Batch Printing")],
 
 979     ["--others",                       $locale->text("Others")],
 
 980     ["email_bcc",                      $locale->text("May set the BCC field when sending emails")],
 
 981     ["config",                         $locale->text("Change Lx-Office installation settings (all menu entries beneath 'System')")],
 
 988   return grep !/^--/, map { $_->[0] } all_rights_full();
 
 992   $main::lxdebug->enter_sub();
 
 996   my $form   = $main::form;
 
 998   my $dbh    = $self->dbconnect();
 
1000   my $query  = 'SELECT * FROM auth."group"';
 
1001   my $sth    = prepare_execute_query($form, $dbh, $query);
 
1005   while ($row = $sth->fetchrow_hashref()) {
 
1006     $groups->{$row->{id}} = $row;
 
1010   $query = 'SELECT * FROM auth.user_group WHERE group_id = ?';
 
1011   $sth   = prepare_query($form, $dbh, $query);
 
1013   foreach $group (values %{$groups}) {
 
1016     do_statement($form, $sth, $query, $group->{id});
 
1018     while ($row = $sth->fetchrow_hashref()) {
 
1019       push @members, $row->{user_id};
 
1021     $group->{members} = [ uniq @members ];
 
1025   $query = 'SELECT * FROM auth.group_rights WHERE group_id = ?';
 
1026   $sth   = prepare_query($form, $dbh, $query);
 
1028   foreach $group (values %{$groups}) {
 
1029     $group->{rights} = {};
 
1031     do_statement($form, $sth, $query, $group->{id});
 
1033     while ($row = $sth->fetchrow_hashref()) {
 
1034       $group->{rights}->{$row->{right}} |= $row->{granted};
 
1037     map { $group->{rights}->{$_} = 0 if (!defined $group->{rights}->{$_}); } all_rights();
 
1041   $main::lxdebug->leave_sub();
 
1047   $main::lxdebug->enter_sub();
 
1052   my $form  = $main::form;
 
1053   my $dbh   = $self->dbconnect();
 
1057   my ($query, $sth, $row, $rights);
 
1059   if (!$group->{id}) {
 
1060     ($group->{id}) = selectrow_query($form, $dbh, qq|SELECT nextval('auth.group_id_seq')|);
 
1062     $query = qq|INSERT INTO auth."group" (id, name, description) VALUES (?, '', '')|;
 
1063     do_query($form, $dbh, $query, $group->{id});
 
1066   do_query($form, $dbh, qq|UPDATE auth."group" SET name = ?, description = ? WHERE id = ?|, map { $group->{$_} } qw(name description id));
 
1068   do_query($form, $dbh, qq|DELETE FROM auth.user_group WHERE group_id = ?|, $group->{id});
 
1070   $query  = qq|INSERT INTO auth.user_group (user_id, group_id) VALUES (?, ?)|;
 
1071   $sth    = prepare_query($form, $dbh, $query);
 
1073   foreach my $user_id (uniq @{ $group->{members} }) {
 
1074     do_statement($form, $sth, $query, $user_id, $group->{id});
 
1078   do_query($form, $dbh, qq|DELETE FROM auth.group_rights WHERE group_id = ?|, $group->{id});
 
1080   $query = qq|INSERT INTO auth.group_rights (group_id, "right", granted) VALUES (?, ?, ?)|;
 
1081   $sth   = prepare_query($form, $dbh, $query);
 
1083   foreach my $right (keys %{ $group->{rights} }) {
 
1084     do_statement($form, $sth, $query, $group->{id}, $right, $group->{rights}->{$right} ? 't' : 'f');
 
1090   $main::lxdebug->leave_sub();
 
1094   $main::lxdebug->enter_sub();
 
1099   my $form = $main::form;
 
1101   my $dbh  = $self->dbconnect();
 
1104   do_query($form, $dbh, qq|DELETE FROM auth.user_group WHERE group_id = ?|, $id);
 
1105   do_query($form, $dbh, qq|DELETE FROM auth.group_rights WHERE group_id = ?|, $id);
 
1106   do_query($form, $dbh, qq|DELETE FROM auth."group" WHERE id = ?|, $id);
 
1110   $main::lxdebug->leave_sub();
 
1113 sub evaluate_rights_ary {
 
1114   $main::lxdebug->enter_sub(2);
 
1121   foreach my $el (@{$ary}) {
 
1122     if (ref $el eq "ARRAY") {
 
1123       if ($action eq '|') {
 
1124         $value |= evaluate_rights_ary($el);
 
1126         $value &= evaluate_rights_ary($el);
 
1129     } elsif (($el eq '&') || ($el eq '|')) {
 
1132     } elsif ($action eq '|') {
 
1141   $main::lxdebug->leave_sub(2);
 
1146 sub _parse_rights_string {
 
1147   $main::lxdebug->enter_sub(2);
 
1157   push @stack, $cur_ary;
 
1159   while ($access =~ m/^([a-z_0-9]+|\||\&|\(|\)|\s+)/) {
 
1161     substr($access, 0, length $1) = "";
 
1163     next if ($token =~ /\s/);
 
1165     if ($token eq "(") {
 
1166       my $new_cur_ary = [];
 
1167       push @stack, $new_cur_ary;
 
1168       push @{$cur_ary}, $new_cur_ary;
 
1169       $cur_ary = $new_cur_ary;
 
1171     } elsif ($token eq ")") {
 
1175         $main::lxdebug->leave_sub(2);
 
1179       $cur_ary = $stack[-1];
 
1181     } elsif (($token eq "|") || ($token eq "&")) {
 
1182       push @{$cur_ary}, $token;
 
1185       push @{$cur_ary}, $self->{RIGHTS}->{$login}->{$token} * 1;
 
1189   my $result = ($access || (1 < scalar @stack)) ? 0 : evaluate_rights_ary($stack[0]);
 
1191   $main::lxdebug->leave_sub(2);
 
1197   $main::lxdebug->enter_sub(2);
 
1202   my $default = shift;
 
1204   $self->{FULL_RIGHTS}           ||= { };
 
1205   $self->{FULL_RIGHTS}->{$login} ||= { };
 
1207   if (!defined $self->{FULL_RIGHTS}->{$login}->{$right}) {
 
1208     $self->{RIGHTS}           ||= { };
 
1209     $self->{RIGHTS}->{$login} ||= $self->load_rights_for_user($login);
 
1211     $self->{FULL_RIGHTS}->{$login}->{$right} = $self->_parse_rights_string($login, $right);
 
1214   my $granted = $self->{FULL_RIGHTS}->{$login}->{$right};
 
1215   $granted    = $default if (!defined $granted);
 
1217   $main::lxdebug->leave_sub(2);
 
1223   $::lxdebug->enter_sub(2);
 
1224   my ($self, $right, $dont_abort) = @_;
 
1226   if ($self->check_right($::myconfig{login}, $right)) {
 
1227     $::lxdebug->leave_sub(2);
 
1232     delete $::form->{title};
 
1233     $::form->show_generic_error($::locale->text("You do not have the permissions to access this function."));
 
1236   $::lxdebug->leave_sub(2);
 
1241 sub load_rights_for_user {
 
1242   $::lxdebug->enter_sub;
 
1244   my ($self, $login) = @_;
 
1245   my $dbh   = $self->dbconnect;
 
1246   my ($query, $sth, $row, $rights);
 
1248   $rights = { map { $_ => 0 } all_rights() };
 
1251     qq|SELECT gr."right", gr.granted
 
1252        FROM auth.group_rights gr
 
1255           FROM auth.user_group ug
 
1256           LEFT JOIN auth."user" u ON (ug.user_id = u.id)
 
1257           WHERE u.login = ?)|;
 
1259   $sth = prepare_execute_query($::form, $dbh, $query, $login);
 
1261   while ($row = $sth->fetchrow_hashref()) {
 
1262     $rights->{$row->{right}} |= $row->{granted};
 
1266   $::lxdebug->leave_sub;
 
1280 SL::Auth - Authentication and session handling
 
1286 =item C<set_session_value @values>
 
1287 =item C<set_session_value %values>
 
1289 Store all values of C<@values> or C<%values> in the session. Each
 
1290 member of C<@values> is tested if it is a hash reference. If it is
 
1291 then it must contain the keys C<key> and C<value> and can optionally
 
1292 contain the key C<auto_restore>. In this case C<value> is associated
 
1293 with C<key> and restored to C<$::form> upon the next request
 
1294 automatically if C<auto_restore> is trueish or if C<value> is a scalar
 
1297 If the current member of C<@values> is not a hash reference then it
 
1298 will be used as the C<key> and the next entry of C<@values> is used as
 
1299 the C<value> to store. In this case setting C<auto_restore> is not
 
1302 Therefore the following two invocations are identical:
 
1304   $::auth-E<gt>set_session_value(name =E<gt> "Charlie");
 
1305   $::auth-E<gt>set_session_value({ key =E<gt> "name", value =E<gt> "Charlie" });
 
1307 All of these values are copied back into C<$::form> for the next
 
1308 request automatically if they're scalar values or if they have
 
1309 C<auto_restore> set to trueish.
 
1311 The values can be any Perl structure. They are stored as YAML dumps.
 
1313 =item C<get_session_value $key>
 
1315 Retrieve a value from the session. Returns C<undef> if the value
 
1318 =item C<create_unique_sesion_value $value, %params>
 
1320 Create a unique key in the session and store C<$value>
 
1323 Returns the key created in the session.
 
1325 =item C<save_session>
 
1327 Stores the session values in the database. This is the only function
 
1328 that actually stores stuff in the database. Neither the various
 
1329 setters nor the deleter access the database.
 
1331 =item <save_form_in_session %params>
 
1333 Stores the content of C<$params{form}> (default: C<$::form>) in the
 
1334 session using L</create_unique_sesion_value>.
 
1336 If C<$params{non_scalars}> is trueish then non-scalar values will be
 
1337 stored as well. Default is to only store scalar values.
 
1339 The following keys will never be saved: C<login>, C<password>,
 
1340 C<stylesheet>, C<titlebar>, C<version>. Additional keys not to save
 
1341 can be given as an array ref in C<$params{skip_keys}>.
 
1343 Returns the unique key under which the form is stored.
 
1345 =item <restore_form_from_session $key, %params>
 
1347 Restores the form from the session into C<$params{form}> (default:
 
1350 If C<$params{clobber}> is falsish then existing values with the same
 
1351 key in C<$params{form}> will not be overwritten. C<$params{clobber}>
 
1364 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>