5 use Digest::MD5 qw(md5_hex);
7 use Time::HiRes qw(gettimeofday);
8 use List::MoreUtils qw(uniq);
10 use Regexp::IPv6 qw($IPv6_re);
12 use SL::Auth::ColumnInformation;
13 use SL::Auth::Constants qw(:all);
16 use SL::Auth::Password;
17 use SL::Auth::SessionValue;
23 use SL::DBUtils qw(do_query do_statement prepare_execute_query prepare_query selectall_array_query selectrow_query);
27 use constant SESSION_KEY_ROOT_AUTH => 'session_auth_status_root';
28 use constant SESSION_KEY_USER_AUTH => 'session_auth_status_user';
30 use Rose::Object::MakeMethods::Generic (
31 scalar => [ qw(client) ],
36 my ($type, %params) = @_;
37 my $self = bless {}, $type;
39 $self->_read_auth_config(%params);
46 my ($self, %params) = @_;
48 $self->{SESSION} = { };
49 $self->{FULL_RIGHTS} = { };
50 $self->{RIGHTS} = { };
51 $self->{unique_counter} = 0;
52 $self->{column_information} = SL::Auth::ColumnInformation->new(auth => $self);
56 my ($self, %params) = @_;
58 $self->{SESSION} = { };
59 $self->{FULL_RIGHTS} = { };
60 $self->{RIGHTS} = { };
61 $self->{unique_counter} = 0;
63 if ($self->is_db_connected) {
64 # reset is called during request shutdown already. In case of a
65 # completely new auth DB this would fail and generate an error
66 # message even if the user is currently trying to create said auth
67 # DB. Therefore only fetch the column information if a connection
68 # has been established.
69 $self->{column_information} = SL::Auth::ColumnInformation->new(auth => $self);
70 $self->{column_information}->_fetch;
72 delete $self->{column_information};
75 $self->{authenticator}->reset;
81 my ($self, $id_or_name) = @_;
85 return undef unless $id_or_name;
87 my $column = $id_or_name =~ m/^\d+$/ ? 'id' : 'name';
88 my $dbh = $self->dbconnect;
90 return undef unless $dbh;
92 $self->client($dbh->selectrow_hashref(qq|SELECT * FROM auth.clients WHERE ${column} = ?|, undef, $id_or_name));
97 sub get_default_client_id {
100 my $dbh = $self->dbconnect;
104 my $row = $dbh->selectrow_hashref(qq|SELECT id FROM auth.clients WHERE is_default = TRUE LIMIT 1|);
106 return $row->{id} if $row;
112 $self->{dbh}->disconnect() if ($self->{dbh});
115 # form isn't loaded yet, so auth needs it's own error.
117 $::lxdebug->show_backtrace();
119 my ($self, @msg) = @_;
120 if ($ENV{HTTP_USER_AGENT}) {
121 print Form->create_http_response(content_type => 'text/html');
122 print "<pre>", join ('<br>', @msg), "</pre>";
124 print STDERR "Error: @msg\n";
126 $::dispatcher->end_request;
129 sub _read_auth_config {
130 my ($self, %params) = @_;
132 map { $self->{$_} = $::lx_office_conf{authentication}->{$_} } keys %{ $::lx_office_conf{authentication} };
134 # Prevent password leakage to log files when dumping Auth instances.
135 $self->{admin_password} = sub { $::lx_office_conf{authentication}->{admin_password} };
137 if ($params{unit_tests_database}) {
138 $self->{DB_config} = $::lx_office_conf{'testing/database'};
139 $self->{module} = 'DB';
142 $self->{DB_config} = $::lx_office_conf{'authentication/database'};
143 $self->{LDAP_config} = $::lx_office_conf{'authentication/ldap'};
146 if ($self->{module} eq 'DB') {
147 $self->{authenticator} = SL::Auth::DB->new($self);
149 } elsif ($self->{module} eq 'LDAP') {
150 $self->{authenticator} = SL::Auth::LDAP->new($self);
153 if (!$self->{authenticator}) {
154 my $locale = Locale->new('en');
155 $self->mini_error($locale->text('No or an unknown authenticantion module specified in "config/kivitendo.conf".'));
158 my $cfg = $self->{DB_config};
161 my $locale = Locale->new('en');
162 $self->mini_error($locale->text('config/kivitendo.conf: Key "DB_config" is missing.'));
165 if (!$cfg->{host} || !$cfg->{db} || !$cfg->{user}) {
166 my $locale = Locale->new('en');
167 $self->mini_error($locale->text('config/kivitendo.conf: Missing parameters in "authentication/database". Required parameters are "host", "db" and "user".'));
170 $self->{authenticator}->verify_config();
172 $self->{session_timeout} *= 1;
173 $self->{session_timeout} = 8 * 60 if (!$self->{session_timeout});
176 sub has_access_to_client {
177 my ($self, $login) = @_;
179 return 0 if !$self->client || !$self->client->{id};
183 FROM auth.clients_users cu
184 LEFT JOIN auth."user" u ON (cu.user_id = u.id)
186 AND (cu.client_id = ?)
189 my ($has_access) = $self->dbconnect->selectrow_array($sql, undef, $login, $self->client->{id});
193 sub authenticate_root {
194 my ($self, $password) = @_;
196 my $session_root_auth = $self->get_session_value(SESSION_KEY_ROOT_AUTH());
197 if (defined $session_root_auth && $session_root_auth == OK) {
201 if (!defined $password) {
205 my $admin_password = SL::Auth::Password->hash_if_unhashed(login => 'root', password => $self->{admin_password}->());
206 $password = SL::Auth::Password->hash(login => 'root', password => $password, stored_password => $admin_password);
208 my $result = $password eq $admin_password ? OK : ERR_PASSWORD;
209 $self->set_session_value(SESSION_KEY_ROOT_AUTH() => $result);
215 my ($self, $login, $password) = @_;
217 if (!$self->client || !$self->has_access_to_client($login)) {
221 my $session_auth = $self->get_session_value(SESSION_KEY_USER_AUTH());
222 if (defined $session_auth && $session_auth == OK) {
226 if (!defined $password) {
230 my $result = $login ? $self->{authenticator}->authenticate($login, $password) : ERR_USER;
231 $self->set_session_value(SESSION_KEY_USER_AUTH() => $result, login => $login, client_id => $self->client->{id});
235 sub punish_wrong_login {
236 my $failed_login_penalty = ($::lx_office_conf{authentication} || {})->{failed_login_penalty};
237 sleep $failed_login_penalty if $failed_login_penalty;
240 sub get_stored_password {
241 my ($self, $login) = @_;
243 my $dbh = $self->dbconnect;
245 return undef unless $dbh;
247 my $query = qq|SELECT password FROM auth."user" WHERE login = ?|;
248 my ($stored_password) = $dbh->selectrow_array($query, undef, $login);
250 return $stored_password;
255 my $may_fail = shift;
261 my $cfg = $self->{DB_config};
262 my $dsn = 'dbi:Pg:dbname=' . $cfg->{db} . ';host=' . $cfg->{host};
265 $dsn .= ';port=' . $cfg->{port};
268 $main::lxdebug->message(LXDebug->DEBUG1, "Auth::dbconnect DSN: $dsn");
270 $self->{dbh} = SL::DBConnect->connect($dsn, $cfg->{user}, $cfg->{password}, { pg_enable_utf8 => 1, AutoCommit => 1 });
272 if (!$may_fail && !$self->{dbh}) {
274 $main::form->error($main::locale->text('The connection to the authentication database failed:') . "\n" . $DBI::errstr);
284 $self->{dbh}->disconnect();
289 sub is_db_connected {
291 return !!$self->{dbh};
295 my ($self, $dbh) = @_;
297 $dbh ||= $self->dbconnect();
298 my $query = qq|SELECT COUNT(*) FROM pg_tables WHERE (schemaname = 'auth') AND (tablename = 'user')|;
300 my ($count) = $dbh->selectrow_array($query);
308 my $dbh = $self->dbconnect(1);
313 sub create_database {
317 my $cfg = $self->{DB_config};
319 if (!$params{superuser}) {
320 $params{superuser} = $cfg->{user};
321 $params{superuser_password} = $cfg->{password};
324 $params{template} ||= 'template0';
325 $params{template} =~ s|[^a-zA-Z0-9_\-]||g;
327 my $dsn = 'dbi:Pg:dbname=template1;host=' . $cfg->{host};
330 $dsn .= ';port=' . $cfg->{port};
333 $main::lxdebug->message(LXDebug->DEBUG1(), "Auth::create_database DSN: $dsn");
335 my $dbh = SL::DBConnect->connect($dsn, $params{superuser}, $params{superuser_password}, { pg_enable_utf8 => 1 });
338 $main::form->error($main::locale->text('The connection to the template database failed:') . "\n" . $DBI::errstr);
341 my $query = qq|CREATE DATABASE "$cfg->{db}" OWNER "$cfg->{user}" TEMPLATE "$params{template}" ENCODING 'UNICODE'|;
343 $main::lxdebug->message(LXDebug->DEBUG1(), "Auth::create_database query: $query");
348 my $error = $dbh->errstr();
350 $query = qq|SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = 'template0'|;
351 my ($cluster_encoding) = $dbh->selectrow_array($query);
353 if ($cluster_encoding && ($cluster_encoding !~ m/^(?:UTF-?8|UNICODE)$/i)) {
354 $error = $::locale->text('Your PostgreSQL installationen does not use Unicode as its encoding. This is not supported anymore.');
359 $main::form->error($main::locale->text('The creation of the authentication database failed:') . "\n" . $error);
367 my $dbh = $self->dbconnect();
370 SL::DBUpgrade2->new(form => $::form)->process_query($dbh, 'sql/auth_db.sql');
378 my $form = $main::form;
380 my $dbh = $self->dbconnect();
382 my ($sth, $query, $user_id);
386 $query = qq|SELECT id FROM auth."user" WHERE login = ?|;
387 ($user_id) = selectrow_query($form, $dbh, $query, $login);
390 $query = qq|SELECT nextval('auth.user_id_seq')|;
391 ($user_id) = selectrow_query($form, $dbh, $query);
393 $query = qq|INSERT INTO auth."user" (id, login) VALUES (?, ?)|;
394 do_query($form, $dbh, $query, $user_id, $login);
397 $query = qq|DELETE FROM auth.user_config WHERE (user_id = ?)|;
398 do_query($form, $dbh, $query, $user_id);
400 $query = qq|INSERT INTO auth.user_config (user_id, cfg_key, cfg_value) VALUES (?, ?, ?)|;
401 $sth = prepare_query($form, $dbh, $query);
403 while (my ($cfg_key, $cfg_value) = each %params) {
404 next if ($cfg_key eq 'password');
406 do_statement($form, $sth, $query, $user_id, $cfg_key, $cfg_value);
412 sub can_change_password {
415 return $self->{authenticator}->can_change_password();
418 sub change_password {
419 my ($self, $login, $new_password) = @_;
421 my $result = $self->{authenticator}->change_password($login, $new_password);
429 my $dbh = $self->dbconnect();
430 my $query = qq|SELECT u.id, u.login, cfg.cfg_key, cfg.cfg_value, s.mtime AS last_action
432 FROM auth."user" AS u
434 LEFT JOIN auth.user_config AS cfg
435 ON (cfg.user_id = u.id)
437 LEFT JOIN auth.session_content AS sc_login
438 ON (sc_login.sess_key = 'login' AND sc_login.sess_value = ('--- ' \|\| u.login \|\| '\n'))
440 LEFT JOIN auth.session AS s
441 ON (s.id = sc_login.session_id)
443 my $sth = prepare_execute_query($main::form, $dbh, $query);
447 while (my $ref = $sth->fetchrow_hashref()) {
449 $users{$ref->{login}} ||= {
450 'login' => $ref->{login},
452 'last_action' => $ref->{last_action},
454 $users{$ref->{login}}->{$ref->{cfg_key}} = $ref->{cfg_value} if (($ref->{cfg_key} ne 'login') && ($ref->{cfg_key} ne 'id'));
463 my ($self, %params) = @_;
465 my $dbh = $self->dbconnect();
467 my (@where, @values);
468 if ($params{login}) {
469 push @where, 'u.login = ?';
470 push @values, $params{login};
473 push @where, 'u.id = ?';
474 push @values, $params{id};
476 my $where = join ' AND ', '1 = 1', @where;
477 my $query = qq|SELECT u.id, u.login, cfg.cfg_key, cfg.cfg_value
478 FROM auth.user_config cfg
479 LEFT JOIN auth."user" u ON (cfg.user_id = u.id)
481 my $sth = prepare_execute_query($main::form, $dbh, $query, @values);
485 while (my $ref = $sth->fetchrow_hashref()) {
486 $user_data{$ref->{cfg_key}} = $ref->{cfg_value};
487 @user_data{qw(id login)} = @{$ref}{qw(id login)};
490 # The XUL/XML & 'CSS new' backed menus have been removed.
491 my %menustyle_map = ( xml => 'new', v4 => 'v3' );
492 $user_data{menustyle} = $menustyle_map{lc($user_data{menustyle} || '')} || $user_data{menustyle};
494 # The 'Win2000.css' stylesheet has been removed.
495 $user_data{stylesheet} = 'kivitendo.css' if ($user_data{stylesheet} || '') =~ m/win2000/i;
497 # Set default language if selected language does not exist (anymore).
498 $user_data{countrycode} = $::lx_office_conf{system}->{language} unless $user_data{countrycode} && -d "locale/$user_data{countrycode}";
509 my $dbh = $self->dbconnect();
510 my ($id) = selectrow_query($main::form, $dbh, qq|SELECT id FROM auth."user" WHERE login = ?|, $login);
519 my $dbh = $self->dbconnect;
520 my $id = $self->get_user_id($login);
529 do_query($::form, $dbh, qq|DELETE FROM auth.user_group WHERE user_id = ?|, $id);
530 do_query($::form, $dbh, qq|DELETE FROM auth.user_config WHERE user_id = ?|, $id);
531 do_query($::form, $dbh, qq|DELETE FROM auth.user WHERE id = ?|, $id);
533 # TODO: SL::Auth::delete_user
534 # do_query($::form, $u_dbh, qq|UPDATE employee SET deleted = 't' WHERE login = ?|, $login) if $u_dbh && $user_db_exists;
539 # --------------------------------------
543 sub restore_session {
546 $session_id = $::request->{cgi}->cookie($self->get_session_cookie_name());
547 $session_id =~ s|[^0-9a-f]||g if $session_id;
549 $self->{SESSION} = { };
552 return $self->session_restore_result(SESSION_NONE());
555 my ($dbh, $query, $sth, $cookie, $ref, $form);
559 # Don't fail if the auth DB doesn't exist yet.
560 if (!( $dbh = $self->dbconnect(1) )) {
561 return $self->session_restore_result(SESSION_NONE());
564 # Don't fail if the "auth" schema doesn't exist yet, e.g. if the
565 # admin is creating the session tables at the moment.
566 $query = qq|SELECT *, (mtime < (now() - '$self->{session_timeout}m'::interval)) AS is_expired FROM auth.session WHERE id = ?|;
568 if (!($sth = $dbh->prepare($query)) || !$sth->execute($session_id)) {
569 $sth->finish if $sth;
570 return $self->session_restore_result(SESSION_NONE());
573 $cookie = $sth->fetchrow_hashref;
576 # The session ID provided is valid in the following cases:
577 # 1. session ID exists in the database
578 # 2. hasn't expired yet
579 # 3. if cookie for the API token is given: the cookie's value equal database column 'auth.session.api_token' for the session ID
580 $self->{api_token} = $cookie->{api_token} if $cookie;
581 my $api_token_cookie = $self->get_api_token_cookie;
582 my $cookie_is_bad = !$cookie || $cookie->{is_expired};
583 $cookie_is_bad ||= $api_token_cookie && ($api_token_cookie ne $cookie->{api_token}) if $api_token_cookie;
584 if ($cookie_is_bad) {
585 $self->destroy_session();
586 return $self->session_restore_result($cookie ? SESSION_EXPIRED() : SESSION_NONE());
589 if ($self->{column_information}->has('auto_restore')) {
590 $self->_load_with_auto_restore_column($dbh, $session_id);
592 $self->_load_without_auto_restore_column($dbh, $session_id);
595 return $self->session_restore_result(SESSION_OK());
598 sub session_restore_result {
601 $self->{session_restore_result} = $_[0];
603 return $self->{session_restore_result};
606 sub _load_without_auto_restore_column {
607 my ($self, $dbh, $session_id) = @_;
610 SELECT sess_key, sess_value
611 FROM auth.session_content
612 WHERE (session_id = ?)
614 my $sth = prepare_execute_query($::form, $dbh, $query, $session_id);
616 while (my $ref = $sth->fetchrow_hashref) {
617 my $value = SL::Auth::SessionValue->new(auth => $self,
618 key => $ref->{sess_key},
619 value => $ref->{sess_value},
621 $self->{SESSION}->{ $ref->{sess_key} } = $value;
623 next if defined $::form->{$ref->{sess_key}};
625 my $data = $value->get;
626 $::form->{$ref->{sess_key}} = $data if $value->{auto_restore} || !ref $data;
630 sub _load_with_auto_restore_column {
631 my ($self, $dbh, $session_id) = @_;
633 my %auto_restore_keys = map { $_ => 1 } qw(login password rpw client_id), SESSION_KEY_ROOT_AUTH, SESSION_KEY_USER_AUTH;
636 SELECT sess_key, sess_value, auto_restore
637 FROM auth.session_content
638 WHERE (session_id = ?)
640 my $sth = prepare_execute_query($::form, $dbh, $query, $session_id);
642 while (my $ref = $sth->fetchrow_hashref) {
643 if ($ref->{auto_restore} || $auto_restore_keys{$ref->{sess_key}}) {
644 my $value = SL::Auth::SessionValue->new(auth => $self,
645 key => $ref->{sess_key},
646 value => $ref->{sess_value},
647 auto_restore => $ref->{auto_restore},
649 $self->{SESSION}->{ $ref->{sess_key} } = $value;
651 next if defined $::form->{$ref->{sess_key}};
653 my $data = $value->get;
654 $::form->{$ref->{sess_key}} = $data if $value->{auto_restore} || !ref $data;
656 my $value = SL::Auth::SessionValue->new(auth => $self,
657 key => $ref->{sess_key});
658 $self->{SESSION}->{ $ref->{sess_key} } = $value;
665 sub destroy_session {
669 my $dbh = $self->dbconnect();
673 do_query($main::form, $dbh, qq|DELETE FROM auth.session_content WHERE session_id = ?|, $session_id);
674 do_query($main::form, $dbh, qq|DELETE FROM auth.session WHERE id = ?|, $session_id);
678 SL::SessionFile->destroy_session($session_id);
681 $self->{SESSION} = { };
685 sub active_session_ids {
687 my $dbh = $self->dbconnect;
689 my $query = qq|SELECT id FROM auth.session|;
691 my @ids = selectall_array_query($::form, $dbh, $query);
696 sub expire_sessions {
699 return if !$self->session_tables_present;
701 my $dbh = $self->dbconnect();
703 my $query = qq|SELECT id
705 WHERE (mtime < (now() - '$self->{session_timeout}m'::interval))|;
707 my @ids = selectall_array_query($::form, $dbh, $query);
712 SL::SessionFile->destroy_session($_) for @ids;
714 $query = qq|DELETE FROM auth.session_content
715 WHERE session_id IN (| . join(', ', ('?') x scalar(@ids)) . qq|)|;
716 do_query($main::form, $dbh, $query, @ids);
718 $query = qq|DELETE FROM auth.session
719 WHERE id IN (| . join(', ', ('?') x scalar(@ids)) . qq|)|;
720 do_query($main::form, $dbh, $query, @ids);
726 sub _create_session_id {
728 map { push @data, int(rand() * 255); } (1..32);
730 my $id = md5_hex(pack 'C*', @data);
735 sub create_or_refresh_session {
736 $session_id ||= shift->_create_session_id;
741 my $provided_dbh = shift;
743 my $dbh = $provided_dbh || $self->dbconnect(1);
745 return unless $dbh && $session_id;
747 $dbh->begin_work unless $provided_dbh;
749 # If this fails then the "auth" schema might not exist yet, e.g. if
750 # the admin is just trying to create the auth database.
751 if (!$dbh->do(qq|LOCK auth.session_content|)) {
752 $dbh->rollback unless $provided_dbh;
756 my @unfetched_keys = map { $_->{key} }
757 grep { ! $_->{fetched} }
758 values %{ $self->{SESSION} };
759 # $::lxdebug->dump(0, "unfetched_keys", [ sort @unfetched_keys ]);
760 # $::lxdebug->dump(0, "all keys", [ sort map { $_->{key} } values %{ $self->{SESSION} } ]);
761 my $query = qq|DELETE FROM auth.session_content WHERE (session_id = ?)|;
762 $query .= qq| AND (sess_key NOT IN (| . join(', ', ('?') x scalar @unfetched_keys) . qq|))| if @unfetched_keys;
764 do_query($::form, $dbh, $query, $session_id, @unfetched_keys);
766 my ($id) = selectrow_query($::form, $dbh, qq|SELECT id FROM auth.session WHERE id = ?|, $session_id);
769 do_query($::form, $dbh, qq|UPDATE auth.session SET mtime = now() WHERE id = ?|, $session_id);
771 do_query($::form, $dbh, qq|INSERT INTO auth.session (id, ip_address, mtime) VALUES (?, ?, now())|, $session_id, $ENV{REMOTE_ADDR});
774 if ($self->{column_information}->has('api_token', 'session')) {
775 my ($stored_api_token) = $dbh->selectrow_array(qq|SELECT api_token FROM auth.session WHERE id = ?|, undef, $session_id);
776 do_query($::form, $dbh, qq|UPDATE auth.session SET api_token = ? WHERE id = ?|, $self->_create_session_id, $session_id) unless $stored_api_token;
779 my @values_to_save = grep { $_->{fetched} }
780 values %{ $self->{SESSION} };
781 if (@values_to_save) {
782 my ($columns, $placeholders) = ('', '');
783 my $auto_restore = $self->{column_information}->has('auto_restore');
786 $columns .= ', auto_restore';
787 $placeholders .= ', ?';
790 $query = qq|INSERT INTO auth.session_content (session_id, sess_key, sess_value ${columns}) VALUES (?, ?, ? ${placeholders})|;
791 my $sth = prepare_query($::form, $dbh, $query);
793 foreach my $value (@values_to_save) {
794 my @values = ($value->{key}, $value->get_dumped);
795 push @values, $value->{auto_restore} if $auto_restore;
797 do_statement($::form, $sth, $query, $session_id, @values);
803 $dbh->commit() unless $provided_dbh;
806 sub set_session_value {
810 $self->{SESSION} ||= { };
813 my $key = shift @params;
815 if (ref $key eq 'HASH') {
816 $self->{SESSION}->{ $key->{key} } = SL::Auth::SessionValue->new(key => $key->{key},
817 value => $key->{value},
818 auto_restore => $key->{auto_restore});
821 my $value = shift @params;
822 $self->{SESSION}->{ $key } = SL::Auth::SessionValue->new(key => $key,
830 sub delete_session_value {
833 $self->{SESSION} ||= { };
834 delete @{ $self->{SESSION} }{ @_ };
839 sub get_session_value {
841 my $data = $self->{SESSION} && $self->{SESSION}->{ $_[0] } ? $self->{SESSION}->{ $_[0] }->get : undef;
846 sub create_unique_sesion_value {
847 my ($self, $value, %params) = @_;
849 $self->{SESSION} ||= { };
851 my @now = gettimeofday();
852 my $key = "$$-" . ($now[0] * 1000000 + $now[1]) . "-";
853 $self->{unique_counter} ||= 0;
857 $self->{unique_counter}++;
858 $hashed_key = md5_hex($key . $self->{unique_counter});
859 } while (exists $self->{SESSION}->{$hashed_key});
861 $self->set_session_value($hashed_key => $value);
866 sub save_form_in_session {
867 my ($self, %params) = @_;
869 my $form = delete($params{form}) || $::form;
870 my $non_scalars = delete $params{non_scalars};
873 my %skip_keys = map { ( $_ => 1 ) } (qw(login password stylesheet version titlebar), @{ $params{skip_keys} || [] });
875 foreach my $key (grep { !$skip_keys{$_} } keys %{ $form }) {
876 $data->{$key} = $form->{$key} if !ref($form->{$key}) || $non_scalars;
879 return $self->create_unique_sesion_value($data, %params);
882 sub restore_form_from_session {
883 my ($self, $key, %params) = @_;
885 my $data = $self->get_session_value($key);
886 return $self unless $data;
888 my $form = delete($params{form}) || $::form;
889 my $clobber = exists $params{clobber} ? $params{clobber} : 1;
891 map { $form->{$_} = $data->{$_} if $clobber || !exists $form->{$_} } keys %{ $data };
896 sub set_cookie_environment_variable {
898 $ENV{HTTP_COOKIE} = $self->get_session_cookie_name() . "=${session_id}";
901 sub get_session_cookie_name {
902 my ($self, %params) = @_;
904 $params{type} ||= 'id';
905 my $name = $self->{cookie_name} || 'lx_office_erp_session_id';
906 $name .= '_api_token' if $params{type} eq 'api_token';
915 sub get_api_token_cookie {
918 $::request->{cgi}->cookie($self->get_session_cookie_name(type => 'api_token'));
921 sub is_api_token_cookie_valid {
923 my $provided_api_token = $self->get_api_token_cookie;
924 return $self->{api_token} && $provided_api_token && ($self->{api_token} eq $provided_api_token);
927 sub _tables_present {
928 my ($self, @tables) = @_;
929 my $cache_key = join '_', @tables;
931 # Only re-check for the presence of auth tables if either the check
932 # hasn't been done before of if they weren't present.
933 return $self->{"$cache_key\_tables_present"} ||= do {
934 my $dbh = $self->dbconnect(1);
943 WHERE (schemaname = 'auth')
944 AND (tablename IN (@{[ join ', ', ('?') x @tables ]}))|;
946 my ($count) = selectrow_query($main::form, $dbh, $query, @tables);
948 scalar @tables == $count;
952 sub session_tables_present {
953 $_[0]->_tables_present('session', 'session_content');
956 sub master_rights_present {
957 $_[0]->_tables_present('master_rights');
960 # --------------------------------------
962 sub all_rights_full {
965 @{ $self->{master_rights} ||= do {
966 $self->dbconnect->selectall_arrayref("SELECT name, description, category FROM auth.master_rights ORDER BY position");
972 return map { $_->[0] } grep { !$_->[2] } $_[0]->all_rights_full;
978 my $form = $main::form;
980 my $dbh = $self->dbconnect();
982 my $query = 'SELECT * FROM auth."group"';
983 my $sth = prepare_execute_query($form, $dbh, $query);
987 while ($row = $sth->fetchrow_hashref()) {
988 $groups->{$row->{id}} = $row;
992 $query = 'SELECT * FROM auth.user_group WHERE group_id = ?';
993 $sth = prepare_query($form, $dbh, $query);
995 foreach $group (values %{$groups}) {
998 do_statement($form, $sth, $query, $group->{id});
1000 while ($row = $sth->fetchrow_hashref()) {
1001 push @members, $row->{user_id};
1003 $group->{members} = [ uniq @members ];
1007 $query = 'SELECT * FROM auth.group_rights WHERE group_id = ?';
1008 $sth = prepare_query($form, $dbh, $query);
1010 foreach $group (values %{$groups}) {
1011 $group->{rights} = {};
1013 do_statement($form, $sth, $query, $group->{id});
1015 while ($row = $sth->fetchrow_hashref()) {
1016 $group->{rights}->{$row->{right}} |= $row->{granted};
1019 map { $group->{rights}->{$_} = 0 if (!defined $group->{rights}->{$_}); } $self->all_rights;
1030 my $form = $main::form;
1031 my $dbh = $self->dbconnect();
1035 my ($query, $sth, $row, $rights);
1037 if (!$group->{id}) {
1038 ($group->{id}) = selectrow_query($form, $dbh, qq|SELECT nextval('auth.group_id_seq')|);
1040 $query = qq|INSERT INTO auth."group" (id, name, description) VALUES (?, '', '')|;
1041 do_query($form, $dbh, $query, $group->{id});
1044 do_query($form, $dbh, qq|UPDATE auth."group" SET name = ?, description = ? WHERE id = ?|, map { $group->{$_} } qw(name description id));
1046 do_query($form, $dbh, qq|DELETE FROM auth.user_group WHERE group_id = ?|, $group->{id});
1048 $query = qq|INSERT INTO auth.user_group (user_id, group_id) VALUES (?, ?)|;
1049 $sth = prepare_query($form, $dbh, $query);
1051 foreach my $user_id (uniq @{ $group->{members} }) {
1052 do_statement($form, $sth, $query, $user_id, $group->{id});
1056 do_query($form, $dbh, qq|DELETE FROM auth.group_rights WHERE group_id = ?|, $group->{id});
1058 $query = qq|INSERT INTO auth.group_rights (group_id, "right", granted) VALUES (?, ?, ?)|;
1059 $sth = prepare_query($form, $dbh, $query);
1061 foreach my $right (keys %{ $group->{rights} }) {
1062 do_statement($form, $sth, $query, $group->{id}, $right, $group->{rights}->{$right} ? 't' : 'f');
1073 my $form = $main::form;
1075 my $dbh = $self->dbconnect();
1078 do_query($form, $dbh, qq|DELETE FROM auth.user_group WHERE group_id = ?|, $id);
1079 do_query($form, $dbh, qq|DELETE FROM auth.group_rights WHERE group_id = ?|, $id);
1080 do_query($form, $dbh, qq|DELETE FROM auth."group" WHERE id = ?|, $id);
1085 sub evaluate_rights_ary {
1092 foreach my $el (@{$ary}) {
1093 if (ref $el eq "ARRAY") {
1094 my $val = evaluate_rights_ary($el);
1095 $val = !$val if $negate;
1097 if ($action eq '|') {
1103 } elsif (($el eq '&') || ($el eq '|')) {
1106 } elsif ($el eq '!') {
1109 } elsif ($action eq '|') {
1111 $val = !$val if $negate;
1117 $val = !$val if $negate;
1127 sub _parse_rights_string {
1136 push @stack, $cur_ary;
1138 while ($access =~ m/^([a-z_0-9]+|\||\&|\(|\)|\s+)/) {
1140 substr($access, 0, length $1) = "";
1142 next if ($token =~ /\s/);
1144 if ($token eq "(") {
1145 my $new_cur_ary = [];
1146 push @stack, $new_cur_ary;
1147 push @{$cur_ary}, $new_cur_ary;
1148 $cur_ary = $new_cur_ary;
1150 } elsif ($token eq ")") {
1157 $cur_ary = $stack[-1];
1159 } elsif (($token eq "|") || ($token eq "&")) {
1160 push @{$cur_ary}, $token;
1163 push @{$cur_ary}, ($self->{RIGHTS}->{$login}->{$token} // 0) * 1;
1167 my $result = ($access || (1 < scalar @stack)) ? 0 : evaluate_rights_ary($stack[0]);
1176 my $default = shift;
1178 $self->{FULL_RIGHTS} ||= { };
1179 $self->{FULL_RIGHTS}->{$login} ||= { };
1181 if (!defined $self->{FULL_RIGHTS}->{$login}->{$right}) {
1182 $self->{RIGHTS} ||= { };
1183 $self->{RIGHTS}->{$login} ||= $self->load_rights_for_user($login);
1185 $self->{FULL_RIGHTS}->{$login}->{$right} = $self->_parse_rights_string($login, $right);
1188 my $granted = $self->{FULL_RIGHTS}->{$login}->{$right};
1189 $granted = $default if (!defined $granted);
1195 my ($self, $right, $dont_abort) = @_;
1197 if ($self->check_right($::myconfig{login}, $right)) {
1202 delete $::form->{title};
1203 $::form->show_generic_error($::locale->text("You do not have the permissions to access this function."));
1209 sub load_rights_for_user {
1210 my ($self, $login) = @_;
1211 my $dbh = $self->dbconnect;
1212 my ($query, $sth, $row, $rights);
1214 $rights = { map { $_ => 0 } $self->all_rights };
1216 return $rights if !$self->client || !$login;
1219 qq|SELECT gr."right", gr.granted
1220 FROM auth.group_rights gr
1223 FROM auth.user_group ug
1224 LEFT JOIN auth."user" u ON (ug.user_id = u.id)
1228 FROM auth.clients_groups cg
1229 WHERE cg.client_id = ?)|;
1231 $sth = prepare_execute_query($::form, $dbh, $query, $login, $self->client->{id});
1233 while ($row = $sth->fetchrow_hashref()) {
1234 $rights->{$row->{right}} |= $row->{granted};
1250 SL::Auth - Authentication and session handling
1256 =item C<set_session_value @values>
1258 =item C<set_session_value %values>
1260 Store all values of C<@values> or C<%values> in the session. Each
1261 member of C<@values> is tested if it is a hash reference. If it is
1262 then it must contain the keys C<key> and C<value> and can optionally
1263 contain the key C<auto_restore>. In this case C<value> is associated
1264 with C<key> and restored to C<$::form> upon the next request
1265 automatically if C<auto_restore> is trueish or if C<value> is a scalar
1268 If the current member of C<@values> is not a hash reference then it
1269 will be used as the C<key> and the next entry of C<@values> is used as
1270 the C<value> to store. In this case setting C<auto_restore> is not
1273 Therefore the following two invocations are identical:
1275 $::auth-E<gt>set_session_value(name =E<gt> "Charlie");
1276 $::auth-E<gt>set_session_value({ key =E<gt> "name", value =E<gt> "Charlie" });
1278 All of these values are copied back into C<$::form> for the next
1279 request automatically if they're scalar values or if they have
1280 C<auto_restore> set to trueish.
1282 The values can be any Perl structure. They are stored as YAML dumps.
1284 =item C<get_session_value $key>
1286 Retrieve a value from the session. Returns C<undef> if the value
1289 =item C<create_unique_sesion_value $value, %params>
1291 Create a unique key in the session and store C<$value>
1294 Returns the key created in the session.
1296 =item C<save_session>
1298 Stores the session values in the database. This is the only function
1299 that actually stores stuff in the database. Neither the various
1300 setters nor the deleter access the database.
1302 =item C<save_form_in_session %params>
1304 Stores the content of C<$params{form}> (default: C<$::form>) in the
1305 session using L</create_unique_sesion_value>.
1307 If C<$params{non_scalars}> is trueish then non-scalar values will be
1308 stored as well. Default is to only store scalar values.
1310 The following keys will never be saved: C<login>, C<password>,
1311 C<stylesheet>, C<titlebar>, C<version>. Additional keys not to save
1312 can be given as an array ref in C<$params{skip_keys}>.
1314 Returns the unique key under which the form is stored.
1316 =item C<restore_form_from_session $key, %params>
1318 Restores the form from the session into C<$params{form}> (default:
1321 If C<$params{clobber}> is falsish then existing values with the same
1322 key in C<$params{form}> will not be overwritten. C<$params{clobber}>
1329 C<reset> deletes every state information from previous requests, but does not
1330 close the database connection.
1332 Creating a new database handle on each request can take up to 30% of the
1333 pre-request startup time, so we want to avoid that for fast ajax calls.
1335 =item C<assert, $right, $dont_abort>
1337 Checks if current user has the C<$right>. If C<$dont_abort> is falsish
1338 the request dies with a access denied error, otherwise returns true or false.
1348 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>