]> wagnertech.de Git - kivitendo-erp.git/blob - SL/Auth.pm
Konfigurationsdatei config/authentication.pl nach config/lx_office.conf(.default...
[kivitendo-erp.git] / SL / Auth.pm
1 package SL::Auth;
2
3 use DBI;
4
5 use Digest::MD5 qw(md5_hex);
6 use IO::File;
7 use Time::HiRes qw(gettimeofday);
8 use List::MoreUtils qw(uniq);
9 use YAML;
10
11 use SL::Auth::Constants qw(:all);
12 use SL::Auth::DB;
13 use SL::Auth::LDAP;
14
15 use SL::User;
16 use SL::DBUpgrade2;
17 use SL::DBUtils;
18
19 use strict;
20
21 sub new {
22   $main::lxdebug->enter_sub();
23
24   my $type = shift;
25   my $self = {};
26
27   bless $self, $type;
28
29   $self->{SESSION} = { };
30
31   $self->_read_auth_config();
32
33   $main::lxdebug->leave_sub();
34
35   return $self;
36 }
37
38 sub get_user_dbh {
39   my ($self, $login) = @_;
40   my %user = $self->read_user($login);
41   my $dbh  = DBI->connect(
42     $user{dbconnect},
43     $user{dbuser},
44     $user{dbpasswd},
45     {
46       pg_enable_utf8 => $::locale->is_utf8,
47       AutoCommit     => 0
48     }
49   ) or $::form->dberror;
50
51   if ($user{dboptions}) {
52     $dbh->do($user{dboptions}) or $::form->dberror($user{dboptions});
53   }
54
55   return $dbh;
56 }
57
58 sub DESTROY {
59   my $self = shift;
60
61   $self->{dbh}->disconnect() if ($self->{dbh});
62 }
63
64 # form isn't loaded yet, so auth needs it's own error.
65 sub mini_error {
66   $::lxdebug->show_backtrace();
67
68   my ($self, @msg) = @_;
69   if ($ENV{HTTP_USER_AGENT}) {
70     print Form->create_http_response(content_type => 'text/html');
71     print "<pre>", join ('<br>', @msg), "</pre>";
72   } else {
73     print STDERR "Error: @msg\n";
74   }
75   ::end_of_request();
76 }
77
78 sub _read_auth_config {
79   $main::lxdebug->enter_sub();
80
81   my $self = shift;
82
83   map { $self->{$_} = $::lx_office_conf{authentication}->{$_} } keys %{ $::lx_office_conf{authentication} };
84   $self->{DB_config}   = $::lx_office_conf{'authentication/database'};
85   $self->{LDAP_config} = $::lx_office_conf{'authentication/ldap'};
86
87   if ($self->{module} eq 'DB') {
88     $self->{authenticator} = SL::Auth::DB->new($self);
89
90   } elsif ($self->{module} eq 'LDAP') {
91     $self->{authenticator} = SL::Auth::LDAP->new($self);
92   }
93
94   if (!$self->{authenticator}) {
95     my $locale = Locale->new('en');
96     $self->mini_error($locale->text('No or an unknown authenticantion module specified in "config/lx_office.conf".'));
97   }
98
99   my $cfg = $self->{DB_config};
100
101   if (!$cfg) {
102     my $locale = Locale->new('en');
103     $self->mini_error($locale->text('config/lx_office.conf: Key "DB_config" is missing.'));
104   }
105
106   if (!$cfg->{host} || !$cfg->{db} || !$cfg->{user}) {
107     my $locale = Locale->new('en');
108     $self->mini_error($locale->text('config/lx_office.conf: Missing parameters in "authentication/database". Required parameters are "host", "db" and "user".'));
109   }
110
111   $self->{authenticator}->verify_config();
112
113   $self->{session_timeout} *= 1;
114   $self->{session_timeout}  = 8 * 60 if (!$self->{session_timeout});
115
116   $main::lxdebug->leave_sub();
117 }
118
119 sub authenticate_root {
120   $main::lxdebug->enter_sub();
121
122   my $self           = shift;
123   my $password       = shift;
124   my $is_crypted     = shift;
125
126   $password          = crypt $password, 'ro' if (!$password || !$is_crypted);
127   my $admin_password = crypt "$self->{admin_password}", 'ro';
128
129   $main::lxdebug->leave_sub();
130
131   return OK if $password eq $admin_password;
132   sleep 5;
133   return ERR_PASSWORD;
134 }
135
136 sub authenticate {
137   $main::lxdebug->enter_sub();
138
139   my $self = shift;
140
141   $main::lxdebug->leave_sub();
142
143   my $result = $self->{authenticator}->authenticate(@_);
144   return OK if $result eq OK;
145   sleep 5;
146   return $result;
147 }
148
149 sub dbconnect {
150   $main::lxdebug->enter_sub(2);
151
152   my $self     = shift;
153   my $may_fail = shift;
154
155   if ($self->{dbh}) {
156     $main::lxdebug->leave_sub(2);
157     return $self->{dbh};
158   }
159
160   my $cfg = $self->{DB_config};
161   my $dsn = 'dbi:Pg:dbname=' . $cfg->{db} . ';host=' . $cfg->{host};
162
163   if ($cfg->{port}) {
164     $dsn .= ';port=' . $cfg->{port};
165   }
166
167   $main::lxdebug->message(LXDebug->DEBUG1, "Auth::dbconnect DSN: $dsn");
168
169   $self->{dbh} = DBI->connect($dsn, $cfg->{user}, $cfg->{password}, { pg_enable_utf8 => $::locale->is_utf8, AutoCommit => 0 });
170
171   if (!$may_fail && !$self->{dbh}) {
172     $main::form->error($main::locale->text('The connection to the authentication database failed:') . "\n" . $DBI::errstr);
173   }
174
175   $main::lxdebug->leave_sub(2);
176
177   return $self->{dbh};
178 }
179
180 sub dbdisconnect {
181   $main::lxdebug->enter_sub();
182
183   my $self = shift;
184
185   if ($self->{dbh}) {
186     $self->{dbh}->disconnect();
187     delete $self->{dbh};
188   }
189
190   $main::lxdebug->leave_sub();
191 }
192
193 sub check_tables {
194   $main::lxdebug->enter_sub();
195
196   my $self    = shift;
197
198   my $dbh     = $self->dbconnect();
199   my $query   = qq|SELECT COUNT(*) FROM pg_tables WHERE (schemaname = 'auth') AND (tablename = 'user')|;
200
201   my ($count) = $dbh->selectrow_array($query);
202
203   $main::lxdebug->leave_sub();
204
205   return $count > 0;
206 }
207
208 sub check_database {
209   $main::lxdebug->enter_sub();
210
211   my $self = shift;
212
213   my $dbh  = $self->dbconnect(1);
214
215   $main::lxdebug->leave_sub();
216
217   return $dbh ? 1 : 0;
218 }
219
220 sub create_database {
221   $main::lxdebug->enter_sub();
222
223   my $self   = shift;
224   my %params = @_;
225
226   my $cfg    = $self->{DB_config};
227
228   if (!$params{superuser}) {
229     $params{superuser}          = $cfg->{user};
230     $params{superuser_password} = $cfg->{password};
231   }
232
233   $params{template} ||= 'template0';
234   $params{template}   =~ s|[^a-zA-Z0-9_\-]||g;
235
236   my $dsn = 'dbi:Pg:dbname=template1;host=' . $cfg->{host};
237
238   if ($cfg->{port}) {
239     $dsn .= ';port=' . $cfg->{port};
240   }
241
242   $main::lxdebug->message(LXDebug->DEBUG1(), "Auth::create_database DSN: $dsn");
243
244   my $charset    = $::lx_office_conf{system}->{dbcharset};
245   $charset     ||= Common::DEFAULT_CHARSET;
246   my $encoding   = $Common::charset_to_db_encoding{$charset};
247   $encoding    ||= 'UNICODE';
248
249   my $dbh        = DBI->connect($dsn, $params{superuser}, $params{superuser_password}, { pg_enable_utf8 => $charset =~ m/^utf-?8$/i });
250
251   if (!$dbh) {
252     $main::form->error($main::locale->text('The connection to the template database failed:') . "\n" . $DBI::errstr);
253   }
254
255   my $query = qq|CREATE DATABASE "$cfg->{db}" OWNER "$cfg->{user}" TEMPLATE "$params{template}" ENCODING '$encoding'|;
256
257   $main::lxdebug->message(LXDebug->DEBUG1(), "Auth::create_database query: $query");
258
259   $dbh->do($query);
260
261   if ($dbh->err) {
262     my $error = $dbh->errstr();
263
264     $query                 = qq|SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = 'template0'|;
265     my ($cluster_encoding) = $dbh->selectrow_array($query);
266
267     if ($cluster_encoding && ($cluster_encoding =~ m/^(?:UTF-?8|UNICODE)$/i) && ($encoding !~ m/^(?:UTF-?8|UNICODE)$/i)) {
268       $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.');
269     }
270
271     $dbh->disconnect();
272
273     $main::form->error($main::locale->text('The creation of the authentication database failed:') . "\n" . $error);
274   }
275
276   $dbh->disconnect();
277
278   $main::lxdebug->leave_sub();
279 }
280
281 sub create_tables {
282   $main::lxdebug->enter_sub();
283
284   my $self = shift;
285   my $dbh  = $self->dbconnect();
286
287   my $charset    = $::lx_office_conf{system}->{dbcharset};
288   $charset     ||= Common::DEFAULT_CHARSET;
289
290   $dbh->rollback();
291   SL::DBUpgrade2->new(form => $::form)->process_query($dbh, 'sql/auth_db.sql', undef, $charset);
292
293   $main::lxdebug->leave_sub();
294 }
295
296 sub save_user {
297   $main::lxdebug->enter_sub();
298
299   my $self   = shift;
300   my $login  = shift;
301   my %params = @_;
302
303   my $form   = $main::form;
304
305   my $dbh    = $self->dbconnect();
306
307   my ($sth, $query, $user_id);
308
309   $query     = qq|SELECT id FROM auth."user" WHERE login = ?|;
310   ($user_id) = selectrow_query($form, $dbh, $query, $login);
311
312   if (!$user_id) {
313     $query     = qq|SELECT nextval('auth.user_id_seq')|;
314     ($user_id) = selectrow_query($form, $dbh, $query);
315
316     $query     = qq|INSERT INTO auth."user" (id, login) VALUES (?, ?)|;
317     do_query($form, $dbh, $query, $user_id, $login);
318   }
319
320   $query = qq|DELETE FROM auth.user_config WHERE (user_id = ?)|;
321   do_query($form, $dbh, $query, $user_id);
322
323   $query = qq|INSERT INTO auth.user_config (user_id, cfg_key, cfg_value) VALUES (?, ?, ?)|;
324   $sth   = prepare_query($form, $dbh, $query);
325
326   while (my ($cfg_key, $cfg_value) = each %params) {
327     next if ($cfg_key eq 'password');
328
329     do_statement($form, $sth, $query, $user_id, $cfg_key, $cfg_value);
330   }
331
332   $dbh->commit();
333
334   $main::lxdebug->leave_sub();
335 }
336
337 sub can_change_password {
338   my $self = shift;
339
340   return $self->{authenticator}->can_change_password();
341 }
342
343 sub change_password {
344   $main::lxdebug->enter_sub();
345
346   my $self   = shift;
347   my $result = $self->{authenticator}->change_password(@_);
348
349   $main::lxdebug->leave_sub();
350
351   return $result;
352 }
353
354 sub read_all_users {
355   $main::lxdebug->enter_sub();
356
357   my $self  = shift;
358
359   my $dbh   = $self->dbconnect();
360   my $query = qq|SELECT u.id, u.login, cfg.cfg_key, cfg.cfg_value
361                  FROM auth.user_config cfg
362                  LEFT JOIN auth."user" u ON (cfg.user_id = u.id)|;
363   my $sth   = prepare_execute_query($main::form, $dbh, $query);
364
365   my %users;
366
367   while (my $ref = $sth->fetchrow_hashref()) {
368     $users{$ref->{login}}                    ||= { 'login' => $ref->{login}, 'id' => $ref->{id} };
369     $users{$ref->{login}}->{$ref->{cfg_key}}   = $ref->{cfg_value} if (($ref->{cfg_key} ne 'login') && ($ref->{cfg_key} ne 'id'));
370   }
371
372   $sth->finish();
373
374   $main::lxdebug->leave_sub();
375
376   return %users;
377 }
378
379 sub read_user {
380   $main::lxdebug->enter_sub();
381
382   my $self  = shift;
383   my $login = shift;
384
385   my $dbh   = $self->dbconnect();
386   my $query = qq|SELECT u.id, u.login, cfg.cfg_key, cfg.cfg_value
387                  FROM auth.user_config cfg
388                  LEFT JOIN auth."user" u ON (cfg.user_id = u.id)
389                  WHERE (u.login = ?)|;
390   my $sth   = prepare_execute_query($main::form, $dbh, $query, $login);
391
392   my %user_data;
393
394   while (my $ref = $sth->fetchrow_hashref()) {
395     $user_data{$ref->{cfg_key}} = $ref->{cfg_value};
396     @user_data{qw(id login)}    = @{$ref}{qw(id login)};
397   }
398
399   $sth->finish();
400
401   $main::lxdebug->leave_sub();
402
403   return %user_data;
404 }
405
406 sub get_user_id {
407   $main::lxdebug->enter_sub();
408
409   my $self  = shift;
410   my $login = shift;
411
412   my $dbh   = $self->dbconnect();
413   my ($id)  = selectrow_query($main::form, $dbh, qq|SELECT id FROM auth."user" WHERE login = ?|, $login);
414
415   $main::lxdebug->leave_sub();
416
417   return $id;
418 }
419
420 sub delete_user {
421   $main::lxdebug->enter_sub();
422
423   my $self  = shift;
424   my $login = shift;
425
426   my $form  = $main::form;
427
428   my $dbh   = $self->dbconnect();
429   my $query = qq|SELECT id FROM auth."user" WHERE login = ?|;
430
431   my ($id)  = selectrow_query($form, $dbh, $query, $login);
432
433   return $main::lxdebug->leave_sub() if (!$id);
434
435   do_query($form, $dbh, qq|DELETE FROM auth.user_group WHERE user_id = ?|, $id);
436   do_query($form, $dbh, qq|DELETE FROM auth.user_config WHERE user_id = ?|, $id);
437
438   $dbh->commit();
439
440   $main::lxdebug->leave_sub();
441 }
442
443 # --------------------------------------
444
445 my $session_id;
446
447 sub restore_session {
448   $main::lxdebug->enter_sub();
449
450   my $self = shift;
451
452   my $cgi            =  $main::cgi;
453   $cgi             ||=  CGI->new('');
454
455   $session_id        =  $cgi->cookie($self->get_session_cookie_name());
456   $session_id        =~ s|[^0-9a-f]||g;
457
458   $self->{SESSION}   = { };
459
460   if (!$session_id) {
461     $main::lxdebug->leave_sub();
462     return SESSION_NONE;
463   }
464
465   my ($dbh, $query, $sth, $cookie, $ref, $form);
466
467   $form   = $main::form;
468
469   $dbh    = $self->dbconnect();
470   $query  = qq|SELECT *, (mtime < (now() - '$self->{session_timeout}m'::interval)) AS is_expired FROM auth.session WHERE id = ?|;
471
472   $cookie = selectfirst_hashref_query($form, $dbh, $query, $session_id);
473
474   if (!$cookie || $cookie->{is_expired} || ($cookie->{ip_address} ne $ENV{REMOTE_ADDR})) {
475     $self->destroy_session();
476     $main::lxdebug->leave_sub();
477     return $cookie ? SESSION_EXPIRED : SESSION_NONE;
478   }
479
480   $query = qq|SELECT sess_key, sess_value FROM auth.session_content WHERE session_id = ?|;
481   $sth   = prepare_execute_query($form, $dbh, $query, $session_id);
482
483   while (my $ref = $sth->fetchrow_hashref()) {
484     $self->{SESSION}->{$ref->{sess_key}} = $ref->{sess_value};
485     $form->{$ref->{sess_key}}            = $self->_load_value($ref->{sess_value}) if (!defined $form->{$ref->{sess_key}});
486   }
487
488   $sth->finish();
489
490   $main::lxdebug->leave_sub();
491
492   return SESSION_OK;
493 }
494
495 sub _load_value {
496   return $_[1] if $_[1] !~ m/^---/;
497
498   my $value;
499   eval {
500     $value = YAML::Load($_[1]);
501     1;
502   } or return $_[1];
503
504   return $value;
505 }
506
507 sub destroy_session {
508   $main::lxdebug->enter_sub();
509
510   my $self = shift;
511
512   if ($session_id) {
513     my $dbh = $self->dbconnect();
514
515     do_query($main::form, $dbh, qq|DELETE FROM auth.session_content WHERE session_id = ?|, $session_id);
516     do_query($main::form, $dbh, qq|DELETE FROM auth.session WHERE id = ?|, $session_id);
517
518     $dbh->commit();
519
520     $session_id      = undef;
521     $self->{SESSION} = { };
522   }
523
524   $main::lxdebug->leave_sub();
525 }
526
527 sub expire_sessions {
528   $main::lxdebug->enter_sub();
529
530   my $self  = shift;
531
532   my $dbh   = $self->dbconnect();
533   my $query =
534     qq|DELETE FROM auth.session_content
535        WHERE session_id IN
536          (SELECT id
537           FROM auth.session
538           WHERE (mtime < (now() - '$self->{session_timeout}m'::interval)))|;
539
540   do_query($main::form, $dbh, $query);
541
542   $query =
543     qq|DELETE FROM auth.session
544        WHERE (mtime < (now() - '$self->{session_timeout}m'::interval))|;
545
546   do_query($main::form, $dbh, $query);
547
548   $dbh->commit();
549
550   $main::lxdebug->leave_sub();
551 }
552
553 sub _create_session_id {
554   $main::lxdebug->enter_sub();
555
556   my @data;
557   map { push @data, int(rand() * 255); } (1..32);
558
559   my $id = md5_hex(pack 'C*', @data);
560
561   $main::lxdebug->leave_sub();
562
563   return $id;
564 }
565
566 sub create_or_refresh_session {
567   $main::lxdebug->enter_sub();
568
569   my $self = shift;
570
571   $session_id ||= $self->_create_session_id();
572
573   my ($form, $dbh, $query, $sth, $id);
574
575   $form  = $main::form;
576   $dbh   = $self->dbconnect();
577
578   $query = qq|SELECT id FROM auth.session WHERE id = ?|;
579
580   ($id)  = selectrow_query($form, $dbh, $query, $session_id);
581
582   if ($id) {
583     do_query($form, $dbh, qq|UPDATE auth.session SET mtime = now() WHERE id = ?|, $session_id);
584
585   } else {
586     do_query($form, $dbh, qq|INSERT INTO auth.session (id, ip_address, mtime) VALUES (?, ?, now())|, $session_id, $ENV{REMOTE_ADDR});
587
588   }
589
590   $self->save_session($dbh);
591
592   $dbh->commit();
593
594   $main::lxdebug->leave_sub();
595 }
596
597 sub save_session {
598   my $self         = shift;
599   my $provided_dbh = shift;
600
601   my $dbh          = $provided_dbh || $self->dbconnect();
602
603   do_query($::form, $dbh, qq|DELETE FROM auth.session_content WHERE session_id = ?|, $session_id);
604
605   if (%{ $self->{SESSION} }) {
606     my $query = qq|INSERT INTO auth.session_content (session_id, sess_key, sess_value) VALUES (?, ?, ?)|;
607     my $sth   = prepare_query($::form, $dbh, $query);
608
609     foreach my $key (sort keys %{ $self->{SESSION} }) {
610       do_statement($::form, $sth, $query, $session_id, $key, $self->{SESSION}->{$key});
611     }
612
613     $sth->finish();
614   }
615
616   $dbh->commit() unless $provided_dbh;
617 }
618
619 sub set_session_value {
620   $main::lxdebug->enter_sub();
621
622   my $self   = shift;
623   my %params = @_;
624
625   $self->{SESSION} ||= { };
626
627   while (my ($key, $value) = each %params) {
628     $self->{SESSION}->{ $key } = YAML::Dump($value);
629   }
630
631   $main::lxdebug->leave_sub();
632
633   return $self;
634 }
635
636 sub delete_session_value {
637   $main::lxdebug->enter_sub();
638
639   my $self = shift;
640
641   $self->{SESSION} ||= { };
642   delete @{ $self->{SESSION} }{ @_ };
643
644   $main::lxdebug->leave_sub();
645
646   return $self;
647 }
648
649 sub get_session_value {
650   $main::lxdebug->enter_sub();
651
652   my $self  = shift;
653   my $value = $self->{SESSION} ? $self->_load_value($self->{SESSION}->{ $_[0] }) : undef;
654
655   $main::lxdebug->leave_sub();
656
657   return $value;
658 }
659
660 sub set_cookie_environment_variable {
661   my $self = shift;
662   $ENV{HTTP_COOKIE} = $self->get_session_cookie_name() . "=${session_id}";
663 }
664
665 sub get_session_cookie_name {
666   my $self = shift;
667
668   return $self->{cookie_name} || 'lx_office_erp_session_id';
669 }
670
671 sub get_session_id {
672   return $session_id;
673 }
674
675 sub session_tables_present {
676   $main::lxdebug->enter_sub();
677
678   my $self = shift;
679   my $dbh  = $self->dbconnect(1);
680
681   if (!$dbh) {
682     $main::lxdebug->leave_sub();
683     return 0;
684   }
685
686   my $query =
687     qq|SELECT COUNT(*)
688        FROM pg_tables
689        WHERE (schemaname = 'auth')
690          AND (tablename IN ('session', 'session_content'))|;
691
692   my ($count) = selectrow_query($main::form, $dbh, $query);
693
694   $main::lxdebug->leave_sub();
695
696   return 2 == $count;
697 }
698
699 # --------------------------------------
700
701 sub all_rights_full {
702   my $locale = $main::locale;
703
704   my @all_rights = (
705     ["--crm",                          $locale->text("CRM optional software")],
706     ["crm_search",                     $locale->text("CRM search")],
707     ["crm_new",                        $locale->text("CRM create customers, vendors and contacts")],
708     ["crm_service",                    $locale->text("CRM services")],
709     ["crm_admin",                      $locale->text("CRM admin")],
710     ["crm_adminuser",                  $locale->text("CRM user")],
711     ["crm_adminstatus",                $locale->text("CRM status")],
712     ["crm_email",                      $locale->text("CRM send email")],
713     ["crm_termin",                     $locale->text("CRM termin")],
714     ["crm_opportunity",                $locale->text("CRM opportunity")],
715     ["crm_knowhow",                    $locale->text("CRM know how")],
716     ["crm_follow",                     $locale->text("CRM follow up")],
717     ["crm_notices",                    $locale->text("CRM notices")],
718     ["crm_other",                      $locale->text("CRM other")],
719     ["--master_data",                  $locale->text("Master Data")],
720     ["customer_vendor_edit",           $locale->text("Create and edit customers and vendors")],
721     ["part_service_assembly_edit",     $locale->text("Create and edit parts, services, assemblies")],
722     ["project_edit",                   $locale->text("Create and edit projects")],
723     ["license_edit",                   $locale->text("Manage license keys")],
724     ["--ar",                           $locale->text("AR")],
725     ["sales_quotation_edit",           $locale->text("Create and edit sales quotations")],
726     ["sales_order_edit",               $locale->text("Create and edit sales orders")],
727     ["sales_delivery_order_edit",      $locale->text("Create and edit sales delivery orders")],
728     ["invoice_edit",                   $locale->text("Create and edit invoices and credit notes")],
729     ["dunning_edit",                   $locale->text("Create and edit dunnings")],
730     ["sales_all_edit",                 $locale->text("View/edit all employees sales documents")],
731     ["--ap",                           $locale->text("AP")],
732     ["request_quotation_edit",         $locale->text("Create and edit RFQs")],
733     ["purchase_order_edit",            $locale->text("Create and edit purchase orders")],
734     ["purchase_delivery_order_edit",   $locale->text("Create and edit purchase delivery orders")],
735     ["vendor_invoice_edit",            $locale->text("Create and edit vendor invoices")],
736     ["--warehouse_management",         $locale->text("Warehouse management")],
737     ["warehouse_contents",             $locale->text("View warehouse content")],
738     ["warehouse_management",           $locale->text("Warehouse management")],
739     ["--general_ledger_cash",          $locale->text("General ledger and cash")],
740     ["general_ledger",                 $locale->text("Transactions, AR transactions, AP transactions")],
741     ["datev_export",                   $locale->text("DATEV Export")],
742     ["cash",                           $locale->text("Receipt, payment, reconciliation")],
743     ["--reports",                      $locale->text('Reports')],
744     ["report",                         $locale->text('All reports')],
745     ["advance_turnover_tax_return",    $locale->text('Advance turnover tax return')],
746     ["--batch_printing",               $locale->text("Batch Printing")],
747     ["batch_printing",                 $locale->text("Batch Printing")],
748     ["--others",                       $locale->text("Others")],
749     ["email_bcc",                      $locale->text("May set the BCC field when sending emails")],
750     ["config",                         $locale->text("Change Lx-Office installation settings (all menu entries beneath 'System')")],
751     );
752
753   return @all_rights;
754 }
755
756 sub all_rights {
757   return grep !/^--/, map { $_->[0] } all_rights_full();
758 }
759
760 sub read_groups {
761   $main::lxdebug->enter_sub();
762
763   my $self = shift;
764
765   my $form   = $main::form;
766   my $groups = {};
767   my $dbh    = $self->dbconnect();
768
769   my $query  = 'SELECT * FROM auth."group"';
770   my $sth    = prepare_execute_query($form, $dbh, $query);
771
772   my ($row, $group);
773
774   while ($row = $sth->fetchrow_hashref()) {
775     $groups->{$row->{id}} = $row;
776   }
777   $sth->finish();
778
779   $query = 'SELECT * FROM auth.user_group WHERE group_id = ?';
780   $sth   = prepare_query($form, $dbh, $query);
781
782   foreach $group (values %{$groups}) {
783     my @members;
784
785     do_statement($form, $sth, $query, $group->{id});
786
787     while ($row = $sth->fetchrow_hashref()) {
788       push @members, $row->{user_id};
789     }
790     $group->{members} = [ uniq @members ];
791   }
792   $sth->finish();
793
794   $query = 'SELECT * FROM auth.group_rights WHERE group_id = ?';
795   $sth   = prepare_query($form, $dbh, $query);
796
797   foreach $group (values %{$groups}) {
798     $group->{rights} = {};
799
800     do_statement($form, $sth, $query, $group->{id});
801
802     while ($row = $sth->fetchrow_hashref()) {
803       $group->{rights}->{$row->{right}} |= $row->{granted};
804     }
805
806     map { $group->{rights}->{$_} = 0 if (!defined $group->{rights}->{$_}); } all_rights();
807   }
808   $sth->finish();
809
810   $main::lxdebug->leave_sub();
811
812   return $groups;
813 }
814
815 sub save_group {
816   $main::lxdebug->enter_sub();
817
818   my $self  = shift;
819   my $group = shift;
820
821   my $form  = $main::form;
822   my $dbh   = $self->dbconnect();
823
824   my ($query, $sth, $row, $rights);
825
826   if (!$group->{id}) {
827     ($group->{id}) = selectrow_query($form, $dbh, qq|SELECT nextval('auth.group_id_seq')|);
828
829     $query = qq|INSERT INTO auth."group" (id, name, description) VALUES (?, '', '')|;
830     do_query($form, $dbh, $query, $group->{id});
831   }
832
833   do_query($form, $dbh, qq|UPDATE auth."group" SET name = ?, description = ? WHERE id = ?|, map { $group->{$_} } qw(name description id));
834
835   do_query($form, $dbh, qq|DELETE FROM auth.user_group WHERE group_id = ?|, $group->{id});
836
837   $query  = qq|INSERT INTO auth.user_group (user_id, group_id) VALUES (?, ?)|;
838   $sth    = prepare_query($form, $dbh, $query);
839
840   foreach my $user_id (uniq @{ $group->{members} }) {
841     do_statement($form, $sth, $query, $user_id, $group->{id});
842   }
843   $sth->finish();
844
845   do_query($form, $dbh, qq|DELETE FROM auth.group_rights WHERE group_id = ?|, $group->{id});
846
847   $query = qq|INSERT INTO auth.group_rights (group_id, "right", granted) VALUES (?, ?, ?)|;
848   $sth   = prepare_query($form, $dbh, $query);
849
850   foreach my $right (keys %{ $group->{rights} }) {
851     do_statement($form, $sth, $query, $group->{id}, $right, $group->{rights}->{$right} ? 't' : 'f');
852   }
853   $sth->finish();
854
855   $dbh->commit();
856
857   $main::lxdebug->leave_sub();
858 }
859
860 sub delete_group {
861   $main::lxdebug->enter_sub();
862
863   my $self = shift;
864   my $id   = shift;
865
866   my $form = $main::from;
867
868   my $dbh  = $self->dbconnect();
869
870   do_query($form, $dbh, qq|DELETE FROM auth.user_group WHERE group_id = ?|, $id);
871   do_query($form, $dbh, qq|DELETE FROM auth.group_rights WHERE group_id = ?|, $id);
872   do_query($form, $dbh, qq|DELETE FROM auth."group" WHERE id = ?|, $id);
873
874   $dbh->commit();
875
876   $main::lxdebug->leave_sub();
877 }
878
879 sub evaluate_rights_ary {
880   $main::lxdebug->enter_sub(2);
881
882   my $ary    = shift;
883
884   my $value  = 0;
885   my $action = '|';
886
887   foreach my $el (@{$ary}) {
888     if (ref $el eq "ARRAY") {
889       if ($action eq '|') {
890         $value |= evaluate_rights_ary($el);
891       } else {
892         $value &= evaluate_rights_ary($el);
893       }
894
895     } elsif (($el eq '&') || ($el eq '|')) {
896       $action = $el;
897
898     } elsif ($action eq '|') {
899       $value |= $el;
900
901     } else {
902       $value &= $el;
903
904     }
905   }
906
907   $main::lxdebug->leave_sub(2);
908
909   return $value;
910 }
911
912 sub _parse_rights_string {
913   $main::lxdebug->enter_sub(2);
914
915   my $self   = shift;
916
917   my $login  = shift;
918   my $access = shift;
919
920   my @stack;
921   my $cur_ary = [];
922
923   push @stack, $cur_ary;
924
925   while ($access =~ m/^([a-z_0-9]+|\||\&|\(|\)|\s+)/) {
926     my $token = $1;
927     substr($access, 0, length $1) = "";
928
929     next if ($token =~ /\s/);
930
931     if ($token eq "(") {
932       my $new_cur_ary = [];
933       push @stack, $new_cur_ary;
934       push @{$cur_ary}, $new_cur_ary;
935       $cur_ary = $new_cur_ary;
936
937     } elsif ($token eq ")") {
938       pop @stack;
939
940       if (!@stack) {
941         $main::lxdebug->leave_sub(2);
942         return 0;
943       }
944
945       $cur_ary = $stack[-1];
946
947     } elsif (($token eq "|") || ($token eq "&")) {
948       push @{$cur_ary}, $token;
949
950     } else {
951       push @{$cur_ary}, $self->{RIGHTS}->{$login}->{$token} * 1;
952     }
953   }
954
955   my $result = ($access || (1 < scalar @stack)) ? 0 : evaluate_rights_ary($stack[0]);
956
957   $main::lxdebug->leave_sub(2);
958
959   return $result;
960 }
961
962 sub check_right {
963   $main::lxdebug->enter_sub(2);
964
965   my $self    = shift;
966   my $login   = shift;
967   my $right   = shift;
968   my $default = shift;
969
970   $self->{FULL_RIGHTS}           ||= { };
971   $self->{FULL_RIGHTS}->{$login} ||= { };
972
973   if (!defined $self->{FULL_RIGHTS}->{$login}->{$right}) {
974     $self->{RIGHTS}           ||= { };
975     $self->{RIGHTS}->{$login} ||= $self->load_rights_for_user($login);
976
977     $self->{FULL_RIGHTS}->{$login}->{$right} = $self->_parse_rights_string($login, $right);
978   }
979
980   my $granted = $self->{FULL_RIGHTS}->{$login}->{$right};
981   $granted    = $default if (!defined $granted);
982
983   $main::lxdebug->leave_sub(2);
984
985   return $granted;
986 }
987
988 sub assert {
989   $main::lxdebug->enter_sub(2);
990
991   my $self       = shift;
992   my $right      = shift;
993   my $dont_abort = shift;
994
995   my $form       = $main::form;
996
997   if ($self->check_right($form->{login}, $right)) {
998     $main::lxdebug->leave_sub(2);
999     return 1;
1000   }
1001
1002   if (!$dont_abort) {
1003     delete $form->{title};
1004     $form->show_generic_error($main::locale->text("You do not have the permissions to access this function."));
1005   }
1006
1007   $main::lxdebug->leave_sub(2);
1008
1009   return 0;
1010 }
1011
1012 sub load_rights_for_user {
1013   $main::lxdebug->enter_sub();
1014
1015   my $self  = shift;
1016   my $login = shift;
1017
1018   my $form  = $main::form;
1019   my $dbh   = $self->dbconnect();
1020
1021   my ($query, $sth, $row, $rights);
1022
1023   $rights = {};
1024
1025   $query =
1026     qq|SELECT gr."right", gr.granted
1027        FROM auth.group_rights gr
1028        WHERE group_id IN
1029          (SELECT ug.group_id
1030           FROM auth.user_group ug
1031           LEFT JOIN auth."user" u ON (ug.user_id = u.id)
1032           WHERE u.login = ?)|;
1033
1034   $sth = prepare_execute_query($form, $dbh, $query, $login);
1035
1036   while ($row = $sth->fetchrow_hashref()) {
1037     $rights->{$row->{right}} |= $row->{granted};
1038   }
1039   $sth->finish();
1040
1041   map({ $rights->{$_} = 0 unless (defined $rights->{$_}); } SL::Auth::all_rights());
1042
1043   $main::lxdebug->leave_sub();
1044
1045   return $rights;
1046 }
1047
1048 1;