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