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;
26 use constant SESSION_KEY_ROOT_AUTH => 'session_auth_status_root';
27 use constant SESSION_KEY_USER_AUTH => 'session_auth_status_user';
29 use Rose::Object::MakeMethods::Generic (
30 scalar => [ qw(client) ],
35 $main::lxdebug->enter_sub();
37 my ($type, %params) = @_;
38 my $self = bless {}, $type;
40 $self->_read_auth_config(%params);
43 $main::lxdebug->leave_sub();
49 my ($self, %params) = @_;
52 $self->{SESSION} = { };
53 $self->{FULL_RIGHTS} = { };
54 $self->{RIGHTS} = { };
55 $self->{unique_counter} = 0;
56 $self->{column_information} = SL::Auth::ColumnInformation->new(auth => $self);
57 $self->{authenticator}->reset;
63 my ($self, $id_or_name) = @_;
67 return undef unless $id_or_name;
69 my $column = $id_or_name =~ m/^\d+$/ ? 'id' : 'name';
70 my $dbh = $self->dbconnect;
72 return undef unless $dbh;
74 $self->client($dbh->selectrow_hashref(qq|SELECT * FROM auth.clients WHERE ${column} = ?|, undef, $id_or_name));
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();
102 my ($self, %params) = @_;
104 map { $self->{$_} = $::lx_office_conf{authentication}->{$_} } keys %{ $::lx_office_conf{authentication} };
106 # Prevent password leakage to log files when dumping Auth instances.
107 $self->{admin_password} = sub { $::lx_office_conf{authentication}->{admin_password} };
109 if ($params{unit_tests_database}) {
110 $self->{DB_config} = $::lx_office_conf{'testing/database'};
111 $self->{module} = 'DB';
114 $self->{DB_config} = $::lx_office_conf{'authentication/database'};
115 $self->{LDAP_config} = $::lx_office_conf{'authentication/ldap'};
118 if ($self->{module} eq 'DB') {
119 $self->{authenticator} = SL::Auth::DB->new($self);
121 } elsif ($self->{module} eq 'LDAP') {
122 $self->{authenticator} = SL::Auth::LDAP->new($self);
125 if (!$self->{authenticator}) {
126 my $locale = Locale->new('en');
127 $self->mini_error($locale->text('No or an unknown authenticantion module specified in "config/kivitendo.conf".'));
130 my $cfg = $self->{DB_config};
133 my $locale = Locale->new('en');
134 $self->mini_error($locale->text('config/kivitendo.conf: Key "DB_config" is missing.'));
137 if (!$cfg->{host} || !$cfg->{db} || !$cfg->{user}) {
138 my $locale = Locale->new('en');
139 $self->mini_error($locale->text('config/kivitendo.conf: Missing parameters in "authentication/database". Required parameters are "host", "db" and "user".'));
142 $self->{authenticator}->verify_config();
144 $self->{session_timeout} *= 1;
145 $self->{session_timeout} = 8 * 60 if (!$self->{session_timeout});
147 $main::lxdebug->leave_sub();
150 sub has_access_to_client {
151 my ($self, $login) = @_;
153 return 0 if !$self->client || !$self->client->{id};
157 FROM auth.clients_users cu
158 LEFT JOIN auth."user" u ON (cu.user_id = u.id)
160 AND (cu.client_id = ?)
163 my ($has_access) = $self->dbconnect->selectrow_array($sql, undef, $login, $self->client->{id});
167 sub authenticate_root {
168 $main::lxdebug->enter_sub();
170 my ($self, $password) = @_;
172 my $session_root_auth = $self->get_session_value(SESSION_KEY_ROOT_AUTH());
173 if (defined $session_root_auth && $session_root_auth == OK) {
174 $::lxdebug->leave_sub;
178 if (!defined $password) {
179 $::lxdebug->leave_sub;
183 $password = SL::Auth::Password->hash(login => 'root', password => $password);
184 my $admin_password = SL::Auth::Password->hash_if_unhashed(login => 'root', password => $self->{admin_password}->());
186 my $result = $password eq $admin_password ? OK : ERR_PASSWORD;
187 $self->set_session_value(SESSION_KEY_ROOT_AUTH() => $result);
189 $::lxdebug->leave_sub;
194 $main::lxdebug->enter_sub();
196 my ($self, $login, $password) = @_;
198 if (!$self->client || !$self->has_access_to_client($login)) {
199 $::lxdebug->leave_sub;
203 my $session_auth = $self->get_session_value(SESSION_KEY_USER_AUTH());
204 if (defined $session_auth && $session_auth == OK) {
205 $::lxdebug->leave_sub;
209 if (!defined $password) {
210 $::lxdebug->leave_sub;
214 my $result = $login ? $self->{authenticator}->authenticate($login, $password) : ERR_USER;
215 $self->set_session_value(SESSION_KEY_USER_AUTH() => $result, login => $login, client_id => $self->client->{id});
217 $::lxdebug->leave_sub;
221 sub punish_wrong_login {
222 my $failed_login_penalty = ($::lx_office_conf{authentication} || {})->{failed_login_penalty};
223 sleep $failed_login_penalty if $failed_login_penalty;
226 sub get_stored_password {
227 my ($self, $login) = @_;
229 my $dbh = $self->dbconnect;
231 return undef unless $dbh;
233 my $query = qq|SELECT password FROM auth."user" WHERE login = ?|;
234 my ($stored_password) = $dbh->selectrow_array($query, undef, $login);
236 return $stored_password;
240 $main::lxdebug->enter_sub(2);
243 my $may_fail = shift;
246 $main::lxdebug->leave_sub(2);
250 my $cfg = $self->{DB_config};
251 my $dsn = 'dbi:Pg:dbname=' . $cfg->{db} . ';host=' . $cfg->{host};
254 $dsn .= ';port=' . $cfg->{port};
257 $main::lxdebug->message(LXDebug->DEBUG1, "Auth::dbconnect DSN: $dsn");
259 $self->{dbh} = SL::DBConnect->connect($dsn, $cfg->{user}, $cfg->{password}, { pg_enable_utf8 => 1, AutoCommit => 1 });
261 if (!$may_fail && !$self->{dbh}) {
262 $main::form->error($main::locale->text('The connection to the authentication database failed:') . "\n" . $DBI::errstr);
265 $main::lxdebug->leave_sub(2);
271 $main::lxdebug->enter_sub();
276 $self->{dbh}->disconnect();
280 $main::lxdebug->leave_sub();
284 $main::lxdebug->enter_sub();
286 my ($self, $dbh) = @_;
288 $dbh ||= $self->dbconnect();
289 my $query = qq|SELECT COUNT(*) FROM pg_tables WHERE (schemaname = 'auth') AND (tablename = 'user')|;
291 my ($count) = $dbh->selectrow_array($query);
293 $main::lxdebug->leave_sub();
299 $main::lxdebug->enter_sub();
303 my $dbh = $self->dbconnect(1);
305 $main::lxdebug->leave_sub();
310 sub create_database {
311 $main::lxdebug->enter_sub();
316 my $cfg = $self->{DB_config};
318 if (!$params{superuser}) {
319 $params{superuser} = $cfg->{user};
320 $params{superuser_password} = $cfg->{password};
323 $params{template} ||= 'template0';
324 $params{template} =~ s|[^a-zA-Z0-9_\-]||g;
326 my $dsn = 'dbi:Pg:dbname=template1;host=' . $cfg->{host};
329 $dsn .= ';port=' . $cfg->{port};
332 $main::lxdebug->message(LXDebug->DEBUG1(), "Auth::create_database DSN: $dsn");
334 my $dbh = SL::DBConnect->connect($dsn, $params{superuser}, $params{superuser_password}, { pg_enable_utf8 => 1 });
337 $main::form->error($main::locale->text('The connection to the template database failed:') . "\n" . $DBI::errstr);
340 my $query = qq|CREATE DATABASE "$cfg->{db}" OWNER "$cfg->{user}" TEMPLATE "$params{template}" ENCODING 'UNICODE'|;
342 $main::lxdebug->message(LXDebug->DEBUG1(), "Auth::create_database query: $query");
347 my $error = $dbh->errstr();
349 $query = qq|SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = 'template0'|;
350 my ($cluster_encoding) = $dbh->selectrow_array($query);
352 if ($cluster_encoding && ($cluster_encoding !~ m/^(?:UTF-?8|UNICODE)$/i)) {
353 $error = $::locale->text('Your PostgreSQL installationen does not use Unicode as its encoding. This is not supported anymore.');
358 $main::form->error($main::locale->text('The creation of the authentication database failed:') . "\n" . $error);
363 $main::lxdebug->leave_sub();
367 $main::lxdebug->enter_sub();
370 my $dbh = $self->dbconnect();
373 SL::DBUpgrade2->new(form => $::form)->process_query($dbh, 'sql/auth_db.sql');
375 $main::lxdebug->leave_sub();
379 $main::lxdebug->enter_sub();
385 my $form = $main::form;
387 my $dbh = $self->dbconnect();
389 my ($sth, $query, $user_id);
393 $query = qq|SELECT id FROM auth."user" WHERE login = ?|;
394 ($user_id) = selectrow_query($form, $dbh, $query, $login);
397 $query = qq|SELECT nextval('auth.user_id_seq')|;
398 ($user_id) = selectrow_query($form, $dbh, $query);
400 $query = qq|INSERT INTO auth."user" (id, login) VALUES (?, ?)|;
401 do_query($form, $dbh, $query, $user_id, $login);
404 $query = qq|DELETE FROM auth.user_config WHERE (user_id = ?)|;
405 do_query($form, $dbh, $query, $user_id);
407 $query = qq|INSERT INTO auth.user_config (user_id, cfg_key, cfg_value) VALUES (?, ?, ?)|;
408 $sth = prepare_query($form, $dbh, $query);
410 while (my ($cfg_key, $cfg_value) = each %params) {
411 next if ($cfg_key eq 'password');
413 do_statement($form, $sth, $query, $user_id, $cfg_key, $cfg_value);
418 $main::lxdebug->leave_sub();
421 sub can_change_password {
424 return $self->{authenticator}->can_change_password();
427 sub change_password {
428 $main::lxdebug->enter_sub();
430 my ($self, $login, $new_password) = @_;
432 my $result = $self->{authenticator}->change_password($login, $new_password);
434 $main::lxdebug->leave_sub();
440 $main::lxdebug->enter_sub();
444 my $dbh = $self->dbconnect();
445 my $query = qq|SELECT u.id, u.login, cfg.cfg_key, cfg.cfg_value, s.mtime AS last_action
447 FROM auth."user" AS u
449 LEFT JOIN auth.user_config AS cfg
450 ON (cfg.user_id = u.id)
452 LEFT JOIN auth.session_content AS sc_login
453 ON (sc_login.sess_key = 'login' AND sc_login.sess_value = ('--- ' \|\| u.login \|\| '\n'))
455 LEFT JOIN auth.session AS s
456 ON (s.id = sc_login.session_id)
458 my $sth = prepare_execute_query($main::form, $dbh, $query);
462 while (my $ref = $sth->fetchrow_hashref()) {
464 $users{$ref->{login}} ||= {
465 'login' => $ref->{login},
467 'last_action' => $ref->{last_action},
469 $users{$ref->{login}}->{$ref->{cfg_key}} = $ref->{cfg_value} if (($ref->{cfg_key} ne 'login') && ($ref->{cfg_key} ne 'id'));
474 $main::lxdebug->leave_sub();
480 $main::lxdebug->enter_sub();
482 my ($self, %params) = @_;
484 my $dbh = $self->dbconnect();
486 my (@where, @values);
487 if ($params{login}) {
488 push @where, 'u.login = ?';
489 push @values, $params{login};
492 push @where, 'u.id = ?';
493 push @values, $params{id};
495 my $where = join ' AND ', '1 = 1', @where;
496 my $query = qq|SELECT u.id, u.login, cfg.cfg_key, cfg.cfg_value
497 FROM auth.user_config cfg
498 LEFT JOIN auth."user" u ON (cfg.user_id = u.id)
500 my $sth = prepare_execute_query($main::form, $dbh, $query, @values);
504 while (my $ref = $sth->fetchrow_hashref()) {
505 $user_data{$ref->{cfg_key}} = $ref->{cfg_value};
506 @user_data{qw(id login)} = @{$ref}{qw(id login)};
509 # The XUL/XML & 'CSS new' backed menus have been removed.
510 my %menustyle_map = ( xml => 'new', v4 => 'v3' );
511 $user_data{menustyle} = $menustyle_map{lc($user_data{menustyle} || '')} || $user_data{menustyle};
513 # The 'Win2000.css' stylesheet has been removed.
514 $user_data{stylesheet} = 'kivitendo.css' if ($user_data{stylesheet} || '') =~ m/win2000/i;
516 # Set default language if selected language does not exist (anymore).
517 $user_data{countrycode} = $::lx_office_conf{system}->{language} unless $user_data{countrycode} && -d "locale/$user_data{countrycode}";
521 $main::lxdebug->leave_sub();
527 $main::lxdebug->enter_sub();
532 my $dbh = $self->dbconnect();
533 my ($id) = selectrow_query($main::form, $dbh, qq|SELECT id FROM auth."user" WHERE login = ?|, $login);
535 $main::lxdebug->leave_sub();
541 $::lxdebug->enter_sub;
546 my $dbh = $self->dbconnect;
547 my $id = $self->get_user_id($login);
549 $dbh->rollback and return $::lxdebug->leave_sub if (!$id);
553 do_query($::form, $dbh, qq|DELETE FROM auth.user_group WHERE user_id = ?|, $id);
554 do_query($::form, $dbh, qq|DELETE FROM auth.user_config WHERE user_id = ?|, $id);
555 do_query($::form, $dbh, qq|DELETE FROM auth.user WHERE id = ?|, $id);
557 # TODO: SL::Auth::delete_user
558 # do_query($::form, $u_dbh, qq|UPDATE employee SET deleted = 't' WHERE login = ?|, $login) if $u_dbh && $user_db_exists;
562 $::lxdebug->leave_sub;
565 # --------------------------------------
569 sub restore_session {
570 $main::lxdebug->enter_sub();
574 $session_id = $::request->{cgi}->cookie($self->get_session_cookie_name());
575 $session_id =~ s|[^0-9a-f]||g if $session_id;
577 $self->{SESSION} = { };
580 $main::lxdebug->leave_sub();
581 return $self->session_restore_result(SESSION_NONE());
584 my ($dbh, $query, $sth, $cookie, $ref, $form);
588 # Don't fail if the auth DB doesn't yet.
589 if (!( $dbh = $self->dbconnect(1) )) {
590 $::lxdebug->leave_sub;
591 return $self->session_restore_result(SESSION_NONE());
594 # Don't fail if the "auth" schema doesn't exist yet, e.g. if the
595 # admin is creating the session tables at the moment.
596 $query = qq|SELECT *, (mtime < (now() - '$self->{session_timeout}m'::interval)) AS is_expired FROM auth.session WHERE id = ?|;
598 if (!($sth = $dbh->prepare($query)) || !$sth->execute($session_id)) {
599 $sth->finish if $sth;
600 $::lxdebug->leave_sub;
601 return $self->session_restore_result(SESSION_NONE());
604 $cookie = $sth->fetchrow_hashref;
607 # The session ID provided is valid in the following cases:
608 # 1. session ID exists in the database
609 # 2. hasn't expired yet
610 # 3. if form field '{AUTH}api_token' is given: form field must equal database column 'auth.session.api_token' for the session ID
611 # 4. if form field '{AUTH}api_token' is NOT given then: the requestee's IP address must match the stored IP address
612 $self->{api_token} = $cookie->{api_token} if $cookie;
613 my $api_token_cookie = $self->get_api_token_cookie;
614 my $cookie_is_bad = !$cookie || $cookie->{is_expired};
615 $cookie_is_bad ||= $api_token_cookie && ($api_token_cookie ne $cookie->{api_token}) if $api_token_cookie;
616 $cookie_is_bad ||= $cookie->{ip_address} ne $ENV{REMOTE_ADDR} if !$api_token_cookie;
617 if ($cookie_is_bad) {
618 $self->destroy_session();
619 $main::lxdebug->leave_sub();
620 return $self->session_restore_result($cookie ? SESSION_EXPIRED() : SESSION_NONE());
623 if ($self->{column_information}->has('auto_restore')) {
624 $self->_load_with_auto_restore_column($dbh, $session_id);
626 $self->_load_without_auto_restore_column($dbh, $session_id);
629 $main::lxdebug->leave_sub();
631 return $self->session_restore_result(SESSION_OK());
634 sub session_restore_result {
637 $self->{session_restore_result} = $_[0];
639 return $self->{session_restore_result};
642 sub _load_without_auto_restore_column {
643 my ($self, $dbh, $session_id) = @_;
646 SELECT sess_key, sess_value
647 FROM auth.session_content
648 WHERE (session_id = ?)
650 my $sth = prepare_execute_query($::form, $dbh, $query, $session_id);
652 while (my $ref = $sth->fetchrow_hashref) {
653 my $value = SL::Auth::SessionValue->new(auth => $self,
654 key => $ref->{sess_key},
655 value => $ref->{sess_value},
657 $self->{SESSION}->{ $ref->{sess_key} } = $value;
659 next if defined $::form->{$ref->{sess_key}};
661 my $data = $value->get;
662 $::form->{$ref->{sess_key}} = $data if $value->{auto_restore} || !ref $data;
666 sub _load_with_auto_restore_column {
667 my ($self, $dbh, $session_id) = @_;
669 my $auto_restore_keys = join ', ', map { "'${_}'" } qw(login password rpw);
672 SELECT sess_key, sess_value, auto_restore
673 FROM auth.session_content
674 WHERE (session_id = ?)
676 OR sess_key IN (${auto_restore_keys}))
678 my $sth = prepare_execute_query($::form, $dbh, $query, $session_id);
680 while (my $ref = $sth->fetchrow_hashref) {
681 my $value = SL::Auth::SessionValue->new(auth => $self,
682 key => $ref->{sess_key},
683 value => $ref->{sess_value},
684 auto_restore => $ref->{auto_restore},
686 $self->{SESSION}->{ $ref->{sess_key} } = $value;
688 next if defined $::form->{$ref->{sess_key}};
690 my $data = $value->get;
691 $::form->{$ref->{sess_key}} = $data if $value->{auto_restore} || !ref $data;
698 FROM auth.session_content
699 WHERE (session_id = ?)
700 AND NOT COALESCE(auto_restore, FALSE)
701 AND (sess_key NOT IN (${auto_restore_keys}))
703 $sth = prepare_execute_query($::form, $dbh, $query, $session_id);
705 while (my $ref = $sth->fetchrow_hashref) {
706 my $value = SL::Auth::SessionValue->new(auth => $self,
707 key => $ref->{sess_key});
708 $self->{SESSION}->{ $ref->{sess_key} } = $value;
712 sub destroy_session {
713 $main::lxdebug->enter_sub();
718 my $dbh = $self->dbconnect();
722 do_query($main::form, $dbh, qq|DELETE FROM auth.session_content WHERE session_id = ?|, $session_id);
723 do_query($main::form, $dbh, qq|DELETE FROM auth.session WHERE id = ?|, $session_id);
727 SL::SessionFile->destroy_session($session_id);
730 $self->{SESSION} = { };
733 $main::lxdebug->leave_sub();
736 sub active_session_ids {
738 my $dbh = $self->dbconnect;
740 my $query = qq|SELECT id FROM auth.session|;
742 my @ids = selectall_array_query($::form, $dbh, $query);
747 sub expire_sessions {
748 $main::lxdebug->enter_sub();
752 $main::lxdebug->leave_sub and return if !$self->session_tables_present;
754 my $dbh = $self->dbconnect();
756 my $query = qq|SELECT id
758 WHERE (mtime < (now() - '$self->{session_timeout}m'::interval))|;
760 my @ids = selectall_array_query($::form, $dbh, $query);
765 SL::SessionFile->destroy_session($_) for @ids;
767 $query = qq|DELETE FROM auth.session_content
768 WHERE session_id IN (| . join(', ', ('?') x scalar(@ids)) . qq|)|;
769 do_query($main::form, $dbh, $query, @ids);
771 $query = qq|DELETE FROM auth.session
772 WHERE id IN (| . join(', ', ('?') x scalar(@ids)) . qq|)|;
773 do_query($main::form, $dbh, $query, @ids);
778 $main::lxdebug->leave_sub();
781 sub _create_session_id {
782 $main::lxdebug->enter_sub();
785 map { push @data, int(rand() * 255); } (1..32);
787 my $id = md5_hex(pack 'C*', @data);
789 $main::lxdebug->leave_sub();
794 sub create_or_refresh_session {
795 $session_id ||= shift->_create_session_id;
799 $::lxdebug->enter_sub;
801 my $provided_dbh = shift;
803 my $dbh = $provided_dbh || $self->dbconnect(1);
805 $::lxdebug->leave_sub && return unless $dbh && $session_id;
807 $dbh->begin_work unless $provided_dbh;
809 # If this fails then the "auth" schema might not exist yet, e.g. if
810 # the admin is just trying to create the auth database.
811 if (!$dbh->do(qq|LOCK auth.session_content|)) {
812 $dbh->rollback unless $provided_dbh;
813 $::lxdebug->leave_sub;
817 my @unfetched_keys = map { $_->{key} }
818 grep { ! $_->{fetched} }
819 values %{ $self->{SESSION} };
820 # $::lxdebug->dump(0, "unfetched_keys", [ sort @unfetched_keys ]);
821 # $::lxdebug->dump(0, "all keys", [ sort map { $_->{key} } values %{ $self->{SESSION} } ]);
822 my $query = qq|DELETE FROM auth.session_content WHERE (session_id = ?)|;
823 $query .= qq| AND (sess_key NOT IN (| . join(', ', ('?') x scalar @unfetched_keys) . qq|))| if @unfetched_keys;
825 do_query($::form, $dbh, $query, $session_id, @unfetched_keys);
827 my ($id) = selectrow_query($::form, $dbh, qq|SELECT id FROM auth.session WHERE id = ?|, $session_id);
830 do_query($::form, $dbh, qq|UPDATE auth.session SET mtime = now() WHERE id = ?|, $session_id);
832 do_query($::form, $dbh, qq|INSERT INTO auth.session (id, ip_address, mtime) VALUES (?, ?, now())|, $session_id, $ENV{REMOTE_ADDR});
835 if ($self->{column_information}->has('api_token', 'session')) {
836 my ($stored_api_token) = $dbh->selectrow_array(qq|SELECT api_token FROM auth.session WHERE id = ?|, undef, $session_id);
837 do_query($::form, $dbh, qq|UPDATE auth.session SET api_token = ? WHERE id = ?|, $self->_create_session_id, $session_id) unless $stored_api_token;
840 my @values_to_save = grep { $_->{fetched} }
841 values %{ $self->{SESSION} };
842 if (@values_to_save) {
843 my ($columns, $placeholders) = ('', '');
844 my $auto_restore = $self->{column_information}->has('auto_restore');
847 $columns .= ', auto_restore';
848 $placeholders .= ', ?';
851 $query = qq|INSERT INTO auth.session_content (session_id, sess_key, sess_value ${columns}) VALUES (?, ?, ? ${placeholders})|;
852 my $sth = prepare_query($::form, $dbh, $query);
854 foreach my $value (@values_to_save) {
855 my @values = ($value->{key}, $value->get_dumped);
856 push @values, $value->{auto_restore} if $auto_restore;
858 do_statement($::form, $sth, $query, $session_id, @values);
864 $dbh->commit() unless $provided_dbh;
865 $::lxdebug->leave_sub;
868 sub set_session_value {
869 $main::lxdebug->enter_sub();
874 $self->{SESSION} ||= { };
877 my $key = shift @params;
879 if (ref $key eq 'HASH') {
880 $self->{SESSION}->{ $key->{key} } = SL::Auth::SessionValue->new(key => $key->{key},
881 value => $key->{value},
882 auto_restore => $key->{auto_restore});
885 my $value = shift @params;
886 $self->{SESSION}->{ $key } = SL::Auth::SessionValue->new(key => $key,
891 $main::lxdebug->leave_sub();
896 sub delete_session_value {
897 $main::lxdebug->enter_sub();
901 $self->{SESSION} ||= { };
902 delete @{ $self->{SESSION} }{ @_ };
904 $main::lxdebug->leave_sub();
909 sub get_session_value {
910 $main::lxdebug->enter_sub();
913 my $data = $self->{SESSION} && $self->{SESSION}->{ $_[0] } ? $self->{SESSION}->{ $_[0] }->get : undef;
915 $main::lxdebug->leave_sub();
920 sub create_unique_sesion_value {
921 my ($self, $value, %params) = @_;
923 $self->{SESSION} ||= { };
925 my @now = gettimeofday();
926 my $key = "$$-" . ($now[0] * 1000000 + $now[1]) . "-";
927 $self->{unique_counter} ||= 0;
931 $self->{unique_counter}++;
932 $hashed_key = md5_hex($key . $self->{unique_counter});
933 } while (exists $self->{SESSION}->{$hashed_key});
935 $self->set_session_value($hashed_key => $value);
940 sub save_form_in_session {
941 my ($self, %params) = @_;
943 my $form = delete($params{form}) || $::form;
944 my $non_scalars = delete $params{non_scalars};
947 my %skip_keys = map { ( $_ => 1 ) } (qw(login password stylesheet version titlebar), @{ $params{skip_keys} || [] });
949 foreach my $key (grep { !$skip_keys{$_} } keys %{ $form }) {
950 $data->{$key} = $form->{$key} if !ref($form->{$key}) || $non_scalars;
953 return $self->create_unique_sesion_value($data, %params);
956 sub restore_form_from_session {
957 my ($self, $key, %params) = @_;
959 my $data = $self->get_session_value($key);
960 return $self unless $data;
962 my $form = delete($params{form}) || $::form;
963 my $clobber = exists $params{clobber} ? $params{clobber} : 1;
965 map { $form->{$_} = $data->{$_} if $clobber || !exists $form->{$_} } keys %{ $data };
970 sub set_cookie_environment_variable {
972 $ENV{HTTP_COOKIE} = $self->get_session_cookie_name() . "=${session_id}";
975 sub get_session_cookie_name {
976 my ($self, %params) = @_;
978 $params{type} ||= 'id';
979 my $name = $self->{cookie_name} || 'lx_office_erp_session_id';
980 $name .= '_api_token' if $params{type} eq 'api_token';
989 sub get_api_token_cookie {
992 $::request->{cgi}->cookie($self->get_session_cookie_name(type => 'api_token'));
995 sub session_tables_present {
996 $main::lxdebug->enter_sub();
1000 # Only re-check for the presence of auth tables if either the check
1001 # hasn't been done before of if they weren't present.
1002 if ($self->{session_tables_present}) {
1003 $main::lxdebug->leave_sub();
1004 return $self->{session_tables_present};
1007 my $dbh = $self->dbconnect(1);
1010 $main::lxdebug->leave_sub();
1017 WHERE (schemaname = 'auth')
1018 AND (tablename IN ('session', 'session_content'))|;
1020 my ($count) = selectrow_query($main::form, $dbh, $query);
1022 $self->{session_tables_present} = 2 == $count;
1024 $main::lxdebug->leave_sub();
1026 return $self->{session_tables_present};
1029 # --------------------------------------
1031 sub all_rights_full {
1032 my $locale = $main::locale;
1035 ["--crm", $locale->text("CRM optional software")],
1036 ["crm_search", $locale->text("CRM search")],
1037 ["crm_new", $locale->text("CRM create customers, vendors and contacts")],
1038 ["crm_service", $locale->text("CRM services")],
1039 ["crm_admin", $locale->text("CRM admin")],
1040 ["crm_adminuser", $locale->text("CRM user")],
1041 ["crm_adminstatus", $locale->text("CRM status")],
1042 ["crm_email", $locale->text("CRM send email")],
1043 ["crm_termin", $locale->text("CRM termin")],
1044 ["crm_opportunity", $locale->text("CRM opportunity")],
1045 ["crm_knowhow", $locale->text("CRM know how")],
1046 ["crm_follow", $locale->text("CRM follow up")],
1047 ["crm_notices", $locale->text("CRM notices")],
1048 ["crm_other", $locale->text("CRM other")],
1049 ["--master_data", $locale->text("Master Data")],
1050 ["customer_vendor_edit", $locale->text("Create customers and vendors. Edit all vendors. Edit only customers where salesman equals employee (login)")],
1051 ["customer_vendor_all_edit", $locale->text("Create customers and vendors. Edit all vendors. Edit all customers")],
1052 ["part_service_assembly_edit", $locale->text("Create and edit parts, services, assemblies")],
1053 ["part_service_assembly_details", $locale->text("Show details and reports of parts, services, assemblies")],
1054 ["project_edit", $locale->text("Create and edit projects")],
1055 ["--ar", $locale->text("AR")],
1056 ["sales_quotation_edit", $locale->text("Create and edit sales quotations")],
1057 ["sales_order_edit", $locale->text("Create and edit sales orders")],
1058 ["sales_delivery_order_edit", $locale->text("Create and edit sales delivery orders")],
1059 ["invoice_edit", $locale->text("Create and edit invoices and credit notes")],
1060 ["dunning_edit", $locale->text("Create and edit dunnings")],
1061 ["sales_all_edit", $locale->text("View/edit all employees sales documents")],
1062 ["edit_prices", $locale->text("Edit prices and discount (if not used, textfield is ONLY set readonly)")],
1063 ["--ap", $locale->text("AP")],
1064 ["request_quotation_edit", $locale->text("Create and edit RFQs")],
1065 ["purchase_order_edit", $locale->text("Create and edit purchase orders")],
1066 ["purchase_delivery_order_edit", $locale->text("Create and edit purchase delivery orders")],
1067 ["vendor_invoice_edit", $locale->text("Create and edit vendor invoices")],
1068 ["--warehouse_management", $locale->text("Warehouse management")],
1069 ["warehouse_contents", $locale->text("View warehouse content")],
1070 ["warehouse_management", $locale->text("Warehouse management")],
1071 ["--general_ledger_cash", $locale->text("General ledger and cash")],
1072 ["general_ledger", $locale->text("Transactions, AR transactions, AP transactions")],
1073 ["datev_export", $locale->text("DATEV Export")],
1074 ["cash", $locale->text("Receipt, payment, reconciliation")],
1075 ["--reports", $locale->text('Reports')],
1076 ["report", $locale->text('All reports')],
1077 ["advance_turnover_tax_return", $locale->text('Advance turnover tax return')],
1078 ["--batch_printing", $locale->text("Batch Printing")],
1079 ["batch_printing", $locale->text("Batch Printing")],
1080 ["--configuration", $locale->text("Configuration")],
1081 ["config", $locale->text("Change kivitendo installation settings (most entries in the 'System' menu)")],
1082 ["admin", $locale->text("Client administration: configuration, editing templates, task server control, background jobs (remaining entries in the 'System' menu)")],
1083 ["--others", $locale->text("Others")],
1084 ["email_bcc", $locale->text("May set the BCC field when sending emails")],
1085 ["productivity", $locale->text("Productivity")],
1086 ["display_admin_link", $locale->text("Show administration link")],
1093 return grep !/^--/, map { $_->[0] } all_rights_full();
1097 $main::lxdebug->enter_sub();
1101 my $form = $main::form;
1103 my $dbh = $self->dbconnect();
1105 my $query = 'SELECT * FROM auth."group"';
1106 my $sth = prepare_execute_query($form, $dbh, $query);
1110 while ($row = $sth->fetchrow_hashref()) {
1111 $groups->{$row->{id}} = $row;
1115 $query = 'SELECT * FROM auth.user_group WHERE group_id = ?';
1116 $sth = prepare_query($form, $dbh, $query);
1118 foreach $group (values %{$groups}) {
1121 do_statement($form, $sth, $query, $group->{id});
1123 while ($row = $sth->fetchrow_hashref()) {
1124 push @members, $row->{user_id};
1126 $group->{members} = [ uniq @members ];
1130 $query = 'SELECT * FROM auth.group_rights WHERE group_id = ?';
1131 $sth = prepare_query($form, $dbh, $query);
1133 foreach $group (values %{$groups}) {
1134 $group->{rights} = {};
1136 do_statement($form, $sth, $query, $group->{id});
1138 while ($row = $sth->fetchrow_hashref()) {
1139 $group->{rights}->{$row->{right}} |= $row->{granted};
1142 map { $group->{rights}->{$_} = 0 if (!defined $group->{rights}->{$_}); } all_rights();
1146 $main::lxdebug->leave_sub();
1152 $main::lxdebug->enter_sub();
1157 my $form = $main::form;
1158 my $dbh = $self->dbconnect();
1162 my ($query, $sth, $row, $rights);
1164 if (!$group->{id}) {
1165 ($group->{id}) = selectrow_query($form, $dbh, qq|SELECT nextval('auth.group_id_seq')|);
1167 $query = qq|INSERT INTO auth."group" (id, name, description) VALUES (?, '', '')|;
1168 do_query($form, $dbh, $query, $group->{id});
1171 do_query($form, $dbh, qq|UPDATE auth."group" SET name = ?, description = ? WHERE id = ?|, map { $group->{$_} } qw(name description id));
1173 do_query($form, $dbh, qq|DELETE FROM auth.user_group WHERE group_id = ?|, $group->{id});
1175 $query = qq|INSERT INTO auth.user_group (user_id, group_id) VALUES (?, ?)|;
1176 $sth = prepare_query($form, $dbh, $query);
1178 foreach my $user_id (uniq @{ $group->{members} }) {
1179 do_statement($form, $sth, $query, $user_id, $group->{id});
1183 do_query($form, $dbh, qq|DELETE FROM auth.group_rights WHERE group_id = ?|, $group->{id});
1185 $query = qq|INSERT INTO auth.group_rights (group_id, "right", granted) VALUES (?, ?, ?)|;
1186 $sth = prepare_query($form, $dbh, $query);
1188 foreach my $right (keys %{ $group->{rights} }) {
1189 do_statement($form, $sth, $query, $group->{id}, $right, $group->{rights}->{$right} ? 't' : 'f');
1195 $main::lxdebug->leave_sub();
1199 $main::lxdebug->enter_sub();
1204 my $form = $main::form;
1206 my $dbh = $self->dbconnect();
1209 do_query($form, $dbh, qq|DELETE FROM auth.user_group WHERE group_id = ?|, $id);
1210 do_query($form, $dbh, qq|DELETE FROM auth.group_rights WHERE group_id = ?|, $id);
1211 do_query($form, $dbh, qq|DELETE FROM auth."group" WHERE id = ?|, $id);
1215 $main::lxdebug->leave_sub();
1218 sub evaluate_rights_ary {
1219 $main::lxdebug->enter_sub(2);
1226 foreach my $el (@{$ary}) {
1227 if (ref $el eq "ARRAY") {
1228 if ($action eq '|') {
1229 $value |= evaluate_rights_ary($el);
1231 $value &= evaluate_rights_ary($el);
1234 } elsif (($el eq '&') || ($el eq '|')) {
1237 } elsif ($action eq '|') {
1246 $main::lxdebug->leave_sub(2);
1251 sub _parse_rights_string {
1252 $main::lxdebug->enter_sub(2);
1262 push @stack, $cur_ary;
1264 while ($access =~ m/^([a-z_0-9]+|\||\&|\(|\)|\s+)/) {
1266 substr($access, 0, length $1) = "";
1268 next if ($token =~ /\s/);
1270 if ($token eq "(") {
1271 my $new_cur_ary = [];
1272 push @stack, $new_cur_ary;
1273 push @{$cur_ary}, $new_cur_ary;
1274 $cur_ary = $new_cur_ary;
1276 } elsif ($token eq ")") {
1280 $main::lxdebug->leave_sub(2);
1284 $cur_ary = $stack[-1];
1286 } elsif (($token eq "|") || ($token eq "&")) {
1287 push @{$cur_ary}, $token;
1290 push @{$cur_ary}, $self->{RIGHTS}->{$login}->{$token} * 1;
1294 my $result = ($access || (1 < scalar @stack)) ? 0 : evaluate_rights_ary($stack[0]);
1296 $main::lxdebug->leave_sub(2);
1302 $main::lxdebug->enter_sub(2);
1307 my $default = shift;
1309 $self->{FULL_RIGHTS} ||= { };
1310 $self->{FULL_RIGHTS}->{$login} ||= { };
1312 if (!defined $self->{FULL_RIGHTS}->{$login}->{$right}) {
1313 $self->{RIGHTS} ||= { };
1314 $self->{RIGHTS}->{$login} ||= $self->load_rights_for_user($login);
1316 $self->{FULL_RIGHTS}->{$login}->{$right} = $self->_parse_rights_string($login, $right);
1319 my $granted = $self->{FULL_RIGHTS}->{$login}->{$right};
1320 $granted = $default if (!defined $granted);
1322 $main::lxdebug->leave_sub(2);
1328 $::lxdebug->enter_sub(2);
1329 my ($self, $right, $dont_abort) = @_;
1331 if ($self->check_right($::myconfig{login}, $right)) {
1332 $::lxdebug->leave_sub(2);
1337 delete $::form->{title};
1338 $::form->show_generic_error($::locale->text("You do not have the permissions to access this function."));
1341 $::lxdebug->leave_sub(2);
1346 sub load_rights_for_user {
1347 $::lxdebug->enter_sub;
1349 my ($self, $login) = @_;
1350 my $dbh = $self->dbconnect;
1351 my ($query, $sth, $row, $rights);
1353 $rights = { map { $_ => 0 } all_rights() };
1356 qq|SELECT gr."right", gr.granted
1357 FROM auth.group_rights gr
1360 FROM auth.user_group ug
1361 LEFT JOIN auth."user" u ON (ug.user_id = u.id)
1365 FROM auth.clients_groups cg
1366 WHERE cg.client_id = ?)|;
1368 $sth = prepare_execute_query($::form, $dbh, $query, $login, $self->client->{id});
1370 while ($row = $sth->fetchrow_hashref()) {
1371 $rights->{$row->{right}} |= $row->{granted};
1375 $::lxdebug->leave_sub;
1389 SL::Auth - Authentication and session handling
1395 =item C<set_session_value @values>
1397 =item C<set_session_value %values>
1399 Store all values of C<@values> or C<%values> in the session. Each
1400 member of C<@values> is tested if it is a hash reference. If it is
1401 then it must contain the keys C<key> and C<value> and can optionally
1402 contain the key C<auto_restore>. In this case C<value> is associated
1403 with C<key> and restored to C<$::form> upon the next request
1404 automatically if C<auto_restore> is trueish or if C<value> is a scalar
1407 If the current member of C<@values> is not a hash reference then it
1408 will be used as the C<key> and the next entry of C<@values> is used as
1409 the C<value> to store. In this case setting C<auto_restore> is not
1412 Therefore the following two invocations are identical:
1414 $::auth-E<gt>set_session_value(name =E<gt> "Charlie");
1415 $::auth-E<gt>set_session_value({ key =E<gt> "name", value =E<gt> "Charlie" });
1417 All of these values are copied back into C<$::form> for the next
1418 request automatically if they're scalar values or if they have
1419 C<auto_restore> set to trueish.
1421 The values can be any Perl structure. They are stored as YAML dumps.
1423 =item C<get_session_value $key>
1425 Retrieve a value from the session. Returns C<undef> if the value
1428 =item C<create_unique_sesion_value $value, %params>
1430 Create a unique key in the session and store C<$value>
1433 Returns the key created in the session.
1435 =item C<save_session>
1437 Stores the session values in the database. This is the only function
1438 that actually stores stuff in the database. Neither the various
1439 setters nor the deleter access the database.
1441 =item <save_form_in_session %params>
1443 Stores the content of C<$params{form}> (default: C<$::form>) in the
1444 session using L</create_unique_sesion_value>.
1446 If C<$params{non_scalars}> is trueish then non-scalar values will be
1447 stored as well. Default is to only store scalar values.
1449 The following keys will never be saved: C<login>, C<password>,
1450 C<stylesheet>, C<titlebar>, C<version>. Additional keys not to save
1451 can be given as an array ref in C<$params{skip_keys}>.
1453 Returns the unique key under which the form is stored.
1455 =item <restore_form_from_session $key, %params>
1457 Restores the form from the session into C<$params{form}> (default:
1460 If C<$params{clobber}> is falsish then existing values with the same
1461 key in C<$params{form}> will not be overwritten. C<$params{clobber}>
1474 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>