4 use constant ERR_PASSWORD => 1;
5 use constant ERR_BACKEND => 100;
7 use constant SESSION_OK => 0;
8 use constant SESSION_NONE => 1;
9 use constant SESSION_EXPIRED => 2;
11 use Digest::MD5 qw(md5_hex);
13 use Time::HiRes qw(gettimeofday);
22 $main::lxdebug->enter_sub();
29 $self->{SESSION} = { };
31 $self->_read_auth_config();
33 $main::lxdebug->leave_sub();
41 $self->{dbh}->disconnect() if ($self->{dbh});
44 sub _read_auth_config {
45 $main::lxdebug->enter_sub();
49 my $form = $main::form;
50 my $locale = $main::locale;
53 my $in = IO::File->new('config/authentication.pl', 'r');
56 $form->error($locale->text('The config file "config/authentication.pl" was not found.'));
67 $form->error($locale->text('The config file "config/authentication.pl" contained invalid Perl code:') . "\n" . $@);
70 if ($self->{module} eq 'DB') {
71 $self->{authenticator} = SL::Auth::DB->new($self);
73 } elsif ($self->{module} eq 'LDAP') {
74 $self->{authenticator} = SL::Auth::LDAP->new($self);
77 if (!$self->{authenticator}) {
78 $form->error($locale->text('No or an unknown authenticantion module specified in "config/authentication.pl".'));
81 my $cfg = $self->{DB_config};
84 $form->error($locale->text('config/authentication.pl: Key "DB_config" is missing.'));
87 if (!$cfg->{host} || !$cfg->{db} || !$cfg->{user}) {
88 $form->error($locale->text('config/authentication.pl: Missing parameters in "DB_config". Required parameters are "host", "db" and "user".'));
91 $self->{authenticator}->verify_config();
93 $self->{session_timeout} *= 1;
94 $self->{session_timeout} = 8 * 60 if (!$self->{session_timeout});
96 $main::lxdebug->leave_sub();
99 sub authenticate_root {
100 $main::lxdebug->enter_sub();
103 my $password = shift;
104 my $is_crypted = shift;
106 $password = crypt $password, 'ro' if (!$password || !$is_crypted);
107 my $admin_password = crypt "$self->{admin_password}", 'ro';
109 $main::lxdebug->leave_sub();
111 return $password eq $admin_password ? OK : ERR_PASSWORD;
115 $main::lxdebug->enter_sub();
119 $main::lxdebug->leave_sub();
121 return $self->{authenticator}->authenticate(@_);
125 $main::lxdebug->enter_sub(2);
128 my $may_fail = shift;
131 $main::lxdebug->leave_sub(2);
135 my $cfg = $self->{DB_config};
136 my $dsn = 'dbi:Pg:dbname=' . $cfg->{db} . ';host=' . $cfg->{host};
139 $dsn .= ';port=' . $cfg->{port};
142 $main::lxdebug->message(LXDebug::DEBUG1, "Auth::dbconnect DSN: $dsn");
144 $self->{dbh} = DBI->connect($dsn, $cfg->{user}, $cfg->{password}, { 'AutoCommit' => 0 });
146 if (!$may_fail && !$self->{dbh}) {
147 $main::form->error($main::locale->text('The connection to the authentication database failed:') . "\n" . $DBI::errstr);
150 $main::lxdebug->leave_sub();
156 $main::lxdebug->enter_sub();
161 $self->{dbh}->disconnect();
165 $main::lxdebug->leave_sub();
169 $main::lxdebug->enter_sub();
173 my $dbh = $self->dbconnect();
174 my $query = qq|SELECT COUNT(*) FROM pg_tables WHERE (schemaname = 'auth') AND (tablename = 'user')|;
176 my ($count) = $dbh->selectrow_array($query);
178 $main::lxdebug->leave_sub();
184 $main::lxdebug->enter_sub();
188 my $dbh = $self->dbconnect(1);
190 $main::lxdebug->leave_sub();
195 sub create_database {
196 $main::lxdebug->enter_sub();
201 my $cfg = $self->{DB_config};
203 if (!$params{superuser}) {
204 $params{superuser} = $cfg->{user};
205 $params{superuser_password} = $cfg->{password};
208 $params{template} ||= 'template0';
209 $params{template} =~ s|[^a-zA-Z0-9_\-]||g;
211 my $dsn = 'dbi:Pg:dbname=template1;host=' . $cfg->{host};
214 $dsn .= ';port=' . $cfg->{port};
217 $main::lxdebug->message(LXDebug::DEBUG1, "Auth::create_database DSN: $dsn");
219 my $dbh = DBI->connect($dsn, $params{superuser}, $params{superuser_password});
222 $main::form->error($main::locale->text('The connection to the template database failed:') . "\n" . $DBI::errstr);
225 my $charset = $main::dbcharset;
226 $charset ||= Common::DEFAULT_CHARSET;
227 my $encoding = $Common::charset_to_db_encoding{$charset};
228 $encoding ||= 'UNICODE';
230 my $query = qq|CREATE DATABASE "$cfg->{db}" OWNER "$cfg->{user}" TEMPLATE "$params{template}" ENCODING '$encoding'|;
232 $main::lxdebug->message(LXDebug::DEBUG1, "Auth::create_database query: $query");
237 my $error = $dbh->errstr();
239 $query = qq|SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = 'template0'|;
240 my ($cluster_encoding) = $dbh->selectrow_array($query);
242 if ($cluster_encoding && ($cluster_encoding =~ m/^(?:UTF-?8|UNICODE)$/i) && ($encoding !~ m/^(?:UTF-?8|UNICODE)$/i)) {
243 $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.');
248 $main::form->error($main::locale->text('The creation of the authentication database failed:') . "\n" . $error);
253 $main::lxdebug->leave_sub();
257 $main::lxdebug->enter_sub();
260 my $dbh = $self->dbconnect();
262 my $charset = $main::dbcharset;
263 $charset ||= Common::DEFAULT_CHARSET;
266 User->process_query($main::form, $dbh, 'sql/auth_db.sql', undef, $charset);
268 $main::lxdebug->leave_sub();
272 $main::lxdebug->enter_sub();
278 my $form = $main::form;
280 my $dbh = $self->dbconnect();
282 my ($sth, $query, $user_id);
284 $query = qq|SELECT id FROM auth."user" WHERE login = ?|;
285 ($user_id) = selectrow_query($form, $dbh, $query, $login);
288 $query = qq|SELECT nextval('auth.user_id_seq')|;
289 ($user_id) = selectrow_query($form, $dbh, $query);
291 $query = qq|INSERT INTO auth."user" (id, login) VALUES (?, ?)|;
292 do_query($form, $dbh, $query, $user_id, $login);
295 $query = qq|DELETE FROM auth.user_config WHERE (user_id = ?)|;
296 do_query($form, $dbh, $query, $user_id);
298 $query = qq|INSERT INTO auth.user_config (user_id, cfg_key, cfg_value) VALUES (?, ?, ?)|;
299 $sth = prepare_query($form, $dbh, $query);
301 while (my ($cfg_key, $cfg_value) = each %params) {
302 next if ($cfg_key eq 'password');
304 do_statement($form, $sth, $query, $user_id, $cfg_key, $cfg_value);
309 $main::lxdebug->leave_sub();
312 sub can_change_password {
315 return $self->{authenticator}->can_change_password();
318 sub change_password {
319 $main::lxdebug->enter_sub();
322 my $result = $self->{authenticator}->change_password(@_);
324 $main::lxdebug->leave_sub();
330 $main::lxdebug->enter_sub();
334 my $dbh = $self->dbconnect();
335 my $query = qq|SELECT u.id, u.login, cfg.cfg_key, cfg.cfg_value
336 FROM auth.user_config cfg
337 LEFT JOIN auth."user" u ON (cfg.user_id = u.id)|;
338 my $sth = prepare_execute_query($main::form, $dbh, $query);
342 while (my $ref = $sth->fetchrow_hashref()) {
343 $users{$ref->{login}} ||= { 'login' => $ref->{login}, 'id' => $ref->{id} };
344 $users{$ref->{login}}->{$ref->{cfg_key}} = $ref->{cfg_value} if (($ref->{cfg_key} ne 'login') && ($ref->{cfg_key} ne 'id'));
349 $main::lxdebug->leave_sub();
355 $main::lxdebug->enter_sub();
360 my $dbh = $self->dbconnect();
361 my $query = qq|SELECT u.id, u.login, cfg.cfg_key, cfg.cfg_value
362 FROM auth.user_config cfg
363 LEFT JOIN auth."user" u ON (cfg.user_id = u.id)
364 WHERE (u.login = ?)|;
365 my $sth = prepare_execute_query($main::form, $dbh, $query, $login);
369 while (my $ref = $sth->fetchrow_hashref()) {
370 $user_data{$ref->{cfg_key}} = $ref->{cfg_value};
371 @user_data{qw(id login)} = @{$ref}{qw(id login)};
376 $main::lxdebug->leave_sub();
382 $main::lxdebug->enter_sub();
387 my $dbh = $self->dbconnect();
388 my ($id) = selectrow_query($main::form, $dbh, qq|SELECT id FROM auth."user" WHERE login = ?|, $login);
390 $main::lxdebug->leave_sub();
396 $main::lxdebug->enter_sub();
401 my $form = $main::form;
403 my $dbh = $self->dbconnect();
404 my $query = qq|SELECT id FROM auth."user" WHERE login = ?|;
406 my ($id) = selectrow_query($form, $dbh, $query, $login);
408 return $main::lxdebug->leave_sub() if (!$id);
410 do_query($form, $dbh, qq|DELETE FROM auth.user_group WHERE user_id = ?|, $id);
411 do_query($form, $dbh, qq|DELETE FROM auth.user_config WHERE user_id = ?|, $id);
415 $main::lxdebug->leave_sub();
418 # --------------------------------------
422 sub restore_session {
423 $main::lxdebug->enter_sub();
427 my $cgi = $main::cgi;
428 $cgi ||= CGI->new('');
430 $session_id = $cgi->cookie($self->get_session_cookie_name());
431 $session_id =~ s|[^0-9a-f]||g;
433 $self->{SESSION} = { };
436 $main::lxdebug->leave_sub();
440 my ($dbh, $query, $sth, $cookie, $ref, $form);
444 $dbh = $self->dbconnect();
445 $query = qq|SELECT *, (mtime < (now() - '$self->{session_timeout}m'::interval)) AS is_expired FROM auth.session WHERE id = ?|;
447 $cookie = selectfirst_hashref_query($form, $dbh, $query, $session_id);
449 if (!$cookie || $cookie->{is_expired} || ($cookie->{ip_address} ne $ENV{REMOTE_ADDR})) {
450 $self->destroy_session();
451 $main::lxdebug->leave_sub();
452 return SESSION_EXPIRED;
455 $query = qq|SELECT sess_key, sess_value FROM auth.session_content WHERE session_id = ?|;
456 $sth = prepare_execute_query($form, $dbh, $query, $session_id);
458 while (my $ref = $sth->fetchrow_hashref()) {
459 $self->{SESSION}->{$ref->{sess_key}} = $ref->{sess_value};
460 $form->{$ref->{sess_key}} = $ref->{sess_value} if (!defined $form->{$ref->{sess_key}});
465 $main::lxdebug->leave_sub();
470 sub destroy_session {
471 $main::lxdebug->enter_sub();
476 my $dbh = $self->dbconnect();
478 do_query($main::form, $dbh, qq|DELETE FROM auth.session_content WHERE session_id = ?|, $session_id);
479 do_query($main::form, $dbh, qq|DELETE FROM auth.session WHERE id = ?|, $session_id);
484 $self->{SESSION} = { };
487 $main::lxdebug->leave_sub();
490 sub expire_sessions {
491 $main::lxdebug->enter_sub();
495 my $dbh = $self->dbconnect();
497 qq|DELETE FROM auth.session_content
501 WHERE (mtime < (now() - '$self->{session_timeout}m'::interval)))|;
503 do_query($main::form, $dbh, $query);
506 qq|DELETE FROM auth.session
507 WHERE (mtime < (now() - '$self->{session_timeout}m'::interval))|;
509 do_query($main::form, $dbh, $query);
513 $main::lxdebug->leave_sub();
516 sub _create_session_id {
517 $main::lxdebug->enter_sub();
519 my @secs = gettimeofday();
523 map { push @data, int(rand() * 255); } (1..32);
525 my $id = md5_hex(pack 'C*', @data);
527 $main::lxdebug->leave_sub();
532 sub create_or_refresh_session {
533 $main::lxdebug->enter_sub();
537 $session_id ||= $self->_create_session_id();
539 my ($form, $dbh, $query, $sth, $id);
542 $dbh = $self->dbconnect();
544 $query = qq|SELECT id FROM auth.session WHERE id = ?|;
546 ($id) = selectrow_query($form, $dbh, $query, $session_id);
549 do_query($form, $dbh, qq|UPDATE auth.session SET mtime = now() WHERE id = ?|, $session_id);
550 do_query($form, $dbh, qq|DELETE FROM auth.session_content WHERE session_id = ?|, $session_id);
553 do_query($form, $dbh, qq|INSERT INTO auth.session (id, ip_address, mtime) VALUES (?, ?, now())|, $session_id, $ENV{REMOTE_ADDR});
557 $query = qq|INSERT INTO auth.session_content (session_id, sess_key, sess_value) VALUES (?, ?, ?)|;
558 $sth = prepare_query($form, $dbh, $query);
560 foreach my $key (sort keys %{ $self->{SESSION} }) {
561 do_statement($form, $sth, $query, $session_id, $key, $self->{SESSION}->{$key});
567 $main::lxdebug->leave_sub();
570 sub set_session_value {
571 $main::lxdebug->enter_sub();
575 $self->{SESSION} ||= { };
577 while (2 <= scalar @_) {
581 $self->{SESSION}->{$key} = $value;
584 $main::lxdebug->leave_sub();
587 sub set_cookie_environment_variable {
589 $ENV{HTTP_COOKIE} = $self->get_session_cookie_name() . "=${session_id}";
592 sub get_session_cookie_name {
595 return $self->{cookie_name} || 'lx_office_erp_session_id';
602 sub session_tables_present {
603 $main::lxdebug->enter_sub();
606 my $dbh = $self->dbconnect(1);
609 $main::lxdebug->leave_sub();
616 WHERE (schemaname = 'auth')
617 AND (tablename IN ('session', 'session_content'))|;
619 my ($count) = selectrow_query($main::form, $dbh, $query);
621 $main::lxdebug->leave_sub();
626 # --------------------------------------
628 sub all_rights_full {
629 my $locale = $main::locale;
632 ["--crm", $locale->text("CRM optional software")],
633 ["crm_search", $locale->text("CRM search")],
634 ["crm_new", $locale->text("CRM create customers, vendors and contacts")],
635 ["crm_service", $locale->text("CRM services")],
636 ["crm_admin", $locale->text("CRM admin")],
637 ["crm_adminuser", $locale->text("CRM user")],
638 ["crm_adminstatus", $locale->text("CRM status")],
639 ["crm_email", $locale->text("CRM send email")],
640 ["crm_termin", $locale->text("CRM termin")],
641 ["crm_opportunity", $locale->text("CRM opportunity")],
642 ["crm_knowhow", $locale->text("CRM know how")],
643 ["crm_follow", $locale->text("CRM follow up")],
644 ["crm_notices", $locale->text("CRM notices")],
645 ["crm_other", $locale->text("CRM other")],
646 ["--master_data", $locale->text("Master Data")],
647 ["customer_vendor_edit", $locale->text("Create and edit customers and vendors")],
648 ["part_service_assembly_edit", $locale->text("Create and edit parts, services, assemblies")],
649 ["project_edit", $locale->text("Create and edit projects")],
650 ["license_edit", $locale->text("Manage license keys")],
651 ["--ar", $locale->text("AR")],
652 ["sales_quotation_edit", $locale->text("Create and edit sales quotations")],
653 ["sales_order_edit", $locale->text("Create and edit sales orders")],
654 ["sales_delivery_order_edit", $locale->text("Create and edit sales delivery orders")],
655 ["invoice_edit", $locale->text("Create and edit invoices and credit notes")],
656 ["dunning_edit", $locale->text("Create and edit dunnings")],
657 ["--ap", $locale->text("AP")],
658 ["request_quotation_edit", $locale->text("Create and edit RFQs")],
659 ["purchase_order_edit", $locale->text("Create and edit purchase orders")],
660 ["purchase_delivery_order_edit", $locale->text("Create and edit purchase delivery orders")],
661 ["vendor_invoice_edit", $locale->text("Create and edit vendor invoices")],
662 ["--warehouse_management", $locale->text("Warehouse management")],
663 ["warehouse_contents", $locale->text("View warehouse content")],
664 ["warehouse_management", $locale->text("Warehouse management")],
665 ["--general_ledger_cash", $locale->text("General ledger and cash")],
666 ["general_ledger", $locale->text("Transactions, AR transactions, AP transactions")],
667 ["datev_export", $locale->text("DATEV Export")],
668 ["cash", $locale->text("Receipt, payment, reconciliation")],
669 ["--reports", $locale->text('Reports')],
670 ["report", $locale->text('All reports')],
671 ["advance_turnover_tax_return", $locale->text('Advance turnover tax return')],
672 ["--others", $locale->text("Others")],
673 ["email_bcc", $locale->text("May set the BCC field when sending emails")],
674 ["config", $locale->text("Change Lx-Office installation settings (all menu entries beneath 'System')")],
681 return grep !/^--/, map { $_->[0] } all_rights_full();
685 $main::lxdebug->enter_sub();
689 my $form = $main::form;
691 my $dbh = $self->dbconnect();
693 my $query = 'SELECT * FROM auth."group"';
694 my $sth = prepare_execute_query($form, $dbh, $query);
698 while ($row = $sth->fetchrow_hashref()) {
699 $groups->{$row->{id}} = $row;
703 $query = 'SELECT * FROM auth.user_group WHERE group_id = ?';
704 $sth = prepare_query($form, $dbh, $query);
706 foreach $group (values %{$groups}) {
707 $group->{members} = [];
709 do_statement($form, $sth, $query, $group->{id});
711 while ($row = $sth->fetchrow_hashref()) {
712 push @{$group->{members}}, $row->{user_id};
717 $query = 'SELECT * FROM auth.group_rights WHERE group_id = ?';
718 $sth = prepare_query($form, $dbh, $query);
720 foreach $group (values %{$groups}) {
721 $group->{rights} = {};
723 do_statement($form, $sth, $query, $group->{id});
725 while ($row = $sth->fetchrow_hashref()) {
726 $group->{rights}->{$row->{right}} |= $row->{granted};
729 map { $group->{rights}->{$_} = 0 if (!defined $group->{rights}->{$_}); } all_rights();
733 $main::lxdebug->leave_sub();
739 $main::lxdebug->enter_sub();
744 my $form = $main::form;
745 my $dbh = $self->dbconnect();
747 my ($query, $sth, $row, $rights);
750 ($group->{id}) = selectrow_query($form, $dbh, qq|SELECT nextval('auth.group_id_seq')|);
752 $query = qq|INSERT INTO auth."group" (id, name, description) VALUES (?, '', '')|;
753 do_query($form, $dbh, $query, $group->{id});
756 do_query($form, $dbh, qq|UPDATE auth."group" SET name = ?, description = ? WHERE id = ?|, map { $group->{$_} } qw(name description id));
758 do_query($form, $dbh, qq|DELETE FROM auth.user_group WHERE group_id = ?|, $group->{id});
760 $query = qq|INSERT INTO auth.user_group (user_id, group_id) VALUES (?, ?)|;
761 $sth = prepare_query($form, $dbh, $query);
763 foreach my $user_id (@{ $group->{members} }) {
764 do_statement($form, $sth, $query, $user_id, $group->{id});
768 do_query($form, $dbh, qq|DELETE FROM auth.group_rights WHERE group_id = ?|, $group->{id});
770 $query = qq|INSERT INTO auth.group_rights (group_id, "right", granted) VALUES (?, ?, ?)|;
771 $sth = prepare_query($form, $dbh, $query);
773 foreach my $right (keys %{ $group->{rights} }) {
774 do_statement($form, $sth, $query, $group->{id}, $right, $group->{rights}->{$right} ? 't' : 'f');
780 $main::lxdebug->leave_sub();
784 $main::lxdebug->enter_sub();
789 my $form = $main::from;
791 my $dbh = $self->dbconnect();
793 do_query($form, $dbh, qq|DELETE FROM auth.user_group WHERE group_id = ?|, $id);
794 do_query($form, $dbh, qq|DELETE FROM auth.group_rights WHERE group_id = ?|, $id);
795 do_query($form, $dbh, qq|DELETE FROM auth."group" WHERE id = ?|, $id);
799 $main::lxdebug->leave_sub();
802 sub evaluate_rights_ary {
803 $main::lxdebug->enter_sub(2);
810 foreach my $el (@{$ary}) {
811 if (ref $el eq "ARRAY") {
812 if ($action eq '|') {
813 $value |= evaluate_rights_ary($el);
815 $value &= evaluate_rights_ary($el);
818 } elsif (($el eq '&') || ($el eq '|')) {
821 } elsif ($action eq '|') {
830 $main::lxdebug->enter_sub(2);
835 sub _parse_rights_string {
836 $main::lxdebug->enter_sub(2);
846 push @stack, $cur_ary;
848 while ($access =~ m/^([a-z_0-9]+|\||\&|\(|\)|\s+)/) {
850 substr($access, 0, length $1) = "";
852 next if ($token =~ /\s/);
855 my $new_cur_ary = [];
856 push @stack, $new_cur_ary;
857 push @{$cur_ary}, $new_cur_ary;
858 $cur_ary = $new_cur_ary;
860 } elsif ($token eq ")") {
864 $main::lxdebug->enter_sub(2);
868 $cur_ary = $stack[-1];
870 } elsif (($token eq "|") || ($token eq "&")) {
871 push @{$cur_ary}, $token;
874 push @{$cur_ary}, $self->{RIGHTS}->{$login}->{$token} * 1;
878 my $result = ($access || (1 < scalar @stack)) ? 0 : evaluate_rights_ary($stack[0]);
880 $main::lxdebug->enter_sub(2);
886 $main::lxdebug->enter_sub(2);
893 $self->{FULL_RIGHTS} ||= { };
894 $self->{FULL_RIGHTS}->{$login} ||= { };
896 if (!defined $self->{FULL_RIGHTS}->{$login}->{$right}) {
897 $self->{RIGHTS} ||= { };
898 $self->{RIGHTS}->{$login} ||= $self->load_rights_for_user($login);
900 $self->{FULL_RIGHTS}->{$login}->{$right} = $self->_parse_rights_string($login, $right);
903 my $granted = $self->{FULL_RIGHTS}->{$login}->{$right};
904 $granted = $default if (!defined $granted);
906 $main::lxdebug->leave_sub(2);
912 $main::lxdebug->enter_sub(2);
916 my $dont_abort = shift;
918 my $form = $main::form;
920 if ($self->check_right($form->{login}, $right)) {
921 $main::lxdebug->leave_sub(2);
926 delete $form->{title};
927 $form->show_generic_error($main::locale->text("You do not have the permissions to access this function."));
930 $main::lxdebug->leave_sub(2);
935 sub load_rights_for_user {
936 $main::lxdebug->enter_sub();
941 my $form = $main::form;
942 my $dbh = $self->dbconnect();
944 my ($query, $sth, $row, $rights);
949 qq|SELECT gr."right", gr.granted
950 FROM auth.group_rights gr
953 FROM auth.user_group ug
954 LEFT JOIN auth."user" u ON (ug.user_id = u.id)
957 $sth = prepare_execute_query($form, $dbh, $query, $login);
959 while ($row = $sth->fetchrow_hashref()) {
960 $rights->{$row->{right}} |= $row->{granted};
964 map({ $rights->{$_} = 0 unless (defined $rights->{$_}); } SL::Auth::all_rights());
966 $main::lxdebug->leave_sub();