1# --
2# Copyright (C) 2001-2020 OTRS AG, https://otrs.com/
3# --
4# This software comes with ABSOLUTELY NO WARRANTY. For details, see
5# the enclosed file COPYING for license information (GPL). If you
6# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
7# --
8
9package Kernel::System::Web::InterfaceAgent;
10
11use strict;
12use warnings;
13
14use Kernel::Language qw(Translatable);
15use Kernel::System::DateTime;
16
17our @ObjectDependencies = (
18    'Kernel::Config',
19    'Kernel::Output::HTML::Layout',
20    'Kernel::System::Auth',
21    'Kernel::System::AuthSession',
22    'Kernel::System::DB',
23    'Kernel::System::Email',
24    'Kernel::System::Group',
25    'Kernel::System::Log',
26    'Kernel::System::Main',
27    'Kernel::System::Scheduler',
28    'Kernel::System::DateTime',
29    'Kernel::System::User',
30    'Kernel::System::Web::Request',
31    'Kernel::System::Valid',
32);
33
34=head1 NAME
35
36Kernel::System::Web::InterfaceAgent - the agent web interface
37
38=head1 DESCRIPTION
39
40the global agent web interface (authentication, session handling, ...)
41
42=head1 PUBLIC INTERFACE
43
44=head2 new()
45
46create agent web interface object. Do not use it directly, instead use:
47
48    use Kernel::System::ObjectManager;
49    my $Debug = 0,
50    local $Kernel::OM = Kernel::System::ObjectManager->new(
51        'Kernel::System::Web::InterfaceAgent' => {
52            Debug   => 0,
53            WebRequest => CGI::Fast->new(), # optional, e. g. if fast cgi is used,
54                                            # the CGI object is already provided
55        }
56    );
57    my $InterfaceAgent = $Kernel::OM->Get('Kernel::System::Web::InterfaceAgent');
58
59=cut
60
61sub new {
62    my ( $Type, %Param ) = @_;
63    my $Self = {};
64    bless( $Self, $Type );
65
66    # Performance log
67    $Self->{PerformanceLogStart} = time();
68
69    # get debug level
70    $Self->{Debug} = $Param{Debug} || 0;
71
72    $Kernel::OM->ObjectParamAdd(
73        'Kernel::System::Log' => {
74            LogPrefix => $Kernel::OM->Get('Kernel::Config')->Get('CGILogPrefix'),
75        },
76        'Kernel::System::Web::Request' => {
77            WebRequest => $Param{WebRequest} || 0,
78        },
79    );
80
81    # debug info
82    if ( $Self->{Debug} ) {
83        $Kernel::OM->Get('Kernel::System::Log')->Log(
84            Priority => 'debug',
85            Message  => 'Global handle started...',
86        );
87    }
88
89    return $Self;
90}
91
92=head2 Run()
93
94execute the object
95
96    $InterfaceAgent->Run();
97
98=cut
99
100sub Run {
101    my $Self = shift;
102
103    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
104
105    my $QueryString = $ENV{QUERY_STRING} || '';
106
107    # Check if https forcing is active, and redirect if needed.
108    if ( $ConfigObject->Get('HTTPSForceRedirect') ) {
109
110        # Some web servers do not set HTTPS environment variable, so it's not possible to easily know if we are using
111        #   https protocol. Look also for similarly named keys in environment hash, since this should prevent loops in
112        #   certain cases.
113        if (
114            (
115                !defined $ENV{HTTPS}
116                && !grep {/^HTTPS(?:_|$)/} keys %ENV
117            )
118            || $ENV{HTTPS} ne 'on'
119            )
120        {
121            my $Host = $ENV{HTTP_HOST} || $ConfigObject->Get('FQDN');
122
123            # Redirect with 301 code. Add two new lines at the end, so HTTP headers are validated correctly.
124            print "Status: 301 Moved Permanently\nLocation: https://$Host$ENV{REQUEST_URI}\n\n";
125            return;
126        }
127    }
128
129    my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request');
130
131    my %Param;
132
133    # get session id
134    $Param{SessionName} = $ConfigObject->Get('SessionName')                      || 'SessionID';
135    $Param{SessionID}   = $ParamObject->GetParam( Param => $Param{SessionName} ) || '';
136
137    # drop old session id (if exists)
138    $QueryString =~ s/(\?|&|;|)$Param{SessionName}(=&|=;|=.+?&|=.+?$)/;/g;
139
140    # define framework params
141    my $FrameworkParams = {
142        Lang         => '',
143        Action       => '',
144        Subaction    => '',
145        RequestedURL => $QueryString,
146    };
147    for my $Key ( sort keys %{$FrameworkParams} ) {
148        $Param{$Key} = $ParamObject->GetParam( Param => $Key )
149            || $FrameworkParams->{$Key};
150    }
151
152    # validate language
153    if ( $Param{Lang} && $Param{Lang} !~ m{\A[a-z]{2}(?:_[A-Z]{2})?\z}xms ) {
154        delete $Param{Lang};
155    }
156
157    # check if the browser sends the SessionID cookie and set the SessionID-cookie
158    # as SessionID! GET or POST SessionID have the lowest priority.
159    my $BrowserHasCookie = 0;
160    if ( $ConfigObject->Get('SessionUseCookie') ) {
161        $Param{SessionIDCookie} = $ParamObject->GetCookie( Key => $Param{SessionName} );
162        if ( $Param{SessionIDCookie} ) {
163            $Param{SessionID} = $Param{SessionIDCookie};
164        }
165    }
166
167    $Kernel::OM->ObjectParamAdd(
168        'Kernel::Output::HTML::Layout' => {
169            Lang         => $Param{Lang},
170            UserLanguage => $Param{Lang},
171        },
172        'Kernel::Language' => {
173            UserLanguage => $Param{Lang}
174        },
175    );
176
177    my $CookieSecureAttribute;
178    if ( $ConfigObject->Get('HttpType') eq 'https' ) {
179
180        # Restrict Cookie to HTTPS if it is used.
181        $CookieSecureAttribute = 1;
182    }
183
184    my $DBCanConnect = $Kernel::OM->Get('Kernel::System::DB')->Connect();
185
186    if ( !$DBCanConnect || $ParamObject->Error() ) {
187        my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
188        if ( !$DBCanConnect ) {
189            $LayoutObject->FatalError(
190                Comment => Translatable('Please contact the administrator.'),
191            );
192            return;
193        }
194        if ( $ParamObject->Error() ) {
195            $LayoutObject->FatalError(
196                Message => $ParamObject->Error(),
197                Comment => Translatable('Please contact the administrator.'),
198            );
199            return;
200        }
201    }
202
203    # get common application and add-on application params
204    my %CommonObjectParam = %{ $ConfigObject->Get('Frontend::CommonParam') };
205    for my $Key ( sort keys %CommonObjectParam ) {
206        $Param{$Key} = $ParamObject->GetParam( Param => $Key ) || $CommonObjectParam{$Key};
207    }
208
209    # security check Action Param (replace non word chars)
210    $Param{Action} =~ s/\W//g;
211
212    my $SessionObject = $Kernel::OM->Get('Kernel::System::AuthSession');
213    my $UserObject    = $Kernel::OM->Get('Kernel::System::User');
214
215    # check request type
216    if ( $Param{Action} eq 'PreLogin' ) {
217        my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
218        $Param{RequestedURL} = $Param{RequestedURL} || "Action=AgentDashboard";
219
220        # login screen
221        $LayoutObject->Print(
222            Output => \$LayoutObject->Login(
223                Title => 'Login',
224                Mode  => 'PreLogin',
225                %Param,
226            ),
227        );
228
229        return;
230    }
231    elsif ( $Param{Action} eq 'Login' ) {
232
233        # get params
234        my $PostUser = $ParamObject->GetParam( Param => 'User' ) || '';
235        my $PostPw   = $ParamObject->GetParam(
236            Param => 'Password',
237            Raw   => 1
238        ) || '';
239        my $PostTwoFactorToken = $ParamObject->GetParam(
240            Param => 'TwoFactorToken',
241            Raw   => 1
242        ) || '';
243
244        # create AuthObject
245        my $AuthObject = $Kernel::OM->Get('Kernel::System::Auth');
246
247        # check submitted data
248        my $User = $AuthObject->Auth(
249            User           => $PostUser,
250            Pw             => $PostPw,
251            TwoFactorToken => $PostTwoFactorToken,
252        );
253
254        # login is invalid
255        if ( !$User ) {
256
257            my $Expires = '+' . $ConfigObject->Get('SessionMaxTime') . 's';
258            if ( !$ConfigObject->Get('SessionUseCookieAfterBrowserClose') ) {
259                $Expires = '';
260            }
261
262            $Kernel::OM->ObjectParamAdd(
263                'Kernel::Output::HTML::Layout' => {
264                    SetCookies => {
265                        OTRSBrowserHasCookie => $ParamObject->SetCookie(
266                            Key      => 'OTRSBrowserHasCookie',
267                            Value    => 1,
268                            Expires  => $Expires,
269                            Path     => $ConfigObject->Get('ScriptAlias'),
270                            Secure   => $CookieSecureAttribute,
271                            HTTPOnly => 1,
272                        ),
273                    },
274                }
275            );
276            my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
277
278            # redirect to alternate login
279            if ( $ConfigObject->Get('LoginURL') ) {
280                $Param{RequestedURL} = $LayoutObject->LinkEncode( $Param{RequestedURL} );
281                print $LayoutObject->Redirect(
282                    ExtURL => $ConfigObject->Get('LoginURL')
283                        . "?Reason=LoginFailed&RequestedURL=$Param{RequestedURL}",
284                );
285                return;
286            }
287
288            # show normal login
289            $LayoutObject->Print(
290                Output => \$LayoutObject->Login(
291                    Title   => 'Login',
292                    Message => $Kernel::OM->Get('Kernel::System::Log')->GetLogEntry(
293                        Type => 'Info',
294                        What => 'Message',
295                        )
296                        || $LayoutObject->{LanguageObject}->Translate( $AuthObject->GetLastErrorMessage() )
297                        || Translatable('Login failed! Your user name or password was entered incorrectly.'),
298                    LoginFailed => 1,
299                    MessageType => 'Error',
300                    User        => $User,
301                    %Param,
302                ),
303            );
304            return;
305        }
306
307        # login is successful
308        my %UserData = $UserObject->GetUserData(
309            User          => $User,
310            Valid         => 1,
311            NoOutOfOffice => 1,
312        );
313
314        # check if the browser supports cookies
315        if ( $ParamObject->GetCookie( Key => 'OTRSBrowserHasCookie' ) ) {
316            $Kernel::OM->ObjectParamAdd(
317                'Kernel::Output::HTML::Layout' => {
318                    BrowserHasCookie => 1,
319                },
320            );
321        }
322
323        # check needed data
324        if ( !$UserData{UserID} || !$UserData{UserLogin} ) {
325
326            # redirect to alternate login
327            if ( $ConfigObject->Get('LoginURL') ) {
328                print $Kernel::OM->Get('Kernel::Output::HTML::Layout')->Redirect(
329                    ExtURL => $ConfigObject->Get('LoginURL') . '?Reason=SystemError',
330                );
331                return;
332            }
333
334            my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
335
336            # show need user data error message
337            $LayoutObject->Print(
338                Output => \$LayoutObject->Login(
339                    Title => 'Error',
340                    Message =>
341                        Translatable(
342                        'Authentication succeeded, but no user data record is found in the database. Please contact the administrator.'
343                        ),
344                    %Param,
345                    MessageType => 'Error',
346                ),
347            );
348            return;
349        }
350
351        my $DateTimeObj = $Kernel::OM->Create('Kernel::System::DateTime');
352
353        # create new session id
354        my $NewSessionID = $SessionObject->CreateSessionID(
355            %UserData,
356            UserLastRequest => $DateTimeObj->ToEpoch(),
357            UserType        => 'User',
358            SessionSource   => 'AgentInterface',
359        );
360
361        # show error message if no session id has been created
362        if ( !$NewSessionID ) {
363
364            # get error message
365            my $Error = $SessionObject->SessionIDErrorMessage() || '';
366
367            # output error message
368            my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
369            $LayoutObject->Print(
370                Output => \$LayoutObject->Login(
371                    Title       => 'Login',
372                    Message     => $Error,
373                    MessageType => 'Error',
374                    %Param,
375                ),
376            );
377            return;
378        }
379
380        # execution in 20 seconds
381        my $ExecutionTimeObj = $DateTimeObj->Clone();
382        $ExecutionTimeObj->Add( Seconds => 20 );
383        my $ExecutionTime = $ExecutionTimeObj->ToString();
384
385        # add a asychronous executor scheduler task to count the concurrent user
386        $Kernel::OM->Get('Kernel::System::Scheduler')->TaskAdd(
387            ExecutionTime            => $ExecutionTime,
388            Type                     => 'AsynchronousExecutor',
389            Name                     => 'PluginAsynchronous::ConcurrentUser',
390            MaximumParallelInstances => 1,
391            Data                     => {
392                Object   => 'Kernel::System::SupportDataCollector::PluginAsynchronous::OTRS::ConcurrentUsers',
393                Function => 'RunAsynchronous',
394            },
395        );
396
397        my $UserTimeZone = $Self->_UserTimeZoneGet(%UserData);
398
399        $SessionObject->UpdateSessionID(
400            SessionID => $NewSessionID,
401            Key       => 'UserTimeZone',
402            Value     => $UserTimeZone,
403        );
404
405        # check if the time zone offset reported by the user's browser differs from that
406        # of the OTRS user's time zone offset
407        my $DateTimeObject = $Kernel::OM->Create(
408            'Kernel::System::DateTime',
409            ObjectParams => {
410                TimeZone => $UserTimeZone,
411            },
412        );
413        my $OTRSUserTimeZoneOffset = $DateTimeObject->Format( Format => '%{offset}' ) / 60;
414        my $BrowserTimeZoneOffset  = ( $ParamObject->GetParam( Param => 'TimeZoneOffset' ) || 0 ) * -1;
415
416        # TimeZoneOffsetDifference contains the difference of the time zone offset between
417        # the user's OTRS time zone setting and the one reported by the user's browser.
418        # If there is a difference it can be evaluated later to e. g. show a message
419        # for the user to check his OTRS time zone setting.
420        my $UserTimeZoneOffsetDifference = abs( $OTRSUserTimeZoneOffset - $BrowserTimeZoneOffset );
421        $SessionObject->UpdateSessionID(
422            SessionID => $NewSessionID,
423            Key       => 'UserTimeZoneOffsetDifference',
424            Value     => $UserTimeZoneOffsetDifference,
425        );
426
427        # create a new LayoutObject with SessionIDCookie
428        my $Expires = '+' . $ConfigObject->Get('SessionMaxTime') . 's';
429        if ( !$ConfigObject->Get('SessionUseCookieAfterBrowserClose') ) {
430            $Expires = '';
431        }
432
433        my $SecureAttribute;
434        if ( $ConfigObject->Get('HttpType') eq 'https' ) {
435
436            # Restrict Cookie to HTTPS if it is used.
437            $SecureAttribute = 1;
438        }
439
440        $Kernel::OM->ObjectParamAdd(
441            'Kernel::Output::HTML::Layout' => {
442                SetCookies => {
443                    SessionIDCookie => $ParamObject->SetCookie(
444                        Key      => $Param{SessionName},
445                        Value    => $NewSessionID,
446                        Expires  => $Expires,
447                        Path     => $ConfigObject->Get('ScriptAlias'),
448                        Secure   => scalar $CookieSecureAttribute,
449                        HTTPOnly => 1,
450                    ),
451                    OTRSBrowserHasCookie => $ParamObject->SetCookie(
452                        Key      => 'OTRSBrowserHasCookie',
453                        Value    => '',
454                        Expires  => '-1y',
455                        Path     => $ConfigObject->Get('ScriptAlias'),
456                        Secure   => $CookieSecureAttribute,
457                        HTTPOnly => 1,
458                    ),
459                },
460                SessionID   => $NewSessionID,
461                SessionName => $Param{SessionName},
462            },
463        );
464
465        # Check if Chat is active
466        if ( $Kernel::OM->Get('Kernel::Config')->Get('ChatEngine::Active') ) {
467            my $ChatReceivingAgentsGroup
468                = $Kernel::OM->Get('Kernel::Config')->Get('ChatEngine::PermissionGroup::ChatReceivingAgents');
469
470            my $ChatReceivingAgentsGroupPermission = $Kernel::OM->Get('Kernel::System::Group')->PermissionCheck(
471                UserID    => $UserData{UserID},
472                GroupName => $ChatReceivingAgentsGroup,
473                Type      => 'rw',
474            );
475
476            if (
477                $UserData{UserID} != -1
478                && $ChatReceivingAgentsGroup
479                && $ChatReceivingAgentsGroupPermission
480                && $Kernel::OM->Get('Kernel::Config')->Get('Ticket::Agent::UnavailableForExternalChatsOnLogin')
481                )
482            {
483                # Get user preferences
484                my %Preferences = $Kernel::OM->Get('Kernel::System::User')->GetPreferences(
485                    UserID => $UserData{UserID},
486                );
487
488                if ( $Preferences{ChatAvailability} && $Preferences{ChatAvailability} == 2 ) {
489
490                    # User is available for external chats. Set his availability to internal only.
491                    $Kernel::OM->Get('Kernel::System::User')->SetPreferences(
492                        Key    => 'ChatAvailability',
493                        Value  => '1',
494                        UserID => $UserData{UserID},
495                    );
496
497                    # Set ChatAvailabilityNotification to display notification in agent interface (only once)
498                    $Kernel::OM->Get('Kernel::System::User')->SetPreferences(
499                        Key    => 'ChatAvailabilityNotification',
500                        Value  => '1',
501                        UserID => $UserData{UserID},
502                    );
503                }
504            }
505        }
506
507        # redirect with new session id and old params
508        # prepare old redirect URL -- do not redirect to Login or Logout (loop)!
509        if ( $Param{RequestedURL} =~ /Action=(Logout|Login|LostPassword|PreLogin)/ ) {
510            $Param{RequestedURL} = '';
511        }
512
513        # redirect with new session id
514        print $Kernel::OM->Get('Kernel::Output::HTML::Layout')->Redirect(
515            OP    => $Param{RequestedURL},
516            Login => 1,
517        );
518        return 1;
519    }
520
521    # logout
522    elsif ( $Param{Action} eq 'Logout' ) {
523
524        my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
525
526        # check session id
527        if ( !$SessionObject->CheckSessionID( SessionID => $Param{SessionID} ) ) {
528
529            # redirect to alternate login
530            if ( $ConfigObject->Get('LoginURL') ) {
531                $Param{RequestedURL} = $LayoutObject->LinkEncode( $Param{RequestedURL} );
532                print $LayoutObject->Redirect(
533                    ExtURL => $ConfigObject->Get('LoginURL')
534                        . "?Reason=InvalidSessionID&RequestedURL=$Param{RequestedURL}",
535                );
536                return;
537            }
538
539            # show login screen
540            $LayoutObject->Print(
541                Output => \$LayoutObject->Login(
542                    Title => 'Logout',
543                    %Param,
544                ),
545            );
546            return;
547        }
548
549        # get session data
550        my %UserData = $SessionObject->GetSessionIDData(
551            SessionID => $Param{SessionID},
552        );
553
554        $UserData{UserTimeZone} = $Self->_UserTimeZoneGet(%UserData);
555
556        # create a new LayoutObject with %UserData
557        $Kernel::OM->ObjectParamAdd(
558            'Kernel::Output::HTML::Layout' => {
559                SetCookies => {
560                    SessionIDCookie => $ParamObject->SetCookie(
561                        Key      => $Param{SessionName},
562                        Value    => '',
563                        Expires  => '-1y',
564                        Path     => $ConfigObject->Get('ScriptAlias'),
565                        Secure   => scalar $CookieSecureAttribute,
566                        HTTPOnly => 1,
567                    ),
568                },
569                %UserData,
570            },
571        );
572        $Kernel::OM->ObjectsDiscard( Objects => ['Kernel::Output::HTML::Layout'] );
573        $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
574
575        # Prevent CSRF attacks
576        $LayoutObject->ChallengeTokenCheck();
577
578        # remove session id
579        if ( !$SessionObject->RemoveSessionID( SessionID => $Param{SessionID} ) ) {
580            $LayoutObject->FatalError(
581                Message => Translatable('Can`t remove SessionID.'),
582                Comment => Translatable('Please contact the administrator.'),
583            );
584            return;
585        }
586
587        # redirect to alternate login
588        if ( $ConfigObject->Get('LogoutURL') ) {
589            print $LayoutObject->Redirect(
590                ExtURL => $ConfigObject->Get('LogoutURL'),
591            );
592            return 1;
593        }
594
595        # show logout screen
596        my $LogoutMessage = $LayoutObject->{LanguageObject}->Translate('Logout successful.');
597
598        $LayoutObject->Print(
599            Output => \$LayoutObject->Login(
600                Title       => 'Logout',
601                Message     => $LogoutMessage,
602                MessageType => 'Success',
603                %Param,
604            ),
605        );
606        return 1;
607    }
608
609    # user lost password
610    elsif ( $Param{Action} eq 'LostPassword' ) {
611
612        my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
613
614        # check feature
615        if ( !$ConfigObject->Get('LostPassword') ) {
616
617            # show normal login
618            $LayoutObject->Print(
619                Output => \$LayoutObject->Login(
620                    Title       => 'Login',
621                    Message     => Translatable('Feature not active!'),
622                    MessageType => 'Error',
623                ),
624            );
625            return;
626        }
627
628        # get params
629        my $User  = $ParamObject->GetParam( Param => 'User' )  || '';
630        my $Token = $ParamObject->GetParam( Param => 'Token' ) || '';
631
632        # get user login by token
633        if ( !$User && $Token ) {
634
635            # Prevent extracting password reset token character-by-character via wildcard injection
636            # The wild card characters "%" and "_" could be used to match arbitrary character.
637            if ( $Token !~ m{\A (?: [a-zA-Z] | \d )+ \z}xms ) {
638
639                # Security: pretend that password reset instructions were actually sent to
640                #   make sure that users cannot find out valid usernames by
641                #   just trying and checking the result message.
642                $LayoutObject->Print(
643                    Output => \$LayoutObject->Login(
644                        Title       => 'Login',
645                        Message     => Translatable('Sent password reset instructions. Please check your email.'),
646                        MessageType => 'Success',
647                        %Param,
648                    ),
649                );
650                return;
651            }
652
653            my %UserList = $UserObject->SearchPreferences(
654                Key   => 'UserToken',
655                Value => $Token,
656            );
657            USERS:
658            for my $UserID ( sort keys %UserList ) {
659                my %UserData = $UserObject->GetUserData(
660                    UserID => $UserID,
661                    Valid  => 1,
662                );
663                if (%UserData) {
664                    $User = $UserData{UserLogin};
665                    last USERS;
666                }
667            }
668        }
669
670        # get user data
671        my %UserData = $UserObject->GetUserData(
672            User  => $User,
673            Valid => 1
674        );
675
676        # verify user is valid when requesting password reset
677        my @ValidIDs    = $Kernel::OM->Get('Kernel::System::Valid')->ValidIDsGet();
678        my $UserIsValid = grep { $UserData{ValidID} && $UserData{ValidID} == $_ } @ValidIDs;
679        if ( !$UserData{UserID} || !$UserIsValid ) {
680
681            # Security: pretend that password reset instructions were actually sent to
682            #   make sure that users cannot find out valid usernames by
683            #   just trying and checking the result message.
684            $LayoutObject->Print(
685                Output => \$LayoutObject->Login(
686                    Title       => 'Login',
687                    Message     => Translatable('Sent password reset instructions. Please check your email.'),
688                    MessageType => 'Success',
689                    %Param,
690                ),
691            );
692            return;
693        }
694
695        # create email object
696        my $EmailObject = $Kernel::OM->Get('Kernel::System::Email');
697
698        # send password reset token
699        if ( !$Token ) {
700
701            # generate token
702            $UserData{Token} = $UserObject->TokenGenerate(
703                UserID => $UserData{UserID},
704            );
705
706            # send token notify email with link
707            my $Body = $ConfigObject->Get('NotificationBodyLostPasswordToken')
708                || 'ERROR: NotificationBodyLostPasswordToken is missing!';
709            my $Subject = $ConfigObject->Get('NotificationSubjectLostPasswordToken')
710                || 'ERROR: NotificationSubjectLostPasswordToken is missing!';
711            for ( sort keys %UserData ) {
712                $Body =~ s/<OTRS_$_>/$UserData{$_}/gi;
713            }
714            my $Sent = $EmailObject->Send(
715                To       => $UserData{UserEmail},
716                Subject  => $Subject,
717                Charset  => $LayoutObject->{UserCharset},
718                MimeType => 'text/plain',
719                Body     => $Body
720            );
721            if ( !$Sent->{Success} ) {
722                $LayoutObject->FatalError(
723                    Comment => Translatable('Please contact the administrator.'),
724                );
725                return;
726            }
727            $LayoutObject->Print(
728                Output => \$LayoutObject->Login(
729                    Title       => 'Login',
730                    Message     => Translatable('Sent password reset instructions. Please check your email.'),
731                    MessageType => 'Success',
732                    %Param,
733                ),
734            );
735            return 1;
736        }
737
738        # reset password
739        # check if token is valid
740        my $TokenValid = $UserObject->TokenCheck(
741            Token  => $Token,
742            UserID => $UserData{UserID},
743        );
744        if ( !$TokenValid ) {
745            $LayoutObject->Print(
746                Output => \$LayoutObject->Login(
747                    Title       => 'Login',
748                    Message     => Translatable('Invalid Token!'),
749                    MessageType => 'Error',
750                    %Param,
751                ),
752            );
753            return;
754        }
755
756        # get new password
757        $UserData{NewPW} = $UserObject->GenerateRandomPassword();
758
759        # update new password
760        $UserObject->SetPassword(
761            UserLogin => $User,
762            PW        => $UserData{NewPW}
763        );
764
765        # send notify email
766        my $Body = $ConfigObject->Get('NotificationBodyLostPassword')
767            || 'New Password is: <OTRS_NEWPW>';
768        my $Subject = $ConfigObject->Get('NotificationSubjectLostPassword')
769            || 'New Password!';
770        for ( sort keys %UserData ) {
771            $Body =~ s/<OTRS_$_>/$UserData{$_}/gi;
772        }
773        my $Sent = $EmailObject->Send(
774            To       => $UserData{UserEmail},
775            Subject  => $Subject,
776            Charset  => $LayoutObject->{UserCharset},
777            MimeType => 'text/plain',
778            Body     => $Body
779        );
780
781        if ( !$Sent->{Success} ) {
782            $LayoutObject->FatalError(
783                Comment => Translatable('Please contact the administrator.'),
784            );
785            return;
786        }
787        my $Message = $LayoutObject->{LanguageObject}->Translate(
788            'Sent new password to %s. Please check your email.',
789            $UserData{UserEmail},
790        );
791        $LayoutObject->Print(
792            Output => \$LayoutObject->Login(
793                Title       => 'Login',
794                Message     => $Message,
795                User        => $User,
796                MessageType => 'Success',
797                %Param,
798            ),
799        );
800        return 1;
801    }
802
803    # show login site
804    elsif ( !$Param{SessionID} ) {
805
806        # create AuthObject
807        my $AuthObject   = $Kernel::OM->Get('Kernel::System::Auth');
808        my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
809        if ( $AuthObject->GetOption( What => 'PreAuth' ) ) {
810
811            # automatic login
812            $Param{RequestedURL} = $LayoutObject->LinkEncode( $Param{RequestedURL} );
813            print $LayoutObject->Redirect(
814                OP => "Action=PreLogin&RequestedURL=$Param{RequestedURL}",
815            );
816            return;
817        }
818        elsif ( $ConfigObject->Get('LoginURL') ) {
819
820            # redirect to alternate login
821            $Param{RequestedURL} = $LayoutObject->LinkEncode( $Param{RequestedURL} );
822            print $LayoutObject->Redirect(
823                ExtURL => $ConfigObject->Get('LoginURL')
824                    . "?RequestedURL=$Param{RequestedURL}",
825            );
826            return;
827        }
828
829        # login screen
830        $LayoutObject->Print(
831            Output => \$LayoutObject->Login(
832                Title => 'Login',
833                %Param,
834            ),
835        );
836        return;
837    }
838
839    # run modules if a version value exists
840    elsif ( $Kernel::OM->Get('Kernel::System::Main')->Require("Kernel::Modules::$Param{Action}") ) {
841
842        # check session id
843        if ( !$SessionObject->CheckSessionID( SessionID => $Param{SessionID} ) ) {
844
845            # put '%Param' into LayoutObject
846            $Kernel::OM->ObjectParamAdd(
847                'Kernel::Output::HTML::Layout' => {
848                    SetCookies => {
849                        SessionIDCookie => $ParamObject->SetCookie(
850                            Key      => $Param{SessionName},
851                            Value    => '',
852                            Expires  => '-1y',
853                            Path     => $ConfigObject->Get('ScriptAlias'),
854                            Secure   => scalar $CookieSecureAttribute,
855                            HTTPOnly => 1,
856                        ),
857                    },
858                    %Param,
859                },
860            );
861
862            $Kernel::OM->ObjectsDiscard( Objects => ['Kernel::Output::HTML::Layout'] );
863            my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
864
865            # create AuthObject
866            my $AuthObject = $Kernel::OM->Get('Kernel::System::Auth');
867            if ( $AuthObject->GetOption( What => 'PreAuth' ) ) {
868
869                # automatic re-login
870                $Param{RequestedURL} = $LayoutObject->LinkEncode( $Param{RequestedURL} );
871                print $LayoutObject->Redirect(
872                    OP => "?Action=PreLogin&RequestedURL=$Param{RequestedURL}",
873                );
874                return;
875            }
876            elsif ( $ConfigObject->Get('LoginURL') ) {
877
878                # redirect to alternate login
879                $Param{RequestedURL} = $LayoutObject->LinkEncode( $Param{RequestedURL} );
880                print $LayoutObject->Redirect(
881                    ExtURL => $ConfigObject->Get('LoginURL')
882                        . "?Reason=InvalidSessionID&RequestedURL=$Param{RequestedURL}",
883                );
884                return;
885            }
886
887            # show login
888            $LayoutObject->Print(
889                Output => \$LayoutObject->Login(
890                    Title => 'Login',
891                    Message =>
892                        $LayoutObject->{LanguageObject}->Translate( $SessionObject->SessionIDErrorMessage() ),
893                    MessageType => 'Error',
894                    %Param,
895                ),
896            );
897            return;
898        }
899
900        # get session data
901        my %UserData = $SessionObject->GetSessionIDData(
902            SessionID => $Param{SessionID},
903        );
904
905        $UserData{UserTimeZone} = $Self->_UserTimeZoneGet(%UserData);
906
907        # check needed data
908        if ( !$UserData{UserID} || !$UserData{UserLogin} || $UserData{UserType} ne 'User' ) {
909
910            my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
911
912            # redirect to alternate login
913            if ( $ConfigObject->Get('LoginURL') ) {
914                print $LayoutObject->Redirect(
915                    ExtURL => $ConfigObject->Get('LoginURL') . '?Reason=SystemError',
916                );
917                return;
918            }
919
920            # show login screen
921            $LayoutObject->Print(
922                Output => \$LayoutObject->Login(
923                    Title       => 'Error',
924                    Message     => Translatable('Error: invalid session.'),
925                    MessageType => 'Error',
926                    %Param,
927                ),
928            );
929            return;
930        }
931
932        # check module registry
933        my $ModuleReg = $ConfigObject->Get('Frontend::Module')->{ $Param{Action} };
934        if ( !$ModuleReg ) {
935
936            $Kernel::OM->Get('Kernel::System::Log')->Log(
937                Priority => 'error',
938                Message =>
939                    "Module Kernel::Modules::$Param{Action} not registered in Kernel/Config.pm!",
940            );
941            $Kernel::OM->Get('Kernel::Output::HTML::Layout')->FatalError(
942                Comment => Translatable('Please contact the administrator.'),
943            );
944            return;
945        }
946
947        # module permisson check
948        if (
949            ref $ModuleReg->{GroupRo} eq 'ARRAY'
950            && !scalar @{ $ModuleReg->{GroupRo} }
951            && ref $ModuleReg->{Group} eq 'ARRAY'
952            && !scalar @{ $ModuleReg->{Group} }
953            )
954        {
955            $Param{AccessRo} = 1;
956            $Param{AccessRw} = 1;
957        }
958        else {
959            my $GroupObject = $Kernel::OM->Get('Kernel::System::Group');
960
961            PERMISSION:
962            for my $Permission (qw(GroupRo Group)) {
963                my $AccessOk = 0;
964                my $Group    = $ModuleReg->{$Permission};
965                next PERMISSION if !$Group;
966                if ( ref $Group eq 'ARRAY' ) {
967                    INNER:
968                    for my $GroupName ( @{$Group} ) {
969                        next INNER if !$GroupName;
970                        next INNER if !$GroupObject->PermissionCheck(
971                            UserID    => $UserData{UserID},
972                            GroupName => $GroupName,
973                            Type      => $Permission eq 'GroupRo' ? 'ro' : 'rw',
974
975                        );
976                        $AccessOk = 1;
977                        last INNER;
978                    }
979                }
980                else {
981                    my $HasPermission = $GroupObject->PermissionCheck(
982                        UserID    => $UserData{UserID},
983                        GroupName => $Group,
984                        Type      => $Permission eq 'GroupRo' ? 'ro' : 'rw',
985
986                    );
987                    if ($HasPermission) {
988                        $AccessOk = 1;
989                    }
990                }
991                if ( $Permission eq 'Group' && $AccessOk ) {
992                    $Param{AccessRo} = 1;
993                    $Param{AccessRw} = 1;
994                }
995                elsif ( $Permission eq 'GroupRo' && $AccessOk ) {
996                    $Param{AccessRo} = 1;
997                }
998            }
999            if ( !$Param{AccessRo} && !$Param{AccessRw} || !$Param{AccessRo} && $Param{AccessRw} ) {
1000
1001                print $Kernel::OM->Get('Kernel::Output::HTML::Layout')->NoPermission(
1002                    Message => Translatable('No Permission to use this frontend module!')
1003                );
1004                return;
1005            }
1006        }
1007
1008        # put '%Param' and '%UserData' into LayoutObject
1009        $Kernel::OM->ObjectParamAdd(
1010            'Kernel::Output::HTML::Layout' => {
1011                %Param,
1012                %UserData,
1013                ModuleReg => $ModuleReg,
1014            },
1015        );
1016        $Kernel::OM->ObjectsDiscard( Objects => ['Kernel::Output::HTML::Layout'] );
1017
1018        # update last request time
1019        if (
1020            !$ParamObject->IsAJAXRequest()
1021            || $Param{Action} eq 'AgentVideoChat'
1022            ||
1023            (
1024                $Param{Action} eq 'AgentChat'
1025                &&
1026                $Param{Subaction} ne 'ChatGetOpenRequests' &&
1027                $Param{Subaction} ne 'ChatMonitorCheck'
1028            )
1029            )
1030        {
1031            my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
1032
1033            $SessionObject->UpdateSessionID(
1034                SessionID => $Param{SessionID},
1035                Key       => 'UserLastRequest',
1036                Value     => $DateTimeObject->ToEpoch(),
1037            );
1038        }
1039
1040        # Override user settings.
1041        my $Home = $ConfigObject->Get('Home');
1042        my $File = "$Home/Kernel/Config/Files/User/$UserData{UserID}.pm";
1043        if ( -e $File ) {
1044            eval {
1045                if ( require $File ) {
1046
1047                    # Prepare file.
1048                    $File =~ s/\Q$Home\E//g;
1049                    $File =~ s/^\///g;
1050                    $File =~ s/\/\//\//g;
1051                    $File =~ s/\//::/g;
1052                    $File =~ s/\.pm$//g;
1053                    $File->Load($ConfigObject);
1054                }
1055                else {
1056                    die "Cannot load file $File: $!\n";
1057                }
1058            };
1059
1060            # Log error and continue.
1061            if ($@) {
1062                my $ErrorMessage = $@;
1063                $Kernel::OM->Get('Kernel::System::Log')->Log(
1064                    Priority => 'error',
1065                    Message  => $ErrorMessage,
1066                );
1067            }
1068        }
1069
1070        # pre application module
1071        my $PreModule = $ConfigObject->Get('PreApplicationModule');
1072        if ($PreModule) {
1073            my %PreModuleList;
1074            if ( ref $PreModule eq 'HASH' ) {
1075                %PreModuleList = %{$PreModule};
1076            }
1077            else {
1078                $PreModuleList{Init} = $PreModule;
1079            }
1080
1081            MODULE:
1082            for my $PreModuleKey ( sort keys %PreModuleList ) {
1083                my $PreModule = $PreModuleList{$PreModuleKey};
1084                next MODULE if !$PreModule;
1085                next MODULE if !$Kernel::OM->Get('Kernel::System::Main')->Require($PreModule);
1086
1087                # debug info
1088                if ( $Self->{Debug} ) {
1089                    $Kernel::OM->Get('Kernel::System::Log')->Log(
1090                        Priority => 'debug',
1091                        Message  => "PreApplication module $PreModule is used.",
1092                    );
1093                }
1094
1095                my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
1096
1097                # use module
1098                my $PreModuleObject = $PreModule->new(
1099                    %Param,
1100                    %UserData,
1101                    ModuleReg => $ModuleReg,
1102                );
1103                my $Output = $PreModuleObject->PreRun();
1104                if ($Output) {
1105                    $LayoutObject->Print( Output => \$Output );
1106                    return 1;
1107                }
1108            }
1109        }
1110
1111        # debug info
1112        if ( $Self->{Debug} ) {
1113            $Kernel::OM->Get('Kernel::System::Log')->Log(
1114                Priority => 'debug',
1115                Message  => 'Kernel::Modules::' . $Param{Action} . '->new',
1116            );
1117        }
1118
1119        my $FrontendObject = ( 'Kernel::Modules::' . $Param{Action} )->new(
1120            %Param,
1121            %UserData,
1122            ModuleReg => $ModuleReg,
1123            Debug     => $Self->{Debug},
1124        );
1125
1126        # debug info
1127        if ( $Self->{Debug} ) {
1128            $Kernel::OM->Get('Kernel::System::Log')->Log(
1129                Priority => 'debug',
1130                Message  => 'Kernel::Modules::' . $Param{Action} . '->run',
1131            );
1132        }
1133
1134        # ->Run $Action with $FrontendObject
1135        $Kernel::OM->Get('Kernel::Output::HTML::Layout')->Print( Output => \$FrontendObject->Run() );
1136
1137        # log request time
1138        if ( $ConfigObject->Get('PerformanceLog') ) {
1139            if ( ( !$QueryString && $Param{Action} ) || $QueryString !~ /Action=/ ) {
1140                $QueryString = 'Action=' . $Param{Action} . '&Subaction=' . $Param{Subaction};
1141            }
1142            my $File = $ConfigObject->Get('PerformanceLog::File');
1143
1144            # Write to PerformanceLog file only if it is smaller than size limit (see bug#14747).
1145            if ( -s $File < ( 1024 * 1024 * $ConfigObject->Get('PerformanceLog::FileMax') ) ) {
1146
1147                ## no critic
1148                if ( open my $Out, '>>', $File ) {
1149                    ## use critic
1150                    print $Out time()
1151                        . '::Agent::'
1152                        . ( time() - $Self->{PerformanceLogStart} )
1153                        . "::$UserData{UserLogin}::$QueryString\n";
1154                    close $Out;
1155
1156                    $Kernel::OM->Get('Kernel::System::Log')->Log(
1157                        Priority => 'debug',
1158                        Message  => "Response::Agent: "
1159                            . ( time() - $Self->{PerformanceLogStart} )
1160                            . "s taken (URL:$QueryString:$UserData{UserLogin})",
1161                    );
1162                }
1163                else {
1164                    $Kernel::OM->Get('Kernel::System::Log')->Log(
1165                        Priority => 'error',
1166                        Message  => "Can't write $File: $!",
1167                    );
1168                }
1169            }
1170            else {
1171                $Kernel::OM->Get('Kernel::System::Log')->Log(
1172                    Priority => 'error',
1173                    Message => "PerformanceLog file '$File' is too large, you need to reset it in PerformanceLog page!",
1174                );
1175            }
1176        }
1177        return 1;
1178    }
1179
1180    # print an error screen
1181    my %Data = $SessionObject->GetSessionIDData(
1182        SessionID => $Param{SessionID},
1183    );
1184    $Data{UserTimeZone} = $Self->_UserTimeZoneGet(%Data);
1185    $Kernel::OM->ObjectParamAdd(
1186        'Kernel::Output::HTML::Layout' => {
1187            %Param,
1188            %Data,
1189        },
1190    );
1191    $Kernel::OM->Get('Kernel::Output::HTML::Layout')->FatalError(
1192        Comment => Translatable('Please contact the administrator.'),
1193    );
1194    return;
1195}
1196
1197=begin Internal:
1198
1199=head2 _UserTimeZoneGet()
1200
1201Get time zone for the current user. This function will validate passed time zone parameter and return default user time
1202zone if it's not valid.
1203
1204    my $UserTimeZone = $Self->_UserTimeZoneGet(
1205        UserTimeZone => 'Europe/Berlin',
1206    );
1207
1208=cut
1209
1210sub _UserTimeZoneGet {
1211    my ( $Self, %Param ) = @_;
1212
1213    my $UserTimeZone;
1214
1215    # Return passed time zone only if it's valid. It can happen that user preferences or session store an old-style
1216    #   offset which is not valid anymore. In this case, return the default value.
1217    #   Please see bug#13374 for more information.
1218    if (
1219        $Param{UserTimeZone}
1220        && Kernel::System::DateTime->IsTimeZoneValid( TimeZone => $Param{UserTimeZone} )
1221        )
1222    {
1223        $UserTimeZone = $Param{UserTimeZone};
1224    }
1225
1226    $UserTimeZone ||= Kernel::System::DateTime->UserDefaultTimeZoneGet();
1227
1228    return $UserTimeZone;
1229}
1230
1231=end Internal:
1232
1233=cut
1234
1235sub DESTROY {
1236    my $Self = shift;
1237
1238    # debug info
1239    if ( $Self->{Debug} ) {
1240        $Kernel::OM->Get('Kernel::System::Log')->Log(
1241            Priority => 'debug',
1242            Message  => 'Global handle stopped.',
1243        );
1244    }
1245
1246    return 1;
1247}
1248
12491;
1250
1251=head1 TERMS AND CONDITIONS
1252
1253This software is part of the OTRS project (L<https://otrs.org/>).
1254
1255This software comes with ABSOLUTELY NO WARRANTY. For details, see
1256the enclosed file COPYING for license information (GPL). If you
1257did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>.
1258
1259=cut
1260