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 and edit customers and vendors")],
 
 951     ["part_service_assembly_edit",     $locale->text("Create and edit parts, services, assemblies")],
 
 952     ["project_edit",                   $locale->text("Create and edit projects")],
 
 953     ["--ar",                           $locale->text("AR")],
 
 954     ["sales_quotation_edit",           $locale->text("Create and edit sales quotations")],
 
 955     ["sales_order_edit",               $locale->text("Create and edit sales orders")],
 
 956     ["sales_delivery_order_edit",      $locale->text("Create and edit sales delivery orders")],
 
 957     ["invoice_edit",                   $locale->text("Create and edit invoices and credit notes")],
 
 958     ["dunning_edit",                   $locale->text("Create and edit dunnings")],
 
 959     ["sales_all_edit",                 $locale->text("View/edit all employees sales documents")],
 
 960     ["edit_prices",                    $locale->text("Edit prices and discount (if not used, textfield is ONLY set readonly)")],
 
 961     ["--ap",                           $locale->text("AP")],
 
 962     ["request_quotation_edit",         $locale->text("Create and edit RFQs")],
 
 963     ["purchase_order_edit",            $locale->text("Create and edit purchase orders")],
 
 964     ["purchase_delivery_order_edit",   $locale->text("Create and edit purchase delivery orders")],
 
 965     ["vendor_invoice_edit",            $locale->text("Create and edit vendor invoices")],
 
 966     ["--warehouse_management",         $locale->text("Warehouse management")],
 
 967     ["warehouse_contents",             $locale->text("View warehouse content")],
 
 968     ["warehouse_management",           $locale->text("Warehouse management")],
 
 969     ["--general_ledger_cash",          $locale->text("General ledger and cash")],
 
 970     ["general_ledger",                 $locale->text("Transactions, AR transactions, AP transactions")],
 
 971     ["datev_export",                   $locale->text("DATEV Export")],
 
 972     ["cash",                           $locale->text("Receipt, payment, reconciliation")],
 
 973     ["--reports",                      $locale->text('Reports')],
 
 974     ["report",                         $locale->text('All reports')],
 
 975     ["advance_turnover_tax_return",    $locale->text('Advance turnover tax return')],
 
 976     ["--batch_printing",               $locale->text("Batch Printing")],
 
 977     ["batch_printing",                 $locale->text("Batch Printing")],
 
 978     ["--others",                       $locale->text("Others")],
 
 979     ["email_bcc",                      $locale->text("May set the BCC field when sending emails")],
 
 980     ["config",                         $locale->text("Change Lx-Office installation settings (all menu entries beneath 'System')")],
 
 987   return grep !/^--/, map { $_->[0] } all_rights_full();
 
 991   $main::lxdebug->enter_sub();
 
 995   my $form   = $main::form;
 
 997   my $dbh    = $self->dbconnect();
 
 999   my $query  = 'SELECT * FROM auth."group"';
 
1000   my $sth    = prepare_execute_query($form, $dbh, $query);
 
1004   while ($row = $sth->fetchrow_hashref()) {
 
1005     $groups->{$row->{id}} = $row;
 
1009   $query = 'SELECT * FROM auth.user_group WHERE group_id = ?';
 
1010   $sth   = prepare_query($form, $dbh, $query);
 
1012   foreach $group (values %{$groups}) {
 
1015     do_statement($form, $sth, $query, $group->{id});
 
1017     while ($row = $sth->fetchrow_hashref()) {
 
1018       push @members, $row->{user_id};
 
1020     $group->{members} = [ uniq @members ];
 
1024   $query = 'SELECT * FROM auth.group_rights WHERE group_id = ?';
 
1025   $sth   = prepare_query($form, $dbh, $query);
 
1027   foreach $group (values %{$groups}) {
 
1028     $group->{rights} = {};
 
1030     do_statement($form, $sth, $query, $group->{id});
 
1032     while ($row = $sth->fetchrow_hashref()) {
 
1033       $group->{rights}->{$row->{right}} |= $row->{granted};
 
1036     map { $group->{rights}->{$_} = 0 if (!defined $group->{rights}->{$_}); } all_rights();
 
1040   $main::lxdebug->leave_sub();
 
1046   $main::lxdebug->enter_sub();
 
1051   my $form  = $main::form;
 
1052   my $dbh   = $self->dbconnect();
 
1056   my ($query, $sth, $row, $rights);
 
1058   if (!$group->{id}) {
 
1059     ($group->{id}) = selectrow_query($form, $dbh, qq|SELECT nextval('auth.group_id_seq')|);
 
1061     $query = qq|INSERT INTO auth."group" (id, name, description) VALUES (?, '', '')|;
 
1062     do_query($form, $dbh, $query, $group->{id});
 
1065   do_query($form, $dbh, qq|UPDATE auth."group" SET name = ?, description = ? WHERE id = ?|, map { $group->{$_} } qw(name description id));
 
1067   do_query($form, $dbh, qq|DELETE FROM auth.user_group WHERE group_id = ?|, $group->{id});
 
1069   $query  = qq|INSERT INTO auth.user_group (user_id, group_id) VALUES (?, ?)|;
 
1070   $sth    = prepare_query($form, $dbh, $query);
 
1072   foreach my $user_id (uniq @{ $group->{members} }) {
 
1073     do_statement($form, $sth, $query, $user_id, $group->{id});
 
1077   do_query($form, $dbh, qq|DELETE FROM auth.group_rights WHERE group_id = ?|, $group->{id});
 
1079   $query = qq|INSERT INTO auth.group_rights (group_id, "right", granted) VALUES (?, ?, ?)|;
 
1080   $sth   = prepare_query($form, $dbh, $query);
 
1082   foreach my $right (keys %{ $group->{rights} }) {
 
1083     do_statement($form, $sth, $query, $group->{id}, $right, $group->{rights}->{$right} ? 't' : 'f');
 
1089   $main::lxdebug->leave_sub();
 
1093   $main::lxdebug->enter_sub();
 
1098   my $form = $main::form;
 
1100   my $dbh  = $self->dbconnect();
 
1103   do_query($form, $dbh, qq|DELETE FROM auth.user_group WHERE group_id = ?|, $id);
 
1104   do_query($form, $dbh, qq|DELETE FROM auth.group_rights WHERE group_id = ?|, $id);
 
1105   do_query($form, $dbh, qq|DELETE FROM auth."group" WHERE id = ?|, $id);
 
1109   $main::lxdebug->leave_sub();
 
1112 sub evaluate_rights_ary {
 
1113   $main::lxdebug->enter_sub(2);
 
1120   foreach my $el (@{$ary}) {
 
1121     if (ref $el eq "ARRAY") {
 
1122       if ($action eq '|') {
 
1123         $value |= evaluate_rights_ary($el);
 
1125         $value &= evaluate_rights_ary($el);
 
1128     } elsif (($el eq '&') || ($el eq '|')) {
 
1131     } elsif ($action eq '|') {
 
1140   $main::lxdebug->leave_sub(2);
 
1145 sub _parse_rights_string {
 
1146   $main::lxdebug->enter_sub(2);
 
1156   push @stack, $cur_ary;
 
1158   while ($access =~ m/^([a-z_0-9]+|\||\&|\(|\)|\s+)/) {
 
1160     substr($access, 0, length $1) = "";
 
1162     next if ($token =~ /\s/);
 
1164     if ($token eq "(") {
 
1165       my $new_cur_ary = [];
 
1166       push @stack, $new_cur_ary;
 
1167       push @{$cur_ary}, $new_cur_ary;
 
1168       $cur_ary = $new_cur_ary;
 
1170     } elsif ($token eq ")") {
 
1174         $main::lxdebug->leave_sub(2);
 
1178       $cur_ary = $stack[-1];
 
1180     } elsif (($token eq "|") || ($token eq "&")) {
 
1181       push @{$cur_ary}, $token;
 
1184       push @{$cur_ary}, $self->{RIGHTS}->{$login}->{$token} * 1;
 
1188   my $result = ($access || (1 < scalar @stack)) ? 0 : evaluate_rights_ary($stack[0]);
 
1190   $main::lxdebug->leave_sub(2);
 
1196   $main::lxdebug->enter_sub(2);
 
1201   my $default = shift;
 
1203   $self->{FULL_RIGHTS}           ||= { };
 
1204   $self->{FULL_RIGHTS}->{$login} ||= { };
 
1206   if (!defined $self->{FULL_RIGHTS}->{$login}->{$right}) {
 
1207     $self->{RIGHTS}           ||= { };
 
1208     $self->{RIGHTS}->{$login} ||= $self->load_rights_for_user($login);
 
1210     $self->{FULL_RIGHTS}->{$login}->{$right} = $self->_parse_rights_string($login, $right);
 
1213   my $granted = $self->{FULL_RIGHTS}->{$login}->{$right};
 
1214   $granted    = $default if (!defined $granted);
 
1216   $main::lxdebug->leave_sub(2);
 
1222   $::lxdebug->enter_sub(2);
 
1223   my ($self, $right, $dont_abort) = @_;
 
1225   if ($self->check_right($::myconfig{login}, $right)) {
 
1226     $::lxdebug->leave_sub(2);
 
1231     delete $::form->{title};
 
1232     $::form->show_generic_error($::locale->text("You do not have the permissions to access this function."));
 
1235   $::lxdebug->leave_sub(2);
 
1240 sub load_rights_for_user {
 
1241   $::lxdebug->enter_sub;
 
1243   my ($self, $login) = @_;
 
1244   my $dbh   = $self->dbconnect;
 
1245   my ($query, $sth, $row, $rights);
 
1247   $rights = { map { $_ => 0 } all_rights() };
 
1250     qq|SELECT gr."right", gr.granted
 
1251        FROM auth.group_rights gr
 
1254           FROM auth.user_group ug
 
1255           LEFT JOIN auth."user" u ON (ug.user_id = u.id)
 
1256           WHERE u.login = ?)|;
 
1258   $sth = prepare_execute_query($::form, $dbh, $query, $login);
 
1260   while ($row = $sth->fetchrow_hashref()) {
 
1261     $rights->{$row->{right}} |= $row->{granted};
 
1265   $::lxdebug->leave_sub;
 
1279 SL::Auth - Authentication and session handling
 
1285 =item C<set_session_value @values>
 
1286 =item C<set_session_value %values>
 
1288 Store all values of C<@values> or C<%values> in the session. Each
 
1289 member of C<@values> is tested if it is a hash reference. If it is
 
1290 then it must contain the keys C<key> and C<value> and can optionally
 
1291 contain the key C<auto_restore>. In this case C<value> is associated
 
1292 with C<key> and restored to C<$::form> upon the next request
 
1293 automatically if C<auto_restore> is trueish or if C<value> is a scalar
 
1296 If the current member of C<@values> is not a hash reference then it
 
1297 will be used as the C<key> and the next entry of C<@values> is used as
 
1298 the C<value> to store. In this case setting C<auto_restore> is not
 
1301 Therefore the following two invocations are identical:
 
1303   $::auth-E<gt>set_session_value(name =E<gt> "Charlie");
 
1304   $::auth-E<gt>set_session_value({ key =E<gt> "name", value =E<gt> "Charlie" });
 
1306 All of these values are copied back into C<$::form> for the next
 
1307 request automatically if they're scalar values or if they have
 
1308 C<auto_restore> set to trueish.
 
1310 The values can be any Perl structure. They are stored as YAML dumps.
 
1312 =item C<get_session_value $key>
 
1314 Retrieve a value from the session. Returns C<undef> if the value
 
1317 =item C<create_unique_sesion_value $value, %params>
 
1319 Create a unique key in the session and store C<$value>
 
1322 Returns the key created in the session.
 
1324 =item C<save_session>
 
1326 Stores the session values in the database. This is the only function
 
1327 that actually stores stuff in the database. Neither the various
 
1328 setters nor the deleter access the database.
 
1330 =item <save_form_in_session %params>
 
1332 Stores the content of C<$params{form}> (default: C<$::form>) in the
 
1333 session using L</create_unique_sesion_value>.
 
1335 If C<$params{non_scalars}> is trueish then non-scalar values will be
 
1336 stored as well. Default is to only store scalar values.
 
1338 The following keys will never be saved: C<login>, C<password>,
 
1339 C<stylesheet>, C<titlebar>, C<version>. Additional keys not to save
 
1340 can be given as an array ref in C<$params{skip_keys}>.
 
1342 Returns the unique key under which the form is stored.
 
1344 =item <restore_form_from_session $key, %params>
 
1346 Restores the form from the session into C<$params{form}> (default:
 
1349 If C<$params{clobber}> is falsish then existing values with the same
 
1350 key in C<$params{form}> will not be overwritten. C<$params{clobber}>
 
1363 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>