]> wagnertech.de Git - kivitendo-erp.git/blob - SL/Auth.pm
Legacy-Interface: Auth::authenticate unterstützt "is_crypted" nicht mehr
[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 => 1 });
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   $dbh->begin_work;
310
311   $query     = qq|SELECT id FROM auth."user" WHERE login = ?|;
312   ($user_id) = selectrow_query($form, $dbh, $query, $login);
313
314   if (!$user_id) {
315     $query     = qq|SELECT nextval('auth.user_id_seq')|;
316     ($user_id) = selectrow_query($form, $dbh, $query);
317
318     $query     = qq|INSERT INTO auth."user" (id, login) VALUES (?, ?)|;
319     do_query($form, $dbh, $query, $user_id, $login);
320   }
321
322   $query = qq|DELETE FROM auth.user_config WHERE (user_id = ?)|;
323   do_query($form, $dbh, $query, $user_id);
324
325   $query = qq|INSERT INTO auth.user_config (user_id, cfg_key, cfg_value) VALUES (?, ?, ?)|;
326   $sth   = prepare_query($form, $dbh, $query);
327
328   while (my ($cfg_key, $cfg_value) = each %params) {
329     next if ($cfg_key eq 'password');
330
331     do_statement($form, $sth, $query, $user_id, $cfg_key, $cfg_value);
332   }
333
334   $dbh->commit();
335
336   $main::lxdebug->leave_sub();
337 }
338
339 sub can_change_password {
340   my $self = shift;
341
342   return $self->{authenticator}->can_change_password();
343 }
344
345 sub change_password {
346   $main::lxdebug->enter_sub();
347
348   my $self   = shift;
349   my $result = $self->{authenticator}->change_password(@_);
350
351   $main::lxdebug->leave_sub();
352
353   return $result;
354 }
355
356 sub read_all_users {
357   $main::lxdebug->enter_sub();
358
359   my $self  = shift;
360
361   my $dbh   = $self->dbconnect();
362   my $query = qq|SELECT u.id, u.login, cfg.cfg_key, cfg.cfg_value
363                  FROM auth.user_config cfg
364                  LEFT JOIN auth."user" u ON (cfg.user_id = u.id)|;
365   my $sth   = prepare_execute_query($main::form, $dbh, $query);
366
367   my %users;
368
369   while (my $ref = $sth->fetchrow_hashref()) {
370     $users{$ref->{login}}                    ||= { 'login' => $ref->{login}, 'id' => $ref->{id} };
371     $users{$ref->{login}}->{$ref->{cfg_key}}   = $ref->{cfg_value} if (($ref->{cfg_key} ne 'login') && ($ref->{cfg_key} ne 'id'));
372   }
373
374   $sth->finish();
375
376   $main::lxdebug->leave_sub();
377
378   return %users;
379 }
380
381 sub read_user {
382   $main::lxdebug->enter_sub();
383
384   my $self  = shift;
385   my $login = shift;
386
387   my $dbh   = $self->dbconnect();
388   my $query = qq|SELECT u.id, u.login, cfg.cfg_key, cfg.cfg_value
389                  FROM auth.user_config cfg
390                  LEFT JOIN auth."user" u ON (cfg.user_id = u.id)
391                  WHERE (u.login = ?)|;
392   my $sth   = prepare_execute_query($main::form, $dbh, $query, $login);
393
394   my %user_data;
395
396   while (my $ref = $sth->fetchrow_hashref()) {
397     $user_data{$ref->{cfg_key}} = $ref->{cfg_value};
398     @user_data{qw(id login)}    = @{$ref}{qw(id login)};
399   }
400
401   $sth->finish();
402
403   $main::lxdebug->leave_sub();
404
405   return %user_data;
406 }
407
408 sub get_user_id {
409   $main::lxdebug->enter_sub();
410
411   my $self  = shift;
412   my $login = shift;
413
414   my $dbh   = $self->dbconnect();
415   my ($id)  = selectrow_query($main::form, $dbh, qq|SELECT id FROM auth."user" WHERE login = ?|, $login);
416
417   $main::lxdebug->leave_sub();
418
419   return $id;
420 }
421
422 sub delete_user {
423   $main::lxdebug->enter_sub();
424
425   my $self  = shift;
426   my $login = shift;
427
428   my $form  = $main::form;
429
430   my $dbh   = $self->dbconnect();
431
432   $dbh->begin_work;
433
434   my $query = qq|SELECT id FROM auth."user" WHERE login = ?|;
435
436   my ($id)  = selectrow_query($form, $dbh, $query, $login);
437
438   $dbh->rollback and return $main::lxdebug->leave_sub() if (!$id);
439
440   do_query($form, $dbh, qq|DELETE FROM auth.user_group WHERE user_id = ?|, $id);
441   do_query($form, $dbh, qq|DELETE FROM auth.user_config WHERE user_id = ?|, $id);
442
443   $dbh->commit();
444
445   $main::lxdebug->leave_sub();
446 }
447
448 # --------------------------------------
449
450 my $session_id;
451
452 sub restore_session {
453   $main::lxdebug->enter_sub();
454
455   my $self = shift;
456
457   my $cgi            =  $main::cgi;
458   $cgi             ||=  CGI->new('');
459
460   $session_id        =  $cgi->cookie($self->get_session_cookie_name());
461   $session_id        =~ s|[^0-9a-f]||g;
462
463   $self->{SESSION}   = { };
464
465   if (!$session_id) {
466     $main::lxdebug->leave_sub();
467     return SESSION_NONE;
468   }
469
470   my ($dbh, $query, $sth, $cookie, $ref, $form);
471
472   $form   = $main::form;
473
474   $dbh    = $self->dbconnect();
475   $query  = qq|SELECT *, (mtime < (now() - '$self->{session_timeout}m'::interval)) AS is_expired FROM auth.session WHERE id = ?|;
476
477   $cookie = selectfirst_hashref_query($form, $dbh, $query, $session_id);
478
479   if (!$cookie || $cookie->{is_expired} || ($cookie->{ip_address} ne $ENV{REMOTE_ADDR})) {
480     $self->destroy_session();
481     $main::lxdebug->leave_sub();
482     return $cookie ? SESSION_EXPIRED : SESSION_NONE;
483   }
484
485   $query = qq|SELECT sess_key, sess_value FROM auth.session_content WHERE session_id = ?|;
486   $sth   = prepare_execute_query($form, $dbh, $query, $session_id);
487
488   while (my $ref = $sth->fetchrow_hashref()) {
489     $self->{SESSION}->{$ref->{sess_key}} = $ref->{sess_value};
490     $form->{$ref->{sess_key}}            = $self->_load_value($ref->{sess_value}) if (!defined $form->{$ref->{sess_key}});
491   }
492
493   $sth->finish();
494
495   $main::lxdebug->leave_sub();
496
497   return SESSION_OK;
498 }
499
500 sub _load_value {
501   return $_[1] if $_[1] !~ m/^---/;
502
503   my $value;
504   eval {
505     $value = YAML::Load($_[1]);
506     1;
507   } or return $_[1];
508
509   return $value;
510 }
511
512 sub destroy_session {
513   $main::lxdebug->enter_sub();
514
515   my $self = shift;
516
517   if ($session_id) {
518     my $dbh = $self->dbconnect();
519
520     $dbh->begin_work;
521
522     do_query($main::form, $dbh, qq|DELETE FROM auth.session_content WHERE session_id = ?|, $session_id);
523     do_query($main::form, $dbh, qq|DELETE FROM auth.session WHERE id = ?|, $session_id);
524
525     $dbh->commit();
526
527     $session_id      = undef;
528     $self->{SESSION} = { };
529   }
530
531   $main::lxdebug->leave_sub();
532 }
533
534 sub expire_sessions {
535   $main::lxdebug->enter_sub();
536
537   my $self  = shift;
538
539   my $dbh   = $self->dbconnect();
540
541   $dbh->begin_work;
542
543   my $query =
544     qq|DELETE FROM auth.session_content
545        WHERE session_id IN
546          (SELECT id
547           FROM auth.session
548           WHERE (mtime < (now() - '$self->{session_timeout}m'::interval)))|;
549
550   do_query($main::form, $dbh, $query);
551
552   $query =
553     qq|DELETE FROM auth.session
554        WHERE (mtime < (now() - '$self->{session_timeout}m'::interval))|;
555
556   do_query($main::form, $dbh, $query);
557
558   $dbh->commit();
559
560   $main::lxdebug->leave_sub();
561 }
562
563 sub _create_session_id {
564   $main::lxdebug->enter_sub();
565
566   my @data;
567   map { push @data, int(rand() * 255); } (1..32);
568
569   my $id = md5_hex(pack 'C*', @data);
570
571   $main::lxdebug->leave_sub();
572
573   return $id;
574 }
575
576 sub create_or_refresh_session {
577   $main::lxdebug->enter_sub();
578
579   my $self = shift;
580
581   $session_id ||= $self->_create_session_id();
582
583   my ($form, $dbh, $query, $sth, $id);
584
585   $form  = $main::form;
586   $dbh   = $self->dbconnect();
587
588   $dbh->begin_work;
589   do_query($::form, $dbh, qq|LOCK auth.session_content|);
590
591   $query = qq|SELECT id FROM auth.session WHERE id = ?|;
592
593   ($id)  = selectrow_query($form, $dbh, $query, $session_id);
594
595   if ($id) {
596     do_query($form, $dbh, qq|UPDATE auth.session SET mtime = now() WHERE id = ?|, $session_id);
597
598   } else {
599     do_query($form, $dbh, qq|INSERT INTO auth.session (id, ip_address, mtime) VALUES (?, ?, now())|, $session_id, $ENV{REMOTE_ADDR});
600
601   }
602
603   $self->save_session($dbh);
604
605   $dbh->commit();
606
607   $main::lxdebug->leave_sub();
608 }
609
610 sub save_session {
611   my $self         = shift;
612   my $provided_dbh = shift;
613
614   my $dbh          = $provided_dbh || $self->dbconnect();
615
616   $dbh->begin_work unless $provided_dbh;
617
618   do_query($::form, $dbh, qq|LOCK auth.session_content|);
619   do_query($::form, $dbh, qq|DELETE FROM auth.session_content WHERE session_id = ?|, $session_id);
620
621   if (%{ $self->{SESSION} }) {
622     my $query = qq|INSERT INTO auth.session_content (session_id, sess_key, sess_value) VALUES (?, ?, ?)|;
623     my $sth   = prepare_query($::form, $dbh, $query);
624
625     foreach my $key (sort keys %{ $self->{SESSION} }) {
626       do_statement($::form, $sth, $query, $session_id, $key, $self->{SESSION}->{$key});
627     }
628
629     $sth->finish();
630   }
631
632   $dbh->commit() unless $provided_dbh;
633 }
634
635 sub set_session_value {
636   $main::lxdebug->enter_sub();
637
638   my $self   = shift;
639   my %params = @_;
640
641   $self->{SESSION} ||= { };
642
643   while (my ($key, $value) = each %params) {
644     $self->{SESSION}->{ $key } = YAML::Dump($value);
645   }
646
647   $main::lxdebug->leave_sub();
648
649   return $self;
650 }
651
652 sub delete_session_value {
653   $main::lxdebug->enter_sub();
654
655   my $self = shift;
656
657   $self->{SESSION} ||= { };
658   delete @{ $self->{SESSION} }{ @_ };
659
660   $main::lxdebug->leave_sub();
661
662   return $self;
663 }
664
665 sub get_session_value {
666   $main::lxdebug->enter_sub();
667
668   my $self  = shift;
669   my $value = $self->{SESSION} ? $self->_load_value($self->{SESSION}->{ $_[0] }) : undef;
670
671   $main::lxdebug->leave_sub();
672
673   return $value;
674 }
675
676 sub set_cookie_environment_variable {
677   my $self = shift;
678   $ENV{HTTP_COOKIE} = $self->get_session_cookie_name() . "=${session_id}";
679 }
680
681 sub get_session_cookie_name {
682   my $self = shift;
683
684   return $self->{cookie_name} || 'lx_office_erp_session_id';
685 }
686
687 sub get_session_id {
688   return $session_id;
689 }
690
691 sub session_tables_present {
692   $main::lxdebug->enter_sub();
693
694   my $self = shift;
695   my $dbh  = $self->dbconnect(1);
696
697   if (!$dbh) {
698     $main::lxdebug->leave_sub();
699     return 0;
700   }
701
702   my $query =
703     qq|SELECT COUNT(*)
704        FROM pg_tables
705        WHERE (schemaname = 'auth')
706          AND (tablename IN ('session', 'session_content'))|;
707
708   my ($count) = selectrow_query($main::form, $dbh, $query);
709
710   $main::lxdebug->leave_sub();
711
712   return 2 == $count;
713 }
714
715 # --------------------------------------
716
717 sub all_rights_full {
718   my $locale = $main::locale;
719
720   my @all_rights = (
721     ["--crm",                          $locale->text("CRM optional software")],
722     ["crm_search",                     $locale->text("CRM search")],
723     ["crm_new",                        $locale->text("CRM create customers, vendors and contacts")],
724     ["crm_service",                    $locale->text("CRM services")],
725     ["crm_admin",                      $locale->text("CRM admin")],
726     ["crm_adminuser",                  $locale->text("CRM user")],
727     ["crm_adminstatus",                $locale->text("CRM status")],
728     ["crm_email",                      $locale->text("CRM send email")],
729     ["crm_termin",                     $locale->text("CRM termin")],
730     ["crm_opportunity",                $locale->text("CRM opportunity")],
731     ["crm_knowhow",                    $locale->text("CRM know how")],
732     ["crm_follow",                     $locale->text("CRM follow up")],
733     ["crm_notices",                    $locale->text("CRM notices")],
734     ["crm_other",                      $locale->text("CRM other")],
735     ["--master_data",                  $locale->text("Master Data")],
736     ["customer_vendor_edit",           $locale->text("Create and edit customers and vendors")],
737     ["part_service_assembly_edit",     $locale->text("Create and edit parts, services, assemblies")],
738     ["project_edit",                   $locale->text("Create and edit projects")],
739     ["license_edit",                   $locale->text("Manage license keys")],
740     ["--ar",                           $locale->text("AR")],
741     ["sales_quotation_edit",           $locale->text("Create and edit sales quotations")],
742     ["sales_order_edit",               $locale->text("Create and edit sales orders")],
743     ["sales_delivery_order_edit",      $locale->text("Create and edit sales delivery orders")],
744     ["invoice_edit",                   $locale->text("Create and edit invoices and credit notes")],
745     ["dunning_edit",                   $locale->text("Create and edit dunnings")],
746     ["sales_all_edit",                 $locale->text("View/edit all employees sales documents")],
747     ["--ap",                           $locale->text("AP")],
748     ["request_quotation_edit",         $locale->text("Create and edit RFQs")],
749     ["purchase_order_edit",            $locale->text("Create and edit purchase orders")],
750     ["purchase_delivery_order_edit",   $locale->text("Create and edit purchase delivery orders")],
751     ["vendor_invoice_edit",            $locale->text("Create and edit vendor invoices")],
752     ["--warehouse_management",         $locale->text("Warehouse management")],
753     ["warehouse_contents",             $locale->text("View warehouse content")],
754     ["warehouse_management",           $locale->text("Warehouse management")],
755     ["--general_ledger_cash",          $locale->text("General ledger and cash")],
756     ["general_ledger",                 $locale->text("Transactions, AR transactions, AP transactions")],
757     ["datev_export",                   $locale->text("DATEV Export")],
758     ["cash",                           $locale->text("Receipt, payment, reconciliation")],
759     ["--reports",                      $locale->text('Reports')],
760     ["report",                         $locale->text('All reports')],
761     ["advance_turnover_tax_return",    $locale->text('Advance turnover tax return')],
762     ["--batch_printing",               $locale->text("Batch Printing")],
763     ["batch_printing",                 $locale->text("Batch Printing")],
764     ["--others",                       $locale->text("Others")],
765     ["email_bcc",                      $locale->text("May set the BCC field when sending emails")],
766     ["config",                         $locale->text("Change Lx-Office installation settings (all menu entries beneath 'System')")],
767     );
768
769   return @all_rights;
770 }
771
772 sub all_rights {
773   return grep !/^--/, map { $_->[0] } all_rights_full();
774 }
775
776 sub read_groups {
777   $main::lxdebug->enter_sub();
778
779   my $self = shift;
780
781   my $form   = $main::form;
782   my $groups = {};
783   my $dbh    = $self->dbconnect();
784
785   my $query  = 'SELECT * FROM auth."group"';
786   my $sth    = prepare_execute_query($form, $dbh, $query);
787
788   my ($row, $group);
789
790   while ($row = $sth->fetchrow_hashref()) {
791     $groups->{$row->{id}} = $row;
792   }
793   $sth->finish();
794
795   $query = 'SELECT * FROM auth.user_group WHERE group_id = ?';
796   $sth   = prepare_query($form, $dbh, $query);
797
798   foreach $group (values %{$groups}) {
799     my @members;
800
801     do_statement($form, $sth, $query, $group->{id});
802
803     while ($row = $sth->fetchrow_hashref()) {
804       push @members, $row->{user_id};
805     }
806     $group->{members} = [ uniq @members ];
807   }
808   $sth->finish();
809
810   $query = 'SELECT * FROM auth.group_rights WHERE group_id = ?';
811   $sth   = prepare_query($form, $dbh, $query);
812
813   foreach $group (values %{$groups}) {
814     $group->{rights} = {};
815
816     do_statement($form, $sth, $query, $group->{id});
817
818     while ($row = $sth->fetchrow_hashref()) {
819       $group->{rights}->{$row->{right}} |= $row->{granted};
820     }
821
822     map { $group->{rights}->{$_} = 0 if (!defined $group->{rights}->{$_}); } all_rights();
823   }
824   $sth->finish();
825
826   $main::lxdebug->leave_sub();
827
828   return $groups;
829 }
830
831 sub save_group {
832   $main::lxdebug->enter_sub();
833
834   my $self  = shift;
835   my $group = shift;
836
837   my $form  = $main::form;
838   my $dbh   = $self->dbconnect();
839
840   $dbh->begin_work;
841
842   my ($query, $sth, $row, $rights);
843
844   if (!$group->{id}) {
845     ($group->{id}) = selectrow_query($form, $dbh, qq|SELECT nextval('auth.group_id_seq')|);
846
847     $query = qq|INSERT INTO auth."group" (id, name, description) VALUES (?, '', '')|;
848     do_query($form, $dbh, $query, $group->{id});
849   }
850
851   do_query($form, $dbh, qq|UPDATE auth."group" SET name = ?, description = ? WHERE id = ?|, map { $group->{$_} } qw(name description id));
852
853   do_query($form, $dbh, qq|DELETE FROM auth.user_group WHERE group_id = ?|, $group->{id});
854
855   $query  = qq|INSERT INTO auth.user_group (user_id, group_id) VALUES (?, ?)|;
856   $sth    = prepare_query($form, $dbh, $query);
857
858   foreach my $user_id (uniq @{ $group->{members} }) {
859     do_statement($form, $sth, $query, $user_id, $group->{id});
860   }
861   $sth->finish();
862
863   do_query($form, $dbh, qq|DELETE FROM auth.group_rights WHERE group_id = ?|, $group->{id});
864
865   $query = qq|INSERT INTO auth.group_rights (group_id, "right", granted) VALUES (?, ?, ?)|;
866   $sth   = prepare_query($form, $dbh, $query);
867
868   foreach my $right (keys %{ $group->{rights} }) {
869     do_statement($form, $sth, $query, $group->{id}, $right, $group->{rights}->{$right} ? 't' : 'f');
870   }
871   $sth->finish();
872
873   $dbh->commit();
874
875   $main::lxdebug->leave_sub();
876 }
877
878 sub delete_group {
879   $main::lxdebug->enter_sub();
880
881   my $self = shift;
882   my $id   = shift;
883
884   my $form = $main::from;
885
886   my $dbh  = $self->dbconnect();
887   $dbh->begin_work;
888
889   do_query($form, $dbh, qq|DELETE FROM auth.user_group WHERE group_id = ?|, $id);
890   do_query($form, $dbh, qq|DELETE FROM auth.group_rights WHERE group_id = ?|, $id);
891   do_query($form, $dbh, qq|DELETE FROM auth."group" WHERE id = ?|, $id);
892
893   $dbh->commit();
894
895   $main::lxdebug->leave_sub();
896 }
897
898 sub evaluate_rights_ary {
899   $main::lxdebug->enter_sub(2);
900
901   my $ary    = shift;
902
903   my $value  = 0;
904   my $action = '|';
905
906   foreach my $el (@{$ary}) {
907     if (ref $el eq "ARRAY") {
908       if ($action eq '|') {
909         $value |= evaluate_rights_ary($el);
910       } else {
911         $value &= evaluate_rights_ary($el);
912       }
913
914     } elsif (($el eq '&') || ($el eq '|')) {
915       $action = $el;
916
917     } elsif ($action eq '|') {
918       $value |= $el;
919
920     } else {
921       $value &= $el;
922
923     }
924   }
925
926   $main::lxdebug->leave_sub(2);
927
928   return $value;
929 }
930
931 sub _parse_rights_string {
932   $main::lxdebug->enter_sub(2);
933
934   my $self   = shift;
935
936   my $login  = shift;
937   my $access = shift;
938
939   my @stack;
940   my $cur_ary = [];
941
942   push @stack, $cur_ary;
943
944   while ($access =~ m/^([a-z_0-9]+|\||\&|\(|\)|\s+)/) {
945     my $token = $1;
946     substr($access, 0, length $1) = "";
947
948     next if ($token =~ /\s/);
949
950     if ($token eq "(") {
951       my $new_cur_ary = [];
952       push @stack, $new_cur_ary;
953       push @{$cur_ary}, $new_cur_ary;
954       $cur_ary = $new_cur_ary;
955
956     } elsif ($token eq ")") {
957       pop @stack;
958
959       if (!@stack) {
960         $main::lxdebug->leave_sub(2);
961         return 0;
962       }
963
964       $cur_ary = $stack[-1];
965
966     } elsif (($token eq "|") || ($token eq "&")) {
967       push @{$cur_ary}, $token;
968
969     } else {
970       push @{$cur_ary}, $self->{RIGHTS}->{$login}->{$token} * 1;
971     }
972   }
973
974   my $result = ($access || (1 < scalar @stack)) ? 0 : evaluate_rights_ary($stack[0]);
975
976   $main::lxdebug->leave_sub(2);
977
978   return $result;
979 }
980
981 sub check_right {
982   $main::lxdebug->enter_sub(2);
983
984   my $self    = shift;
985   my $login   = shift;
986   my $right   = shift;
987   my $default = shift;
988
989   $self->{FULL_RIGHTS}           ||= { };
990   $self->{FULL_RIGHTS}->{$login} ||= { };
991
992   if (!defined $self->{FULL_RIGHTS}->{$login}->{$right}) {
993     $self->{RIGHTS}           ||= { };
994     $self->{RIGHTS}->{$login} ||= $self->load_rights_for_user($login);
995
996     $self->{FULL_RIGHTS}->{$login}->{$right} = $self->_parse_rights_string($login, $right);
997   }
998
999   my $granted = $self->{FULL_RIGHTS}->{$login}->{$right};
1000   $granted    = $default if (!defined $granted);
1001
1002   $main::lxdebug->leave_sub(2);
1003
1004   return $granted;
1005 }
1006
1007 sub assert {
1008   $main::lxdebug->enter_sub(2);
1009
1010   my $self       = shift;
1011   my $right      = shift;
1012   my $dont_abort = shift;
1013
1014   my $form       = $main::form;
1015
1016   if ($self->check_right($form->{login}, $right)) {
1017     $main::lxdebug->leave_sub(2);
1018     return 1;
1019   }
1020
1021   if (!$dont_abort) {
1022     delete $form->{title};
1023     $form->show_generic_error($main::locale->text("You do not have the permissions to access this function."));
1024   }
1025
1026   $main::lxdebug->leave_sub(2);
1027
1028   return 0;
1029 }
1030
1031 sub load_rights_for_user {
1032   $main::lxdebug->enter_sub();
1033
1034   my $self  = shift;
1035   my $login = shift;
1036
1037   my $form  = $main::form;
1038   my $dbh   = $self->dbconnect();
1039
1040   my ($query, $sth, $row, $rights);
1041
1042   $rights = {};
1043
1044   $query =
1045     qq|SELECT gr."right", gr.granted
1046        FROM auth.group_rights gr
1047        WHERE group_id IN
1048          (SELECT ug.group_id
1049           FROM auth.user_group ug
1050           LEFT JOIN auth."user" u ON (ug.user_id = u.id)
1051           WHERE u.login = ?)|;
1052
1053   $sth = prepare_execute_query($form, $dbh, $query, $login);
1054
1055   while ($row = $sth->fetchrow_hashref()) {
1056     $rights->{$row->{right}} |= $row->{granted};
1057   }
1058   $sth->finish();
1059
1060   map({ $rights->{$_} = 0 unless (defined $rights->{$_}); } SL::Auth::all_rights());
1061
1062   $main::lxdebug->leave_sub();
1063
1064   return $rights;
1065 }
1066
1067 1;