From 9c6523ef39f49340ca3efd6bd5ddd868473baed3 Mon Sep 17 00:00:00 2001 From: Rodrigo Goncalves Date: Fri, 15 Dec 2017 10:20:35 -0200 Subject: [PATCH] Support for OTRS 6.0.1 --- .gitignore | 1 + .includepath | 6 ++++++ .project | 18 ++++++++++++++++++ CASAuthentication.sopm | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Custom/Kernel/System/Web/InterfaceAgent.pm | 1242 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Custom/Kernel/System/Web/InterfaceCustomer.pm | 1512 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Custom/Kernel/System/Web/Request.pm | 582 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Kernel/System/Auth/CAS.pm | 154 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Kernel/System/CustomerAuth/CAS.pm | 154 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 41 +++++++++++++++++++++++++++++++++++++++++ dist/CreateOpm.sh | 8 ++++++++ 11 files changed, 3774 insertions(+), 0 deletions(-) create mode 100644 .gitignore create mode 100644 .includepath create mode 100644 .project create mode 100755 CASAuthentication.sopm create mode 100644 Custom/Kernel/System/Web/InterfaceAgent.pm create mode 100644 Custom/Kernel/System/Web/InterfaceCustomer.pm create mode 100644 Custom/Kernel/System/Web/Request.pm create mode 100644 Kernel/System/Auth/CAS.pm create mode 100755 Kernel/System/CustomerAuth/CAS.pm create mode 100644 README.md create mode 100755 dist/CreateOpm.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a2d7cfd --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +dist/*.opm diff --git a/.includepath b/.includepath new file mode 100644 index 0000000..da00c9f --- /dev/null +++ b/.includepath @@ -0,0 +1,6 @@ + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..40a4a47 --- /dev/null +++ b/.project @@ -0,0 +1,18 @@ + + + otrs-cas + + + otrs + + + + org.epic.perleditor.perlbuilder + + + + + + org.epic.perleditor.perlnature + + diff --git a/CASAuthentication.sopm b/CASAuthentication.sopm new file mode 100755 index 0000000..0880f2d --- /dev/null +++ b/CASAuthentication.sopm @@ -0,0 +1,56 @@ + + + CASAuthentication + 1.3.0 + 6.0.1 + Rodrigo Gonçalves (rodrigo@goncalves.pro.br) + http://www.goncalves.pro.br/ + AGPL + + First Version + Support for OTRS 4 + Bug in customer authentication for OTRS 4 + Support for users not in database (custom redirect) + Fix for authentication with redirect URL + Support for OTRS 5.0.x and fix for agent redirection on first link + Support for OTRS 6.0.1 + + CAS Authentication (Jasig) Module + + ? + ? + +
+ + In order to active the module, the following lines should be included/changed in Kernel/Config.pm:

+ + $Self->{'AuthModule'} = 'Kernel::System::Auth::CAS';
+ $Self->{'AuthModule::CAS::Gateway'} = 0;
+ $Self->{'AuthModule::CAS::ServiceUrl'} = 'URL FOR OTRS AGENT INDEX (ex: https://host.com/otrs/index.pl)';
+ $Self->{'AuthModule::CAS::CASUrl'} = 'URL FOR CAS SERVICE (WITHOUT TRAILING /) - ex: https://cas.systems.com';
+
+ $Self->{'Customer::AuthModule'} = 'Kernel::System::CustomerAuth::CAS';
+ $Self->{'Customer::AuthModule::CAS::Gateway'} = 0;
+ $Self->{'Customer::AuthModule::CAS::ServiceUrl'} = 'URL FOR OTRS CUSTOMER INDEX (ex: https://host.com/otrs/customer.pl)';
+ $Self->{'Customer::AuthModule::CAS::CASUrl'} = 'URL FOR CAS SERVICE (WITHOUT TRAILING /) - ex: https://cas.systems.com';
+ + ]]>
+ + AuthCAS + + + + + + + + + + + + + + + +
diff --git a/Custom/Kernel/System/Web/InterfaceAgent.pm b/Custom/Kernel/System/Web/InterfaceAgent.pm new file mode 100644 index 0000000..6ab18a7 --- /dev/null +++ b/Custom/Kernel/System/Web/InterfaceAgent.pm @@ -0,0 +1,1242 @@ +# -- +# Copyright (C) 2001-2017 OTRS AG, http://otrs.com/ +# -- +# This software comes with ABSOLUTELY NO WARRANTY. For details, see +# the enclosed file COPYING for license information (AGPL). If you +# did not receive this file, see http://www.gnu.org/licenses/agpl.txt. +# +# +# Custom version for CAS authentication - rodrigo@goncalves.pro.br +# +# Version 18/01/2016 - RG - Version for OTRS 5.0.6 +# Version 2017-12-07 - RG - Version for OTRS 6.0.1 +# +# -- + +package Kernel::System::Web::InterfaceAgent; + +use strict; +use warnings; + +# CAS Custom +use CGI; +use URI::Escape; +# CAS Custom + +use Kernel::Language qw(Translatable); +use Kernel::System::DateTime; + +our @ObjectDependencies = ( + 'Kernel::Config', + 'Kernel::Output::HTML::Layout', + 'Kernel::System::Auth', + 'Kernel::System::AuthSession', + 'Kernel::System::DB', + 'Kernel::System::Email', + 'Kernel::System::Group', + 'Kernel::System::Log', + 'Kernel::System::Main', + 'Kernel::System::Scheduler', + 'Kernel::System::DateTime', + 'Kernel::System::User', + 'Kernel::System::Web::Request', + 'Kernel::System::Valid', +); + +=head1 NAME + +Kernel::System::Web::InterfaceAgent - the agent web interface + +=head1 DESCRIPTION + +the global agent web interface (authentication, session handling, ...) + +=head1 PUBLIC INTERFACE + +=head2 new() + +create agent web interface object. Do not use it directly, instead use: + + use Kernel::System::ObjectManager; + my $Debug = 0, + local $Kernel::OM = Kernel::System::ObjectManager->new( + 'Kernel::System::Web::InterfaceAgent' => { + Debug => 0, + WebRequest => CGI::Fast->new(), # optional, e. g. if fast cgi is used, + # the CGI object is already provided + } + ); + my $InterfaceAgent = $Kernel::OM->Get('Kernel::System::Web::InterfaceAgent'); + +=cut + +sub new { + my ( $Type, %Param ) = @_; + my $Self = {}; + bless( $Self, $Type ); + + # Performance log + $Self->{PerformanceLogStart} = time(); + + # get debug level + $Self->{Debug} = $Param{Debug} || 0; + + $Kernel::OM->ObjectParamAdd( + 'Kernel::System::Log' => { + LogPrefix => $Kernel::OM->Get('Kernel::Config')->Get('CGILogPrefix'), + }, + 'Kernel::System::Web::Request' => { + WebRequest => $Param{WebRequest} || 0, + }, + ); + + # debug info + if ( $Self->{Debug} ) { + $Kernel::OM->Get('Kernel::System::Log')->Log( + Priority => 'debug', + Message => 'Global handle started...', + ); + } + + return $Self; +} + +=head2 Run() + +execute the object + + $InterfaceAgent->Run(); + +=cut + +sub Run { + my $Self = shift; + + # CAS Custom + if ($Self->CASLogout()) { + return; + } + # CAS Custom + + my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); + + my $QueryString = $ENV{QUERY_STRING} || ''; + + # Check if https forcing is active, and redirect if needed. + if ( $ConfigObject->Get('HTTPSForceRedirect') ) { + + # Some web servers do not set HTTPS environment variable, so it's not possible to easily know if we are using + # https protocol. Look also for similarly named keys in environment hash, since this should prevent loops in + # certain cases. + if ( + ( + !defined $ENV{HTTPS} + && !grep {/^HTTPS(?:_|$)/} keys %ENV + ) + || $ENV{HTTPS} ne 'on' + ) + { + my $Host = $ENV{HTTP_HOST} || $ConfigObject->Get('FQDN'); + + # Redirect with 301 code. Add two new lines at the end, so HTTP headers are validated correctly. + print "Status: 301 Moved Permanently\nLocation: https://$Host$ENV{REQUEST_URI}\n\n"; + return; + } + } + + my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request'); + + my %Param; + + # get session id + $Param{SessionName} = $ConfigObject->Get('SessionName') || 'SessionID'; + $Param{SessionID} = $ParamObject->GetParam( Param => $Param{SessionName} ) || ''; + + # drop old session id (if exists) + $QueryString =~ s/(\?|&|;|)$Param{SessionName}(=&|=;|=.+?&|=.+?$)/;/g; + + # define framework params + my $FrameworkParams = { + Lang => '', + Action => '', + Subaction => '', + RequestedURL => $QueryString, + }; + for my $Key ( sort keys %{$FrameworkParams} ) { + $Param{$Key} = $ParamObject->GetParam( Param => $Key ) + || $FrameworkParams->{$Key}; + } + + # validate language + if ( $Param{Lang} && $Param{Lang} !~ m{\A[a-z]{2}(?:_[A-Z]{2})?\z}xms ) { + delete $Param{Lang}; + } + + # check if the browser sends the SessionID cookie and set the SessionID-cookie + # as SessionID! GET or POST SessionID have the lowest priority. + my $BrowserHasCookie = 0; + if ( $ConfigObject->Get('SessionUseCookie') ) { + $Param{SessionIDCookie} = $ParamObject->GetCookie( Key => $Param{SessionName} ); + if ( $Param{SessionIDCookie} ) { + $Param{SessionID} = $Param{SessionIDCookie}; + } + } + + $Kernel::OM->ObjectParamAdd( + 'Kernel::Output::HTML::Layout' => { + Lang => $Param{Lang}, + UserLanguage => $Param{Lang}, + }, + 'Kernel::Language' => { + UserLanguage => $Param{Lang} + }, + ); + + my $CookieSecureAttribute; + if ( $ConfigObject->Get('HttpType') eq 'https' ) { + + # Restrict Cookie to HTTPS if it is used. + $CookieSecureAttribute = 1; + } + + my $DBCanConnect = $Kernel::OM->Get('Kernel::System::DB')->Connect(); + + if ( !$DBCanConnect || $ParamObject->Error() ) { + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + if ( !$DBCanConnect ) { + $LayoutObject->FatalError( + Comment => Translatable('Please contact the administrator.'), + ); + return; + } + if ( $ParamObject->Error() ) { + $LayoutObject->FatalError( + Message => $ParamObject->Error(), + Comment => Translatable('Please contact the administrator.'), + ); + return; + } + } + + # get common application and add-on application params + my %CommonObjectParam = %{ $ConfigObject->Get('Frontend::CommonParam') }; + for my $Key ( sort keys %CommonObjectParam ) { + $Param{$Key} = $ParamObject->GetParam( Param => $Key ) || $CommonObjectParam{$Key}; + } + + # security check Action Param (replace non word chars) + $Param{Action} =~ s/\W//g; + + my $SessionObject = $Kernel::OM->Get('Kernel::System::AuthSession'); + my $UserObject = $Kernel::OM->Get('Kernel::System::User'); + + # check request type + if ( $Param{Action} eq 'PreLogin' ) { + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + $Param{RequestedURL} = $Param{RequestedURL} || "Action=AgentDashboard"; + + # login screen + $LayoutObject->Print( + Output => \$LayoutObject->Login( + Title => 'Login', + Mode => 'PreLogin', + %Param, + ), + ); + + return; + } + elsif ( $Param{Action} eq 'Login' ) { + + # get params + my $PostUser = $ParamObject->GetParam( Param => 'User' ) || ''; + my $PostPw = $ParamObject->GetParam( + Param => 'Password', + Raw => 1 + ) || ''; + my $PostTwoFactorToken = $ParamObject->GetParam( + Param => 'TwoFactorToken', + Raw => 1 + ) || ''; + + # create AuthObject + my $AuthObject = $Kernel::OM->Get('Kernel::System::Auth'); + + # check submitted data + my $User = $AuthObject->Auth( + User => $PostUser, + Pw => $PostPw, + TwoFactorToken => $PostTwoFactorToken, + RequestedURL => $Param{RequestedURL}, + ); + + # login is invalid + if ( !$User ) { + + # CAS Custom + # Fixes OTRS Layout bug when redirecting + my $cgi = new CGI(); + print $cgi->redirect( -URL => $ConfigObject->Get("AuthModule::CAS::CASUrl") . "/login?service=" . $ConfigObject->Get("AuthModule::CAS::ServiceUrl") . "?" . $Param{RequestedURL}); + return; + # CAS Custom + + my $Expires = '+' . $ConfigObject->Get('SessionMaxTime') . 's'; + if ( !$ConfigObject->Get('SessionUseCookieAfterBrowserClose') ) { + $Expires = ''; + } + + $Kernel::OM->ObjectParamAdd( + 'Kernel::Output::HTML::Layout' => { + SetCookies => { + OTRSBrowserHasCookie => $ParamObject->SetCookie( + Key => 'OTRSBrowserHasCookie', + Value => 1, + Expires => $Expires, + Path => $ConfigObject->Get('ScriptAlias'), + Secure => $CookieSecureAttribute, + HTTPOnly => 1, + ), + }, + } + ); + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + + # redirect to alternate login + if ( $ConfigObject->Get('LoginURL') ) { + $Param{RequestedURL} = $LayoutObject->LinkEncode( $Param{RequestedURL} ); + print $LayoutObject->Redirect( + ExtURL => $ConfigObject->Get('LoginURL') + . "?Reason=LoginFailed&RequestedURL=$Param{RequestedURL}", + ); + return; + } + + # show normal login + $LayoutObject->Print( + Output => \$LayoutObject->Login( + Title => 'Login', + Message => $Kernel::OM->Get('Kernel::System::Log')->GetLogEntry( + Type => 'Info', + What => 'Message', + ) + || $LayoutObject->{LanguageObject}->Translate( $AuthObject->GetLastErrorMessage() ) + || Translatable('Login failed! Your user name or password was entered incorrectly.'), + LoginFailed => 1, + MessageType => 'Error', + User => $User, + %Param, + ), + ); + return; + } + + # login is successful + my %UserData = $UserObject->GetUserData( + User => $User, + Valid => 1 + ); + + # check if the browser supports cookies + if ( $ParamObject->GetCookie( Key => 'OTRSBrowserHasCookie' ) ) { + $Kernel::OM->ObjectParamAdd( + 'Kernel::Output::HTML::Layout' => { + BrowserHasCookie => 1, + }, + ); + } + + # check needed data + if ( !$UserData{UserID} || !$UserData{UserLogin} ) { + + # redirect to alternate login + if ( $ConfigObject->Get('LoginURL') ) { + print $Kernel::OM->Get('Kernel::Output::HTML::Layout')->Redirect( + ExtURL => $ConfigObject->Get('LoginURL') . '?Reason=SystemError', + ); + return; + } + + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + + # show need user data error message + $LayoutObject->Print( + Output => \$LayoutObject->Login( + Title => 'Error', + Message => + Translatable( + 'Authentication succeeded, but no user data record is found in the database. Please contact the administrator.' + ), + %Param, + MessageType => 'Error', + ), + ); + return; + } + + my $DateTimeObj = $Kernel::OM->Create('Kernel::System::DateTime'); + + # create new session id + my $NewSessionID = $SessionObject->CreateSessionID( + %UserData, + UserLastRequest => $DateTimeObj->ToEpoch(), + UserType => 'User', + SessionSource => 'AgentInterface', + ); + + # show error message if no session id has been created + if ( !$NewSessionID ) { + + # get error message + my $Error = $SessionObject->SessionIDErrorMessage() || ''; + + # output error message + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + $LayoutObject->Print( + Output => \$LayoutObject->Login( + Title => 'Login', + Message => $Error, + MessageType => 'Error', + %Param, + ), + ); + return; + } + + # execution in 20 seconds + my $ExecutionTimeObj = $DateTimeObj->Clone(); + $ExecutionTimeObj->Add( Seconds => 20 ); + my $ExecutionTime = $ExecutionTimeObj->ToString(); + + # add a asychronous executor scheduler task to count the concurrent user + $Kernel::OM->Get('Kernel::System::Scheduler')->TaskAdd( + ExecutionTime => $ExecutionTime, + Type => 'AsynchronousExecutor', + Name => 'PluginAsynchronous::ConcurrentUser', + MaximumParallelInstances => 1, + Data => { + Object => 'Kernel::System::SupportDataCollector::PluginAsynchronous::OTRS::ConcurrentUsers', + Function => 'RunAsynchronous', + }, + ); + + # get time zone + my $UserTimeZone = $UserData{UserTimeZone} || Kernel::System::DateTime->UserDefaultTimeZoneGet(); + $SessionObject->UpdateSessionID( + SessionID => $NewSessionID, + Key => 'UserTimeZone', + Value => $UserTimeZone, + ); + + # check if the time zone offset reported by the user's browser differs from that + # of the OTRS user's time zone offset + my $DateTimeObject = $Kernel::OM->Create( + 'Kernel::System::DateTime', + ObjectParams => { + TimeZone => $UserTimeZone, + }, + ); + my $OTRSUserTimeZoneOffset = $DateTimeObject->Format( Format => '%{offset}' ) / 60; + my $BrowserTimeZoneOffset = ( $ParamObject->GetParam( Param => 'TimeZoneOffset' ) || 0 ) * -1; + + # TimeZoneOffsetDifference contains the difference of the time zone offset between + # the user's OTRS time zone setting and the one reported by the user's browser. + # If there is a difference it can be evaluated later to e. g. show a message + # for the user to check his OTRS time zone setting. + my $UserTimeZoneOffsetDifference = abs( $OTRSUserTimeZoneOffset - $BrowserTimeZoneOffset ); + $SessionObject->UpdateSessionID( + SessionID => $NewSessionID, + Key => 'UserTimeZoneOffsetDifference', + Value => $UserTimeZoneOffsetDifference, + ); + + # create a new LayoutObject with SessionIDCookie + my $Expires = '+' . $ConfigObject->Get('SessionMaxTime') . 's'; + if ( !$ConfigObject->Get('SessionUseCookieAfterBrowserClose') ) { + $Expires = ''; + } + + my $SecureAttribute; + if ( $ConfigObject->Get('HttpType') eq 'https' ) { + + # Restrict Cookie to HTTPS if it is used. + $SecureAttribute = 1; + } + + $Kernel::OM->ObjectParamAdd( + 'Kernel::Output::HTML::Layout' => { + SetCookies => { + SessionIDCookie => $ParamObject->SetCookie( + Key => $Param{SessionName}, + Value => $NewSessionID, + Expires => $Expires, + Path => $ConfigObject->Get('ScriptAlias'), + Secure => scalar $CookieSecureAttribute, + HTTPOnly => 1, + ), + OTRSBrowserHasCookie => $ParamObject->SetCookie( + Key => 'OTRSBrowserHasCookie', + Value => '', + Expires => '-1y', + Path => $ConfigObject->Get('ScriptAlias'), + Secure => $CookieSecureAttribute, + HTTPOnly => 1, + ), + }, + SessionID => $NewSessionID, + SessionName => $Param{SessionName}, + }, + ); + + # Check if Chat is active + if ( $Kernel::OM->Get('Kernel::Config')->Get('ChatEngine::Active') ) { + my $ChatReceivingAgentsGroup + = $Kernel::OM->Get('Kernel::Config')->Get('ChatEngine::PermissionGroup::ChatReceivingAgents'); + + my $ChatReceivingAgentsGroupPermission = $Kernel::OM->Get('Kernel::System::Group')->PermissionCheck( + UserID => $UserData{UserID}, + GroupName => $ChatReceivingAgentsGroup, + Type => 'rw', + ); + + if ( + $UserData{UserID} != -1 + && $ChatReceivingAgentsGroup + && $ChatReceivingAgentsGroupPermission + && $Kernel::OM->Get('Kernel::Config')->Get('Ticket::Agent::UnavailableForExternalChatsOnLogin') + ) + { + # Get user preferences + my %Preferences = $Kernel::OM->Get('Kernel::System::User')->GetPreferences( + UserID => $UserData{UserID}, + ); + + if ( $Preferences{ChatAvailability} && $Preferences{ChatAvailability} == 2 ) { + + # User is available for external chats. Set his availability to internal only. + $Kernel::OM->Get('Kernel::System::User')->SetPreferences( + Key => 'ChatAvailability', + Value => '1', + UserID => $UserData{UserID}, + ); + + # Set ChatAvailabilityNotification to display notification in agent interface (only once) + $Kernel::OM->Get('Kernel::System::User')->SetPreferences( + Key => 'ChatAvailabilityNotification', + Value => '1', + UserID => $UserData{UserID}, + ); + } + } + } + + # redirect with new session id and old params + # prepare old redirect URL -- do not redirect to Login or Logout (loop)! + if ( $Param{RequestedURL} =~ /Action=(Logout|Login|LostPassword|PreLogin)/ ) { + $Param{RequestedURL} = ''; + } + + # redirect with new session id + print $Kernel::OM->Get('Kernel::Output::HTML::Layout')->Redirect( + OP => $Param{RequestedURL}, + Login => 1, + ); + return 1; + } + + # logout + elsif ( $Param{Action} eq 'Logout' ) { + + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + + # check session id + if ( !$SessionObject->CheckSessionID( SessionID => $Param{SessionID} ) ) { + + # redirect to alternate login + if ( $ConfigObject->Get('LoginURL') ) { + $Param{RequestedURL} = $LayoutObject->LinkEncode( $Param{RequestedURL} ); + print $LayoutObject->Redirect( + ExtURL => $ConfigObject->Get('LoginURL') + . "?Reason=InvalidSessionID&RequestedURL=$Param{RequestedURL}", + ); + return; + } + + # show login screen + $LayoutObject->Print( + Output => \$LayoutObject->Login( + Title => 'Logout', + %Param, + ), + ); + return; + } + + # get session data + my %UserData = $SessionObject->GetSessionIDData( + SessionID => $Param{SessionID}, + ); + + # create a new LayoutObject with %UserData + $Kernel::OM->ObjectParamAdd( + 'Kernel::Output::HTML::Layout' => { + SetCookies => { + SessionIDCookie => $ParamObject->SetCookie( + Key => $Param{SessionName}, + Value => '', + Expires => '-1y', + Path => $ConfigObject->Get('ScriptAlias'), + Secure => scalar $CookieSecureAttribute, + HTTPOnly => 1, + ), + }, + %UserData, + }, + ); + $Kernel::OM->ObjectsDiscard( Objects => ['Kernel::Output::HTML::Layout'] ); + $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + + # Prevent CSRF attacks + $LayoutObject->ChallengeTokenCheck(); + + # remove session id + if ( !$SessionObject->RemoveSessionID( SessionID => $Param{SessionID} ) ) { + $LayoutObject->FatalError( + Message => Translatable('Can`t remove SessionID.'), + Comment => Translatable('Please contact the administrator.'), + ); + return; + } + + # redirect to alternate login + if ( $ConfigObject->Get('LogoutURL') ) { + print $LayoutObject->Redirect( + ExtURL => $ConfigObject->Get('LogoutURL') . "?Reason=Logout", + ); + return 1; + } + + # show logout screen + my $LogoutMessage = $LayoutObject->{LanguageObject}->Translate('Logout successful.'); + + $LayoutObject->Print( + Output => \$LayoutObject->Login( + Title => 'Logout', + Message => $LogoutMessage, + MessageType => 'Success', + %Param, + ), + ); + return 1; + } + + # user lost password + elsif ( $Param{Action} eq 'LostPassword' ) { + + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + + # check feature + if ( !$ConfigObject->Get('LostPassword') ) { + + # show normal login + $LayoutObject->Print( + Output => \$LayoutObject->Login( + Title => 'Login', + Message => Translatable('Feature not active!'), + MessageType => 'Error', + ), + ); + return; + } + + # get params + my $User = $ParamObject->GetParam( Param => 'User' ) || ''; + my $Token = $ParamObject->GetParam( Param => 'Token' ) || ''; + + # get user login by token + if ( !$User && $Token ) { + my %UserList = $UserObject->SearchPreferences( + Key => 'UserToken', + Value => $Token, + ); + USERS: + for my $UserID ( sort keys %UserList ) { + my %UserData = $UserObject->GetUserData( + UserID => $UserID, + Valid => 1, + ); + if (%UserData) { + $User = $UserData{UserLogin}; + last USERS; + } + } + } + + # get user data + my %UserData = $UserObject->GetUserData( + User => $User, + Valid => 1 + ); + + # verify user is valid when requesting password reset + my @ValidIDs = $Kernel::OM->Get('Kernel::System::Valid')->ValidIDsGet(); + my $UserIsValid = grep { $UserData{ValidID} && $UserData{ValidID} == $_ } @ValidIDs; + if ( !$UserData{UserID} || !$UserIsValid ) { + + # Security: pretend that password reset instructions were actually sent to + # make sure that users cannot find out valid usernames by + # just trying and checking the result message. + $LayoutObject->Print( + Output => \$LayoutObject->Login( + Title => 'Login', + Message => Translatable('Sent password reset instructions. Please check your email.'), + MessageType => 'Success', + %Param, + ), + ); + return; + } + + # create email object + my $EmailObject = $Kernel::OM->Get('Kernel::System::Email'); + + # send password reset token + if ( !$Token ) { + + # generate token + $UserData{Token} = $UserObject->TokenGenerate( + UserID => $UserData{UserID}, + ); + + # send token notify email with link + my $Body = $ConfigObject->Get('NotificationBodyLostPasswordToken') + || 'ERROR: NotificationBodyLostPasswordToken is missing!'; + my $Subject = $ConfigObject->Get('NotificationSubjectLostPasswordToken') + || 'ERROR: NotificationSubjectLostPasswordToken is missing!'; + for ( sort keys %UserData ) { + $Body =~ s//$UserData{$_}/gi; + } + my $Sent = $EmailObject->Send( + To => $UserData{UserEmail}, + Subject => $Subject, + Charset => $LayoutObject->{UserCharset}, + MimeType => 'text/plain', + Body => $Body + ); + if ( !$Sent->{Success} ) { + $LayoutObject->FatalError( + Comment => Translatable('Please contact the administrator.'), + ); + return; + } + $LayoutObject->Print( + Output => \$LayoutObject->Login( + Title => 'Login', + Message => Translatable('Sent password reset instructions. Please check your email.'), + MessageType => 'Success', + %Param, + ), + ); + return 1; + } + + # reset password + # check if token is valid + my $TokenValid = $UserObject->TokenCheck( + Token => $Token, + UserID => $UserData{UserID}, + ); + if ( !$TokenValid ) { + $LayoutObject->Print( + Output => \$LayoutObject->Login( + Title => 'Login', + Message => Translatable('Invalid Token!'), + MessageType => 'Error', + %Param, + ), + ); + return; + } + + # get new password + $UserData{NewPW} = $UserObject->GenerateRandomPassword(); + + # update new password + $UserObject->SetPassword( + UserLogin => $User, + PW => $UserData{NewPW} + ); + + # send notify email + my $Body = $ConfigObject->Get('NotificationBodyLostPassword') + || 'New Password is: '; + my $Subject = $ConfigObject->Get('NotificationSubjectLostPassword') + || 'New Password!'; + for ( sort keys %UserData ) { + $Body =~ s//$UserData{$_}/gi; + } + my $Sent = $EmailObject->Send( + To => $UserData{UserEmail}, + Subject => $Subject, + Charset => $LayoutObject->{UserCharset}, + MimeType => 'text/plain', + Body => $Body + ); + + if ( !$Sent->{Success} ) { + $LayoutObject->FatalError( + Comment => Translatable('Please contact the administrator.'), + ); + return; + } + my $Message = $LayoutObject->{LanguageObject}->Translate( + 'Sent new password to %s. Please check your email.', + $UserData{UserEmail}, + ); + $LayoutObject->Print( + Output => \$LayoutObject->Login( + Title => 'Login', + Message => $Message, + User => $User, + MessageType => 'Success', + %Param, + ), + ); + return 1; + } + + # show login site + elsif ( !$Param{SessionID} ) { + + # create AuthObject + my $AuthObject = $Kernel::OM->Get('Kernel::System::Auth'); + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + if ( $AuthObject->GetOption( What => 'PreAuth' ) ) { + + # automatic login + $Param{RequestedURL} = $LayoutObject->LinkEncode( $Param{RequestedURL} ); + print $LayoutObject->Redirect( + OP => "Action=PreLogin&RequestedURL=$Param{RequestedURL}", + ); + return; + } + elsif ( $ConfigObject->Get('LoginURL') ) { + + # redirect to alternate login + $Param{RequestedURL} = $LayoutObject->LinkEncode( $Param{RequestedURL} ); + print $LayoutObject->Redirect( + ExtURL => $ConfigObject->Get('LoginURL') + . "?RequestedURL=$Param{RequestedURL}", + ); + return; + } + + # login screen + $LayoutObject->Print( + Output => \$LayoutObject->Login( + Title => 'Login', + %Param, + ), + ); + return; + } + + # run modules if a version value exists + elsif ( $Kernel::OM->Get('Kernel::System::Main')->Require("Kernel::Modules::$Param{Action}") ) { + + # check session id + if ( !$SessionObject->CheckSessionID( SessionID => $Param{SessionID} ) ) { + + # put '%Param' into LayoutObject + $Kernel::OM->ObjectParamAdd( + 'Kernel::Output::HTML::Layout' => { + SetCookies => { + SessionIDCookie => $ParamObject->SetCookie( + Key => $Param{SessionName}, + Value => '', + Expires => '-1y', + Path => $ConfigObject->Get('ScriptAlias'), + Secure => scalar $CookieSecureAttribute, + HTTPOnly => 1, + ), + }, + %Param, + }, + ); + + $Kernel::OM->ObjectsDiscard( Objects => ['Kernel::Output::HTML::Layout'] ); + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + + # create AuthObject + my $AuthObject = $Kernel::OM->Get('Kernel::System::Auth'); + if ( $AuthObject->GetOption( What => 'PreAuth' ) ) { + + # automatic re-login + $Param{RequestedURL} = $LayoutObject->LinkEncode( $Param{RequestedURL} ); + print $LayoutObject->Redirect( + OP => "?Action=PreLogin&RequestedURL=$Param{RequestedURL}", + ); + return; + } + elsif ( $ConfigObject->Get('LoginURL') ) { + + # redirect to alternate login + $Param{RequestedURL} = $LayoutObject->LinkEncode( $Param{RequestedURL} ); + print $LayoutObject->Redirect( + ExtURL => $ConfigObject->Get('LoginURL') + . "?Reason=InvalidSessionID&RequestedURL=$Param{RequestedURL}", + ); + return; + } + + # show login + $LayoutObject->Print( + Output => \$LayoutObject->Login( + Title => 'Login', + Message => + $LayoutObject->{LanguageObject}->Translate( $SessionObject->SessionIDErrorMessage() ), + MessageType => 'Error', + %Param, + ), + ); + return; + } + + # get session data + my %UserData = $SessionObject->GetSessionIDData( + SessionID => $Param{SessionID}, + ); + + # check needed data + if ( !$UserData{UserID} || !$UserData{UserLogin} || $UserData{UserType} ne 'User' ) { + + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + + # redirect to alternate login + if ( $ConfigObject->Get('LoginURL') ) { + print $LayoutObject->Redirect( + ExtURL => $ConfigObject->Get('LoginURL') . '?Reason=SystemError', + ); + return; + } + + # show login screen + $LayoutObject->Print( + Output => \$LayoutObject->Login( + Title => 'Error', + Message => Translatable('Error: invalid session.'), + MessageType => 'Error', + %Param, + ), + ); + return; + } + + # check module registry + my $ModuleReg = $ConfigObject->Get('Frontend::Module')->{ $Param{Action} }; + if ( !$ModuleReg ) { + + $Kernel::OM->Get('Kernel::System::Log')->Log( + Priority => 'error', + Message => + "Module Kernel::Modules::$Param{Action} not registered in Kernel/Config.pm!", + ); + $Kernel::OM->Get('Kernel::Output::HTML::Layout')->FatalError( + Comment => Translatable('Please contact the administrator.'), + ); + return; + } + + # module permisson check + if ( + ref $ModuleReg->{GroupRo} eq 'ARRAY' + && !scalar @{ $ModuleReg->{GroupRo} } + && ref $ModuleReg->{Group} eq 'ARRAY' + && !scalar @{ $ModuleReg->{Group} } + ) + { + $Param{AccessRo} = 1; + $Param{AccessRw} = 1; + } + else { + my $GroupObject = $Kernel::OM->Get('Kernel::System::Group'); + + PERMISSION: + for my $Permission (qw(GroupRo Group)) { + my $AccessOk = 0; + my $Group = $ModuleReg->{$Permission}; + next PERMISSION if !$Group; + if ( ref $Group eq 'ARRAY' ) { + INNER: + for my $GroupName ( @{$Group} ) { + next INNER if !$GroupName; + next INNER if !$GroupObject->PermissionCheck( + UserID => $UserData{UserID}, + GroupName => $GroupName, + Type => $Permission eq 'GroupRo' ? 'ro' : 'rw', + + ); + $AccessOk = 1; + last INNER; + } + } + else { + my $HasPermission = $GroupObject->PermissionCheck( + UserID => $UserData{UserID}, + GroupName => $Group, + Type => $Permission eq 'GroupRo' ? 'ro' : 'rw', + + ); + if ($HasPermission) { + $AccessOk = 1; + } + } + if ( $Permission eq 'Group' && $AccessOk ) { + $Param{AccessRo} = 1; + $Param{AccessRw} = 1; + } + elsif ( $Permission eq 'GroupRo' && $AccessOk ) { + $Param{AccessRo} = 1; + } + } + if ( !$Param{AccessRo} && !$Param{AccessRw} || !$Param{AccessRo} && $Param{AccessRw} ) { + + print $Kernel::OM->Get('Kernel::Output::HTML::Layout')->NoPermission( + Message => Translatable('No Permission to use this frontend module!') + ); + return; + } + } + + # put '%Param' and '%UserData' into LayoutObject + $Kernel::OM->ObjectParamAdd( + 'Kernel::Output::HTML::Layout' => { + %Param, + %UserData, + ModuleReg => $ModuleReg, + }, + ); + $Kernel::OM->ObjectsDiscard( Objects => ['Kernel::Output::HTML::Layout'] ); + + # update last request time + if ( + !$ParamObject->IsAJAXRequest() + || $Param{Action} eq 'AgentVideoChat' + || + ( + $Param{Action} eq 'AgentChat' + && + $Param{Subaction} ne 'ChatGetOpenRequests' && + $Param{Subaction} ne 'ChatMonitorCheck' + ) + ) + { + my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime'); + + $SessionObject->UpdateSessionID( + SessionID => $Param{SessionID}, + Key => 'UserLastRequest', + Value => $DateTimeObject->ToEpoch(), + ); + } + + # Override user settings. + my $Home = $ConfigObject->Get('Home'); + my $File = "$Home/Kernel/Config/Files/User/$UserData{UserID}.pm"; + if ( -e $File ) { + if ( !require $File ) { + die "ERROR: $!\n"; + } + + # prepare file + $File =~ s/\Q$Home\E//g; + $File =~ s/^\///g; + $File =~ s/\/\//\//g; + $File =~ s/\//::/g; + $File =~ s/\.pm$//g; + $File->Load($ConfigObject); + } + + # pre application module + my $PreModule = $ConfigObject->Get('PreApplicationModule'); + if ($PreModule) { + my %PreModuleList; + if ( ref $PreModule eq 'HASH' ) { + %PreModuleList = %{$PreModule}; + } + else { + $PreModuleList{Init} = $PreModule; + } + + MODULE: + for my $PreModuleKey ( sort keys %PreModuleList ) { + my $PreModule = $PreModuleList{$PreModuleKey}; + next MODULE if !$PreModule; + next MODULE if !$Kernel::OM->Get('Kernel::System::Main')->Require($PreModule); + + # debug info + if ( $Self->{Debug} ) { + $Kernel::OM->Get('Kernel::System::Log')->Log( + Priority => 'debug', + Message => "PreApplication module $PreModule is used.", + ); + } + + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + + # use module + my $PreModuleObject = $PreModule->new( + %Param, + %UserData, + ModuleReg => $ModuleReg, + ); + my $Output = $PreModuleObject->PreRun(); + if ($Output) { + $LayoutObject->Print( Output => \$Output ); + return 1; + } + } + } + + # debug info + if ( $Self->{Debug} ) { + $Kernel::OM->Get('Kernel::System::Log')->Log( + Priority => 'debug', + Message => 'Kernel::Modules::' . $Param{Action} . '->new', + ); + } + + my $FrontendObject = ( 'Kernel::Modules::' . $Param{Action} )->new( + %Param, + %UserData, + ModuleReg => $ModuleReg, + Debug => $Self->{Debug}, + ); + + # debug info + if ( $Self->{Debug} ) { + $Kernel::OM->Get('Kernel::System::Log')->Log( + Priority => 'debug', + Message => 'Kernel::Modules::' . $Param{Action} . '->run', + ); + } + + # ->Run $Action with $FrontendObject + $Kernel::OM->Get('Kernel::Output::HTML::Layout')->Print( Output => \$FrontendObject->Run() ); + + # log request time + if ( $ConfigObject->Get('PerformanceLog') ) { + if ( ( !$QueryString && $Param{Action} ) || $QueryString !~ /Action=/ ) { + $QueryString = 'Action=' . $Param{Action} . '&Subaction=' . $Param{Subaction}; + } + my $File = $ConfigObject->Get('PerformanceLog::File'); + ## no critic + if ( open my $Out, '>>', $File ) { + ## use critic + print $Out time() + . '::Agent::' + . ( time() - $Self->{PerformanceLogStart} ) + . "::$UserData{UserLogin}::$QueryString\n"; + close $Out; + + $Kernel::OM->Get('Kernel::System::Log')->Log( + Priority => 'debug', + Message => "Response::Agent: " + . ( time() - $Self->{PerformanceLogStart} ) + . "s taken (URL:$QueryString:$UserData{UserLogin})", + ); + } + else { + $Kernel::OM->Get('Kernel::System::Log')->Log( + Priority => 'error', + Message => "Can't write $File: $!", + ); + } + } + return 1; + } + + # print an error screen + my %Data = $SessionObject->GetSessionIDData( + SessionID => $Param{SessionID}, + ); + $Kernel::OM->ObjectParamAdd( + 'Kernel::Output::HTML::Layout' => { + %Param, + %Data, + }, + ); + $Kernel::OM->Get('Kernel::Output::HTML::Layout')->FatalError( + Comment => Translatable('Please contact the administrator.'), + ); + return; +} + +sub DESTROY { + my $Self = shift; + + # debug info + if ( $Self->{Debug} ) { + $Kernel::OM->Get('Kernel::System::Log')->Log( + Priority => 'debug', + Message => 'Global handle stopped.', + ); + } + + return 1; +} + +# CAS Custom +=item + Check if it is a CAS logout - if true, logs out user for the session ticket +=cut +sub CASLogout { + my $Self = shift; + + # get post data + my $cgi = new CGI; + my $request = uri_unescape($cgi->query_string()); + + # check if it is CAS logout + if ($request =~ /(logoutRequest=([^<]+)/); + if (! $ticket) { + return 1; + } + + my $DBObject = $Kernel::OM->Get("Kernel::System::DB"); + + # get login for session + my $sqlLogin = 'SELECT UserLogin from cas_session where Ticket=?'; + $DBObject->Prepare(SQL => $sqlLogin, Bind => [\$ticket]); + + my $login; + while (my @Row = $DBObject->FetchrowArray()) { + $login = $Row[0]; + } + + # if a login was found, kill the user session + if ($login) { + $DBObject->Do(SQL => "DELETE FROM sessions WHERE session_id IN (select session_id from (select session_id FROM sessions WHERE data_key='UserLogin' AND data_value=?) as x)", + Bind => [\$login]); + } else { + } + return 1; + } else { + return 0; + } + +} +# CAS Custom + +1; + +=head1 TERMS AND CONDITIONS + +This software is part of the OTRS project (L). + +This software comes with ABSOLUTELY NO WARRANTY. For details, see +the enclosed file COPYING for license information (AGPL). If you +did not receive this file, see L. + +=cut diff --git a/Custom/Kernel/System/Web/InterfaceCustomer.pm b/Custom/Kernel/System/Web/InterfaceCustomer.pm new file mode 100644 index 0000000..a26628d --- /dev/null +++ b/Custom/Kernel/System/Web/InterfaceCustomer.pm @@ -0,0 +1,1512 @@ +# -- +# Copyright (C) 2001-2017 OTRS AG, http://otrs.com/ +# -- +# This software comes with ABSOLUTELY NO WARRANTY. For details, see +# the enclosed file COPYING for license information (AGPL). If you +# did not receive this file, see http://www.gnu.org/licenses/agpl.txt. +# +# +# Custom version for CAS authentication - rodrigo@goncalves.pro.br +# +# Version 2016-01-18 - RG - Version for OTRS 5.0.6 +# Version 2017-12-07 - RG - Version for OTRS 6.0.1 +# +# -- + +package Kernel::System::Web::InterfaceCustomer; + +use strict; +use warnings; + +# CAS Custom +use CGI; +use URI::Escape; +# CAS Custom + +use Kernel::System::DateTime; +use Kernel::System::Email; +use Kernel::System::VariableCheck qw(IsArrayRefWithData IsHashRefWithData); +use Kernel::Language qw(Translatable); + +our @ObjectDependencies = ( + 'Kernel::Config', + 'Kernel::Output::HTML::Layout', + 'Kernel::System::AuthSession', + 'Kernel::System::CustomerAuth', + 'Kernel::System::CustomerGroup', + 'Kernel::System::CustomerUser', + 'Kernel::System::DB', + 'Kernel::System::Group', + 'Kernel::System::Log', + 'Kernel::System::Main', + 'Kernel::System::Scheduler', + 'Kernel::System::DateTime', + 'Kernel::System::Web::Request', + 'Kernel::System::Valid', +); + +=head1 NAME + +Kernel::System::Web::InterfaceCustomer - the customer web interface + +=head1 DESCRIPTION + +the global customer web interface (authentication, session handling, ...) + +=head1 PUBLIC INTERFACE + +=head2 new() + +create customer web interface object + + use Kernel::System::Web::InterfaceCustomer; + + my $Debug = 0; + my $InterfaceCustomer = Kernel::System::Web::InterfaceCustomer->new( + Debug => $Debug, + WebRequest => CGI::Fast->new(), # optional, e. g. if fast cgi is used, the CGI object is already provided + ); + +=cut + +sub new { + my ( $Type, %Param ) = @_; + + # allocate new hash for object + my $Self = {}; + bless( $Self, $Type ); + + # get debug level + $Self->{Debug} = $Param{Debug} || 0; + + # performance log + $Self->{PerformanceLogStart} = time(); + + $Kernel::OM->ObjectParamAdd( + 'Kernel::System::Log' => { + LogPrefix => $Kernel::OM->Get('Kernel::Config')->Get('CGILogPrefix'), + }, + 'Kernel::System::Web::Request' => { + WebRequest => $Param{WebRequest} || 0, + }, + ); + + # debug info + if ( $Self->{Debug} ) { + $Kernel::OM->Get('Kernel::System::Log')->Log( + Priority => 'debug', + Message => 'Global handle started...', + ); + } + + return $Self; +} + +=head2 Run() + +execute the object + + $InterfaceCustomer->Run(); + +=cut + +sub Run { + my $Self = shift; + + # CAS Custom + if ($Self->CASLogout()) { + return; + } + # CAS Custom + + my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); + + my $QueryString = $ENV{QUERY_STRING} || ''; + + # Check if https forcing is active, and redirect if needed. + if ( $ConfigObject->Get('HTTPSForceRedirect') ) { + + # Some web servers do not set HTTPS environment variable, so it's not possible to easily know if we are using + # https protocol. Look also for similarly named keys in environment hash, since this should prevent loops in + # certain cases. + if ( + ( + !defined $ENV{HTTPS} + && !grep {/^HTTPS(?:_|$)/} keys %ENV + ) + || $ENV{HTTPS} ne 'on' + ) + { + my $Host = $ENV{HTTP_HOST} || $ConfigObject->Get('FQDN'); + + # Redirect with 301 code. Add two new lines at the end, so HTTP headers are validated correctly. + print "Status: 301 Moved Permanently\nLocation: https://$Host$ENV{REQUEST_URI}\n\n"; + return; + } + } + + my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request'); + + my %Param; + + # get session id + $Param{SessionName} = $ConfigObject->Get('CustomerPanelSessionName') || 'CSID'; + $Param{SessionID} = $ParamObject->GetParam( Param => $Param{SessionName} ) || ''; + + # drop old session id (if exists) + $QueryString =~ s/(\?|&|;|)$Param{SessionName}(=&|=;|=.+?&|=.+?$)/;/g; + + # define framework params + my $FrameworkParams = { + Lang => '', + Action => '', + Subaction => '', + RequestedURL => $QueryString, + }; + for my $Key ( sort keys %{$FrameworkParams} ) { + $Param{$Key} = $ParamObject->GetParam( Param => $Key ) + || $FrameworkParams->{$Key}; + } + + # validate language + if ( $Param{Lang} && $Param{Lang} !~ m{\A[a-z]{2}(?:_[A-Z]{2})?\z}xms ) { + delete $Param{Lang}; + } + + my $BrowserHasCookie = 0; + + # Check if the browser sends the SessionID cookie and set the SessionID-cookie + # as SessionID! GET or POST SessionID have the lowest priority. + if ( $ConfigObject->Get('SessionUseCookie') ) { + $Param{SessionIDCookie} = $ParamObject->GetCookie( Key => $Param{SessionName} ); + if ( $Param{SessionIDCookie} ) { + $Param{SessionID} = $Param{SessionIDCookie}; + } + } + + my $CookieSecureAttribute; + if ( $ConfigObject->Get('HttpType') eq 'https' ) { + + # Restrict Cookie to HTTPS if it is used. + $CookieSecureAttribute = 1; + } + + $Kernel::OM->ObjectParamAdd( + 'Kernel::Output::HTML::Layout' => { + Lang => $Param{Lang}, + }, + 'Kernel::Language' => { + UserLanguage => $Param{Lang}, + }, + ); + + my $DBCanConnect = $Kernel::OM->Get('Kernel::System::DB')->Connect(); + + if ( !$DBCanConnect || $ParamObject->Error() ) { + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + if ( !$DBCanConnect ) { + $LayoutObject->CustomerFatalError( + Comment => Translatable('Please contact the administrator.'), + ); + return; + } + if ( $ParamObject->Error() ) { + $LayoutObject->CustomerFatalError( + Message => $ParamObject->Error(), + Comment => Translatable('Please contact the administrator.'), + ); + return; + } + } + + my $UserObject = $Kernel::OM->Get('Kernel::System::CustomerUser'); + my $SessionObject = $Kernel::OM->Get('Kernel::System::AuthSession'); + + # get common application and add on application params + my %CommonObjectParam = %{ $ConfigObject->Get('CustomerFrontend::CommonParam') }; + for my $Key ( sort keys %CommonObjectParam ) { + $Param{$Key} = $ParamObject->GetParam( Param => $Key ) || $CommonObjectParam{$Key}; + } + + # security check Action Param (replace non-word chars) + $Param{Action} =~ s/\W//g; + + # check request type + if ( $Param{Action} eq 'PreLogin' ) { + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + + # login screen + $LayoutObject->Print( + Output => \$LayoutObject->CustomerLogin( + Title => 'Login', + Mode => 'PreLogin', + %Param, + ), + ); + + return; + } + elsif ( $Param{Action} eq 'Login' ) { + + # get params + my $PostUser = $ParamObject->GetParam( Param => 'User' ) || ''; + my $PostPw = $ParamObject->GetParam( + Param => 'Password', + Raw => 1 + ) || ''; + my $PostTwoFactorToken = $ParamObject->GetParam( + Param => 'TwoFactorToken', + Raw => 1 + ) || ''; + + # create AuthObject + my $AuthObject = $Kernel::OM->Get('Kernel::System::CustomerAuth'); + + # check submitted data + my $User = $AuthObject->Auth( + User => $PostUser, + Pw => $PostPw, + TwoFactorToken => $PostTwoFactorToken, + RequestedURL => $Param{RequestedURL}, + ); + + my $Expires = '+' . $ConfigObject->Get('SessionMaxTime') . 's'; + if ( !$ConfigObject->Get('SessionUseCookieAfterBrowserClose') ) { + $Expires = ''; + } + + # login is invalid + if ( !$User ) { + + # CAS Custom + # Fixes OTRS Layout bug when redirecting + my $cgi = new CGI(); + print $cgi->redirect( -URL => $ConfigObject->Get("Customer::AuthModule::CAS::CASUrl") . "/login?service=" . $ConfigObject->Get("Customer::AuthModule::CAS::ServiceUrl") . "?" . $Param{RequestedURL}); + return; + # CAS Custom + + $Kernel::OM->ObjectParamAdd( + 'Kernel::Output::HTML::Layout' => { + SetCookies => { + OTRSBrowserHasCookie => $ParamObject->SetCookie( + Key => 'OTRSBrowserHasCookie', + Value => 1, + Expires => $Expires, + Path => $ConfigObject->Get('ScriptAlias'), + Secure => $CookieSecureAttribute, + HTTPOnly => 1, + ), + }, + }, + ); + + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + + # redirect to alternate login + if ( $ConfigObject->Get('CustomerPanelLoginURL') ) { + $Param{RequestedURL} = $LayoutObject->LinkEncode( $Param{RequestedURL} ); + print $LayoutObject->Redirect( + ExtURL => $ConfigObject->Get('CustomerPanelLoginURL') + . "?Reason=LoginFailed;RequestedURL=$Param{RequestedURL}", + ); + return; + } + + # show normal login + $LayoutObject->Print( + Output => \$LayoutObject->CustomerLogin( + Title => 'Login', + Message => $Kernel::OM->Get('Kernel::System::Log')->GetLogEntry( + Type => 'Info', + What => 'Message', + ) + || $AuthObject->GetLastErrorMessage() + || Translatable('Login failed! Your user name or password was entered incorrectly.'), + User => $PostUser, + LoginFailed => 1, + %Param, + ), + ); + return; + } + + # login is successful + my %UserData = $UserObject->CustomerUserDataGet( + User => $User, + Valid => 1 + ); + + # check if the browser supports cookies + if ( $ParamObject->GetCookie( Key => 'OTRSBrowserHasCookie' ) ) { + $Kernel::OM->ObjectParamAdd( + 'Kernel::Output::HTML::Layout' => { + BrowserHasCookie => 1, + }, + ); + } + + # check needed data + if ( !$UserData{UserID} || !$UserData{UserLogin} ) { + + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + + # CAS Custom + my $url = $Kernel::OM->Get("Kernel::Config")->Get('Customer::AuthModule::CAS::InvalidUserURL'); + + if ($url) { + print $LayoutObject->Redirect( ExtURL => $url, ); + return; + } + # CAS Custom + + # redirect to alternate login + if ( $ConfigObject->Get('CustomerPanelLoginURL') ) { + print $LayoutObject->Redirect( + ExtURL => $ConfigObject->Get('CustomerPanelLoginURL') + . '?Reason=SystemError', + ); + return; + } + + # show need user data error message + $LayoutObject->Print( + Output => \$LayoutObject->CustomerLogin( + Title => 'Error', + Message => Translatable( + 'Authentication succeeded, but no customer record is found in the customer backend. Please contact the administrator.' + ), + %Param, + ), + ); + return; + } + + # create datetime object + my $SessionDTObject = $Kernel::OM->Create('Kernel::System::DateTime'); + + # create new session id + my $NewSessionID = $SessionObject->CreateSessionID( + %UserData, + UserLastRequest => $SessionDTObject->ToEpoch(), + UserType => 'Customer', + SessionSource => 'CustomerInterface', + ); + + # show error message if no session id has been created + if ( !$NewSessionID ) { + + # get error message + my $Error = $SessionObject->SessionIDErrorMessage() || ''; + + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + + # output error message + $LayoutObject->Print( + Output => \$LayoutObject->CustomerLogin( + Title => 'Login', + Message => $Error, + %Param, + ), + ); + return; + } + + # execution in 20 seconds + my $ExecutionTimeObj = $Kernel::OM->Create('Kernel::System::DateTime'); + $ExecutionTimeObj->Add( Seconds => 20 ); + + # add a asynchronous executor scheduler task to count the concurrent user + $Kernel::OM->Get('Kernel::System::Scheduler')->TaskAdd( + ExecutionTime => $ExecutionTimeObj->ToString(), + Type => 'AsynchronousExecutor', + Name => 'PluginAsynchronous::ConcurrentUser', + MaximumParallelInstances => 1, + Data => { + Object => 'Kernel::System::SupportDataCollector::PluginAsynchronous::OTRS::ConcurrentUsers', + Function => 'RunAsynchronous', + }, + ); + + # get time zone + my $UserTimeZone = $UserData{UserTimeZone} || Kernel::System::DateTime->UserDefaultTimeZoneGet(); + $SessionObject->UpdateSessionID( + SessionID => $NewSessionID, + Key => 'UserTimeZone', + Value => $UserTimeZone, + ); + + # check if the time zone offset reported by the user's browser differs from that + # of the OTRS user's time zone offset + my $DateTimeObject = $Kernel::OM->Create( + 'Kernel::System::DateTime', + ObjectParams => { + TimeZone => $UserTimeZone, + }, + ); + my $OTRSUserTimeZoneOffset = $DateTimeObject->Format( Format => '%{offset}' ) / 60; + my $BrowserTimeZoneOffset = ( $ParamObject->GetParam( Param => 'TimeZoneOffset' ) || 0 ) * -1; + + # TimeZoneOffsetDifference contains the difference of the time zone offset between + # the user's OTRS time zone setting and the one reported by the user's browser. + # If there is a difference it can be evaluated later to e. g. show a message + # for the user to check his OTRS time zone setting. + my $UserTimeZoneOffsetDifference = abs( $OTRSUserTimeZoneOffset - $BrowserTimeZoneOffset ); + $SessionObject->UpdateSessionID( + SessionID => $NewSessionID, + Key => 'UserTimeZoneOffsetDifference', + Value => $UserTimeZoneOffsetDifference, + ); + + $Kernel::OM->ObjectParamAdd( + 'Kernel::Output::HTML::Layout' => { + SetCookies => { + SessionIDCookie => $ParamObject->SetCookie( + Key => $Param{SessionName}, + Value => $NewSessionID, + Expires => $Expires, + Path => $ConfigObject->Get('ScriptAlias'), + Secure => scalar $CookieSecureAttribute, + HTTPOnly => 1, + ), + OTRSBrowserHasCookie => $ParamObject->SetCookie( + Key => 'OTRSBrowserHasCookie', + Value => '', + Expires => '-1y', + Path => $ConfigObject->Get('ScriptAlias'), + Secure => $CookieSecureAttribute, + HTTPOnly => 1, + ), + + }, + SessionID => $NewSessionID, + SessionName => $Param{SessionName}, + }, + ); + + # redirect with new session id and old params + # prepare old redirect URL -- do not redirect to Login or Logout (loop)! + if ( $Param{RequestedURL} =~ /Action=(Logout|Login|LostPassword|PreLogin)/ ) { + $Param{RequestedURL} = ''; + } + + # redirect with new session id + print $Kernel::OM->Get('Kernel::Output::HTML::Layout')->Redirect( + OP => $Param{RequestedURL}, + Login => 1, + ); + return 1; + } + + # logout + elsif ( $Param{Action} eq 'Logout' ) { + + # check session id + if ( !$SessionObject->CheckSessionID( SessionID => $Param{SessionID} ) ) { + + # new layout object + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + + # redirect to alternate login + if ( $ConfigObject->Get('CustomerPanelLoginURL') ) { + $Param{RequestedURL} = $LayoutObject->LinkEncode( $Param{RequestedURL} ); + print $LayoutObject->Redirect( + ExtURL => $ConfigObject->Get('CustomerPanelLoginURL') + . "?Reason=InvalidSessionID;RequestedURL=$Param{RequestedURL}", + ); + } + + # show login screen + print $LayoutObject->CustomerLogin( + Title => 'Logout', + Message => Translatable('Session invalid. Please log in again.'), + %Param, + ); + return; + } + + # get session data + my %UserData = $SessionObject->GetSessionIDData( + SessionID => $Param{SessionID}, + ); + + # create new LayoutObject with new '%Param' and '%UserData' + $Kernel::OM->ObjectParamAdd( + 'Kernel::Output::HTML::Layout' => { + SetCookies => { + SessionIDCookie => $ParamObject->SetCookie( + Key => $Param{SessionName}, + Value => '', + Expires => '-1y', + Path => $ConfigObject->Get('ScriptAlias'), + Secure => scalar $CookieSecureAttribute, + HTTPOnly => 1, + ), + }, + %Param, + %UserData, + }, + ); + + $Kernel::OM->ObjectsDiscard( Objects => ['Kernel::Output::HTML::Layout'] ); + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + + # remove session id + if ( !$SessionObject->RemoveSessionID( SessionID => $Param{SessionID} ) ) { + $LayoutObject->CustomerFatalError( + Comment => Translatable('Please contact the administrator.') + ); + return; + } + + # redirect to alternate login + if ( $ConfigObject->Get('CustomerPanelLogoutURL') ) { + print $LayoutObject->Redirect( + ExtURL => $ConfigObject->Get('CustomerPanelLogoutURL') + . "?Reason=Logout", + ); + } + + # show logout screen + my $LogoutMessage = $LayoutObject->{LanguageObject}->Translate('Logout successful.'); + + $LayoutObject->Print( + Output => \$LayoutObject->CustomerLogin( + Title => 'Logout', + Message => $LogoutMessage, + MessageType => 'Success', + %Param, + ), + ); + return 1; + } + + # CustomerLostPassword + elsif ( $Param{Action} eq 'CustomerLostPassword' ) { + + # new layout object + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + + # check feature + if ( !$ConfigObject->Get('CustomerPanelLostPassword') ) { + + # show normal login + $LayoutObject->Print( + Output => \$LayoutObject->CustomerLogin( + Title => 'Login', + Message => Translatable('Feature not active!'), + ), + ); + return; + } + + # get params + my $User = $ParamObject->GetParam( Param => 'User' ) || ''; + my $Token = $ParamObject->GetParam( Param => 'Token' ) || ''; + + # get user login by token + if ( !$User && $Token ) { + my %UserList = $UserObject->SearchPreferences( + Key => 'UserToken', + Value => $Token, + ); + USER_ID: + for my $UserID ( sort keys %UserList ) { + my %UserData = $UserObject->CustomerUserDataGet( + User => $UserID, + Valid => 1, + ); + if (%UserData) { + $User = $UserData{UserLogin}; + last USER_ID; + } + } + } + + # get user data + my %UserData = $UserObject->CustomerUserDataGet( User => $User ); + + # verify customer user is valid when requesting password reset + my @ValidIDs = $Kernel::OM->Get('Kernel::System::Valid')->ValidIDsGet(); + my $UserIsValid = grep { $UserData{ValidID} && $UserData{ValidID} == $_ } @ValidIDs; + if ( !$UserData{UserID} || !$UserIsValid ) { + + # Security: pretend that password reset instructions were actually sent to + # make sure that users cannot find out valid usernames by + # just trying and checking the result message. + $LayoutObject->Print( + Output => \$LayoutObject->CustomerLogin( + Title => 'Login', + Message => Translatable('Sent password reset instructions. Please check your email.'), + MessageType => 'Success', + ), + ); + return; + } + + # create email object + my $EmailObject = Kernel::System::Email->new( %{$Self} ); + + # send password reset token + if ( !$Token ) { + + # generate token + $UserData{Token} = $UserObject->TokenGenerate( + UserID => $UserData{UserID}, + ); + + # send token notify email with link + my $Body = $ConfigObject->Get('CustomerPanelBodyLostPasswordToken') + || 'ERROR: CustomerPanelBodyLostPasswordToken is missing!'; + my $Subject = $ConfigObject->Get('CustomerPanelSubjectLostPasswordToken') + || 'ERROR: CustomerPanelSubjectLostPasswordToken is missing!'; + for ( sort keys %UserData ) { + $Body =~ s//$UserData{$_}/gi; + } + my $Sent = $EmailObject->Send( + To => $UserData{UserEmail}, + Subject => $Subject, + Charset => $LayoutObject->{UserCharset}, + MimeType => 'text/plain', + Body => $Body + ); + if ( !$Sent->{Success} ) { + $LayoutObject->FatalError( + Comment => Translatable('Please contact the administrator.'), + ); + return; + } + $LayoutObject->Print( + Output => \$LayoutObject->CustomerLogin( + Title => 'Login', + Message => Translatable('Sent password reset instructions. Please check your email.'), + %Param, + MessageType => 'Success', + ), + ); + return 1; + + } + + # reset password + # check if token is valid + my $TokenValid = $UserObject->TokenCheck( + Token => $Token, + UserID => $UserData{UserID}, + ); + if ( !$TokenValid ) { + $LayoutObject->Print( + Output => \$LayoutObject->CustomerLogin( + Title => 'Login', + Message => Translatable('Invalid Token!'), + %Param, + ), + ); + return; + } + + # get new password + $UserData{NewPW} = $UserObject->GenerateRandomPassword(); + + # update new password + my $Success = $UserObject->SetPassword( + UserLogin => $User, + PW => $UserData{NewPW} + ); + + if ( !$Success ) { + $LayoutObject->Print( + Output => \$LayoutObject->CustomerLogin( + Title => 'Login', + Message => Translatable('Reset password unsuccessful. Please contact the administrator.'), + User => $User, + ), + ); + return; + } + + # send notify email + my $Body = $ConfigObject->Get('CustomerPanelBodyLostPassword') + || 'New Password is: '; + my $Subject = $ConfigObject->Get('CustomerPanelSubjectLostPassword') + || 'New Password!'; + for ( sort keys %UserData ) { + $Body =~ s//$UserData{$_}/gi; + } + my $Sent = $EmailObject->Send( + To => $UserData{UserEmail}, + Subject => $Subject, + Charset => $LayoutObject->{UserCharset}, + MimeType => 'text/plain', + Body => $Body + ); + if ( !$Sent->{Success} ) { + $LayoutObject->CustomerFatalError( + Comment => Translatable('Please contact the administrator.') + ); + return; + } + my $Message = $LayoutObject->{LanguageObject}->Translate( + 'Sent new password to %s. Please check your email.', + $UserData{UserEmail}, + ); + $LayoutObject->Print( + Output => \$LayoutObject->CustomerLogin( + Title => 'Login', + Message => $Message, + User => $User, + MessageType => 'Success', + ), + ); + return 1; + } + + # create new customer account + elsif ( $Param{Action} eq 'CustomerCreateAccount' ) { + + # new layout object + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + + # check feature + if ( !$ConfigObject->Get('CustomerPanelCreateAccount') ) { + + # show normal login + $LayoutObject->Print( + Output => \$LayoutObject->CustomerLogin( + Title => 'Login', + Message => Translatable('Feature not active!'), + ), + ); + return; + } + + # get params + my %GetParams; + for my $Entry ( @{ $ConfigObject->Get('CustomerUser')->{Map} } ) { + $GetParams{ $Entry->[0] } = $ParamObject->GetParam( Param => $Entry->[1] ) + || ''; + } + $GetParams{ValidID} = 1; + + # check needed params + if ( !$GetParams{UserCustomerID} ) { + $GetParams{UserCustomerID} = $GetParams{UserEmail}; + } + if ( !$GetParams{UserLogin} ) { + $GetParams{UserLogin} = $GetParams{UserEmail}; + } + + # get new password + $GetParams{UserPassword} = $UserObject->GenerateRandomPassword(); + + # get user data + my %UserData = $UserObject->CustomerUserDataGet( User => $GetParams{UserLogin} ); + if ( $UserData{UserID} || !$GetParams{UserLogin} ) { + + # send data to JS + $LayoutObject->AddJSData( + Key => 'SignupError', + Value => 1, + ); + + $LayoutObject->Print( + Output => \$LayoutObject->CustomerLogin( + Title => 'Login', + Message => + Translatable('This e-mail address already exists. Please log in or reset your password.'), + UserTitle => $GetParams{UserTitle}, + UserFirstname => $GetParams{UserFirstname}, + UserLastname => $GetParams{UserLastname}, + UserEmail => $GetParams{UserEmail}, + ), + ); + return; + } + + # check for mail address restrictions + my @Whitelist = @{ + $ConfigObject->Get('CustomerPanelCreateAccount::MailRestrictions::Whitelist') // [] + }; + my @Blacklist = @{ + $ConfigObject->Get('CustomerPanelCreateAccount::MailRestrictions::Blacklist') // [] + }; + + my $WhitelistMatched; + for my $WhitelistEntry (@Whitelist) { + my $Regex = eval {qr/$WhitelistEntry/i}; + if ($@) { + $Kernel::OM->Get('Kernel::System::Log')->Log( + Priority => 'error', + Message => + $LayoutObject->{LanguageObject}->Translate( + 'The customer panel mail address whitelist contains the invalid regular expression $WhitelistEntry, please check and correct it.' + ), + ); + } + elsif ( $GetParams{UserEmail} =~ $Regex ) { + $WhitelistMatched++; + } + } + my $BlacklistMatched; + for my $BlacklistEntry (@Blacklist) { + my $Regex = eval {qr/$BlacklistEntry/i}; + if ($@) { + $Kernel::OM->Get('Kernel::System::Log')->Log( + Priority => 'error', + Message => + $LayoutObject->{LanguageObject}->Translate( + 'The customer panel mail address blacklist contains the invalid regular expression $BlacklistEntry, please check and correct it.' + ), + ); + } + elsif ( $GetParams{UserEmail} =~ $Regex ) { + $BlacklistMatched++; + } + } + + if ( ( @Whitelist && !$WhitelistMatched ) || ( @Blacklist && $BlacklistMatched ) ) { + + # send data to JS + $LayoutObject->AddJSData( + Key => 'SignupError', + Value => 1, + ); + + $LayoutObject->Print( + Output => \$LayoutObject->CustomerLogin( + Title => 'Login', + Message => + Translatable('This email address is not allowed to register. Please contact support staff.'), + UserTitle => $GetParams{UserTitle}, + UserFirstname => $GetParams{UserFirstname}, + UserLastname => $GetParams{UserLastname}, + UserEmail => $GetParams{UserEmail}, + ), + ); + + return; + } + + # create account + my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime'); + + my $Now = $DateTimeObject->ToString(); + + my $Add = $UserObject->CustomerUserAdd( + %GetParams, + Comment => $LayoutObject->{LanguageObject}->Translate( 'Added via Customer Panel (%s)', $Now ), + ValidID => 1, + UserID => $ConfigObject->Get('CustomerPanelUserID'), + ); + if ( !$Add ) { + + # send data to JS + $LayoutObject->AddJSData( + Key => 'SignupError', + Value => 1, + ); + + $LayoutObject->Print( + Output => \$LayoutObject->CustomerLogin( + Title => 'Login', + Message => Translatable('Customer user can\'t be added!'), + UserTitle => $GetParams{UserTitle}, + UserFirstname => $GetParams{UserFirstname}, + UserLastname => $GetParams{UserLastname}, + UserEmail => $GetParams{UserEmail}, + ), + ); + return; + } + + # send notify email + my $EmailObject = Kernel::System::Email->new( %{$Self} ); + my $Body = $ConfigObject->Get('CustomerPanelBodyNewAccount') + || 'No Config Option found!'; + my $Subject = $ConfigObject->Get('CustomerPanelSubjectNewAccount') + || 'New OTRS Account!'; + for ( sort keys %GetParams ) { + $Body =~ s//$GetParams{$_}/gi; + } + + # send account info + my $Sent = $EmailObject->Send( + To => $GetParams{UserEmail}, + Subject => $Subject, + Charset => $LayoutObject->{UserCharset}, + MimeType => 'text/plain', + Body => $Body + ); + if ( !$Sent->{Success} ) { + my $Output = $LayoutObject->CustomerHeader( + Area => 'Core', + Title => 'Error' + ); + $Output .= $LayoutObject->CustomerWarning( + Comment => Translatable('Can\'t send account info!') + ); + $Output .= $LayoutObject->CustomerFooter(); + $LayoutObject->Print( Output => \$Output ); + return; + } + + # show sent account info + if ( $ConfigObject->Get('CustomerPanelLoginURL') ) { + + # redirect to alternate login + $Param{RequestedURL} = $LayoutObject->LinkEncode( $Param{RequestedURL} ); + print $LayoutObject->Redirect( + ExtURL => $ConfigObject->Get('CustomerPanelLoginURL') + . "?RequestedURL=$Param{RequestedURL};User=$GetParams{UserLogin};" + . "Email=$GetParams{UserEmail};Reason=NewAccountCreated", + ); + return 1; + } + + my $AccountCreatedMessage = $LayoutObject->{LanguageObject}->Translate( + 'New account created. Sent login information to %s. Please check your email.', + $GetParams{UserEmail}, + ); + + # login screen + $LayoutObject->Print( + Output => \$LayoutObject->CustomerLogin( + Title => 'Login', + Message => $AccountCreatedMessage, + User => $GetParams{UserLogin}, + MessageType => 'Success', + ), + ); + return 1; + } + + # show login site + elsif ( !$Param{SessionID} ) { + + # new layout object + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + + # create AuthObject + my $AuthObject = $Kernel::OM->Get('Kernel::System::CustomerAuth'); + if ( $AuthObject->GetOption( What => 'PreAuth' ) ) { + + # automatic login + $Param{RequestedURL} = $LayoutObject->LinkEncode( $Param{RequestedURL} ); + print $LayoutObject->Redirect( + OP => "Action=PreLogin;RequestedURL=$Param{RequestedURL}", + ); + return; + } + elsif ( $ConfigObject->Get('CustomerPanelLoginURL') ) { + + # redirect to alternate login + $Param{RequestedURL} = $LayoutObject->LinkEncode( $Param{RequestedURL} ); + print $LayoutObject->Redirect( + ExtURL => $ConfigObject->Get('CustomerPanelLoginURL') + . "?RequestedURL=$Param{RequestedURL}", + ); + return; + } + + # login screen + $LayoutObject->Print( + Output => \$LayoutObject->CustomerLogin( + Title => 'Login', + %Param, + ), + ); + return 1; + } + + # run modules if a version value exists + elsif ( $Kernel::OM->Get('Kernel::System::Main')->Require("Kernel::Modules::$Param{Action}") ) { + + # check session id + if ( !$SessionObject->CheckSessionID( SessionID => $Param{SessionID} ) ) { + + # create new LayoutObject with new '%Param' + $Kernel::OM->ObjectParamAdd( + 'Kernel::Output::HTML::Layout' => { + SetCookies => { + SessionIDCookie => $ParamObject->SetCookie( + Key => $Param{SessionName}, + Value => '', + Expires => '-1y', + Path => $ConfigObject->Get('ScriptAlias'), + Secure => scalar $CookieSecureAttribute, + HTTPOnly => 1, + ), + }, + %Param, + } + ); + + $Kernel::OM->ObjectsDiscard( Objects => ['Kernel::Output::HTML::Layout'] ); + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + + # create AuthObject + my $AuthObject = $Kernel::OM->Get('Kernel::System::CustomerAuth'); + if ( $AuthObject->GetOption( What => 'PreAuth' ) ) { + + # automatic re-login + $Param{RequestedURL} = $LayoutObject->LinkEncode( $Param{RequestedURL} ); + print $LayoutObject->Redirect( + OP => "?Action=PreLogin&RequestedURL=$Param{RequestedURL}", + ); + return; + } + + # redirect to alternate login + elsif ( $ConfigObject->Get('CustomerPanelLoginURL') ) { + + # redirect to alternate login + $Param{RequestedURL} = $LayoutObject->LinkEncode( $Param{RequestedURL} ); + print $LayoutObject->Redirect( + ExtURL => $ConfigObject->Get('CustomerPanelLoginURL') + . "?Reason=InvalidSessionID;RequestedURL=$Param{RequestedURL}", + ); + return; + } + + # show login + $LayoutObject->Print( + Output => \$LayoutObject->CustomerLogin( + Title => 'Login', + Message => + $LayoutObject->{LanguageObject}->Translate( $SessionObject->SessionIDErrorMessage() ), + %Param, + ), + ); + return; + } + + # get session data + my %UserData = $SessionObject->GetSessionIDData( + SessionID => $Param{SessionID}, + ); + + # check needed data + if ( !$UserData{UserID} || !$UserData{UserLogin} || $UserData{UserType} ne 'Customer' ) { + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + + # redirect to alternate login + if ( $ConfigObject->Get('CustomerPanelLoginURL') ) { + print $LayoutObject->Redirect( + ExtURL => $ConfigObject->Get('CustomerPanelLoginURL') + . "?Reason=SystemError", + ); + return; + } + + # show login screen + $LayoutObject->Print( + Output => \$LayoutObject->CustomerLogin( + Title => 'Error', + Message => Translatable('Error: invalid session.'), + %Param, + ), + ); + return; + } + + # module registry + my $ModuleReg = $ConfigObject->Get('CustomerFrontend::Module')->{ $Param{Action} }; + if ( !$ModuleReg ) { + + # new layout object + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + $Kernel::OM->Get('Kernel::System::Log')->Log( + Priority => 'error', + Message => + "Module Kernel::Modules::$Param{Action} not registered in Kernel/Config.pm!", + ); + $LayoutObject->CustomerFatalError( + Comment => Translatable('Please contact the administrator.'), + ); + return; + } + + # module permission check for action + if ( + ref $ModuleReg->{GroupRo} eq 'ARRAY' + && !scalar @{ $ModuleReg->{GroupRo} } + && ref $ModuleReg->{Group} eq 'ARRAY' + && !scalar @{ $ModuleReg->{Group} } + ) + { + $Param{AccessRo} = 1; + $Param{AccessRw} = 1; + } + else { + + ( $Param{AccessRo}, $Param{AccessRw} ) = $Self->_CheckModulePermission( + ModuleReg => $ModuleReg, + %UserData, + ); + + if ( !$Param{AccessRo} ) { + + # new layout object + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + $Kernel::OM->Get('Kernel::System::Log')->Log( + Priority => 'error', + Message => 'No Permission to use this frontend action module!' + ); + $LayoutObject->CustomerFatalError( + Comment => Translatable('Please contact the administrator.'), + ); + return; + } + + } + + my $NavigationConfig = $ConfigObject->Get('CustomerFrontend::Navigation')->{ $Param{Action} }; + + # module permission check for submenu item + if ( IsHashRefWithData($NavigationConfig) ) { + + KEY: + for my $Key ( sort keys %{$NavigationConfig} ) { + next KEY if $Key !~ m/^\d+/i; + next KEY if $Param{RequestedURL} !~ m/Subaction/i; + + my @ModuleNavigationConfigs; + + # FIXME: Support both old (HASH) and new (ARRAY of HASH) navigation configurations, for reasons of + # backwards compatibility. Once we are sure everything has been migrated correctly, support for + # HASH-only configuration can be dropped in future major release. + if ( IsHashRefWithData( $NavigationConfig->{$Key} ) ) { + push @ModuleNavigationConfigs, $NavigationConfig->{$Key}; + } + elsif ( IsArrayRefWithData( $NavigationConfig->{$Key} ) ) { + push @ModuleNavigationConfigs, @{ $NavigationConfig->{$Key} }; + } + + # Skip incompatible configuration. + else { + next KEY; + } + + ITEM: + for my $Item (@ModuleNavigationConfigs) { + if ( + $Item->{Link} =~ m/Subaction=/i + && $Item->{Link} !~ m/$Param{Subaction}/i + ) + { + next ITEM; + } + $Param{AccessRo} = 0; + $Param{AccessRw} = 0; + + # module permission check for submenu item + if ( + ref $Item->{GroupRo} eq 'ARRAY' + && !scalar @{ $Item->{GroupRo} } + && ref $Item->{Group} eq 'ARRAY' + && !scalar @{ $Item->{Group} } + ) + { + $Param{AccessRo} = 1; + $Param{AccessRw} = 1; + } + else { + + ( $Param{AccessRo}, $Param{AccessRw} ) = $Self->_CheckModulePermission( + ModuleReg => $Item, + %UserData, + ); + + if ( !$Param{AccessRo} ) { + + # new layout object + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + $Kernel::OM->Get('Kernel::System::Log')->Log( + Priority => 'error', + Message => 'No Permission to use this frontend subaction module!' + ); + $LayoutObject->CustomerFatalError( + Comment => Translatable('Please contact the administrator.') + ); + return; + } + } + } + } + } + + # create new LayoutObject with new '%Param' and '%UserData' + $Kernel::OM->ObjectParamAdd( + 'Kernel::Output::HTML::Layout' => { + %Param, + %UserData, + ModuleReg => $ModuleReg, + }, + ); + + $Kernel::OM->ObjectsDiscard( Objects => ['Kernel::Output::HTML::Layout'] ); + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + + # update last request time + if ( + !$ParamObject->IsAJAXRequest() + || $Param{Action} eq 'CustomerVideoChat' + ) + { + my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime'); + + $SessionObject->UpdateSessionID( + SessionID => $Param{SessionID}, + Key => 'UserLastRequest', + Value => $DateTimeObject->ToEpoch(), + ); + } + + # pre application module + my $PreModule = $ConfigObject->Get('CustomerPanelPreApplicationModule'); + if ($PreModule) { + my %PreModuleList; + if ( ref $PreModule eq 'HASH' ) { + %PreModuleList = %{$PreModule}; + } + else { + $PreModuleList{Init} = $PreModule; + } + + MODULE: + for my $PreModuleKey ( sort keys %PreModuleList ) { + my $PreModule = $PreModuleList{$PreModuleKey}; + next MODULE if !$PreModule; + next MODULE if !$Kernel::OM->Get('Kernel::System::Main')->Require($PreModule); + + # debug info + if ( $Self->{Debug} ) { + $Kernel::OM->Get('Kernel::System::Log')->Log( + Priority => 'debug', + Message => "CustomerPanelPreApplication module $PreModule is used.", + ); + } + + # use module + my $PreModuleObject = $PreModule->new( + %Param, + %UserData, + + ); + my $Output = $PreModuleObject->PreRun(); + if ($Output) { + $LayoutObject->Print( Output => \$Output ); + return 1; + } + } + } + + # debug info + if ( $Self->{Debug} ) { + $Kernel::OM->Get('Kernel::System::Log')->Log( + Priority => 'debug', + Message => 'Kernel::Modules::' . $Param{Action} . '->new', + ); + } + + my $FrontendObject = ( 'Kernel::Modules::' . $Param{Action} )->new( + %Param, + %UserData, + ModuleReg => $ModuleReg, + Debug => $Self->{Debug}, + ); + + # debug info + if ( $Self->{Debug} ) { + $Kernel::OM->Get('Kernel::System::Log')->Log( + Priority => 'debug', + Message => 'Kernel::Modules::' . $Param{Action} . '->run', + ); + } + + # ->Run $Action with $FrontendObject + $LayoutObject->Print( Output => \$FrontendObject->Run() ); + + # log request time + if ( $ConfigObject->Get('PerformanceLog') ) { + if ( ( !$QueryString && $Param{Action} ) || $QueryString !~ /Action=/ ) { + $QueryString = 'Action=' . $Param{Action} . ';Subaction=' . $Param{Subaction}; + } + my $File = $ConfigObject->Get('PerformanceLog::File'); + ## no critic + if ( open my $Out, '>>', $File ) { + ## use critic + print $Out time() + . '::Customer::' + . ( time() - $Self->{PerformanceLogStart} ) + . "::$UserData{UserLogin}::$QueryString\n"; + close $Out; + $Kernel::OM->Get('Kernel::System::Log')->Log( + Priority => 'debug', + Message => 'Response::Customer: ' + . ( time() - $Self->{PerformanceLogStart} ) + . "s taken (URL:$QueryString:$UserData{UserLogin})", + ); + } + else { + $Kernel::OM->Get('Kernel::System::Log')->Log( + Priority => 'error', + Message => "Can't write $File: $!", + ); + } + } + return 1; + } + + # print an error screen + my %Data = $SessionObject->GetSessionIDData( + SessionID => $Param{SessionID}, + ); + $Kernel::OM->ObjectParamAdd( + 'Kernel::Output::HTML::Layout' => { + %Param, + %Data, + }, + ); + my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); + $LayoutObject->CustomerFatalError( + Comment => Translatable('Please contact the administrator.'), + ); + return; +} + +=begin Internal: + +=head2 _CheckModulePermission() + +module permission check + + ($AccessRo, $AccessRw = $AutoResponseObject->_CheckModulePermission( + ModuleReg => $ModuleReg, + %UserData, + ); + +=cut + +sub _CheckModulePermission { + my ( $Self, %Param ) = @_; + + my $AccessRo = 0; + my $AccessRw = 0; + + PERMISSION: + for my $Permission (qw(GroupRo Group)) { + my $AccessOk = 0; + my $Group = $Param{ModuleReg}->{$Permission}; + + next PERMISSION if !$Group; + + my $GroupObject = $Kernel::OM->Get('Kernel::System::CustomerGroup'); + + if ( IsArrayRefWithData($Group) ) { + GROUP: + for my $Item ( @{$Group} ) { + next GROUP if !$Item; + next GROUP if !$GroupObject->PermissionCheck( + UserID => $Param{UserID}, + GroupName => $Item, + Type => $Permission eq 'GroupRo' ? 'ro' : 'rw', + ); + + $AccessOk = 1; + last GROUP; + } + } + else { + my $HasPermission = $GroupObject->PermissionCheck( + UserID => $Param{UserID}, + GroupName => $Group, + Type => $Permission eq 'GroupRo' ? 'ro' : 'rw', + ); + if ($HasPermission) { + $AccessOk = 1; + } + } + if ( $Permission eq 'Group' && $AccessOk ) { + $AccessRo = 1; + $AccessRw = 1; + } + elsif ( $Permission eq 'GroupRo' && $AccessOk ) { + $AccessRo = 1; + } + } + + return ( $AccessRo, $AccessRw ); +} + +=end Internal: + +=cut + +sub DESTROY { + my $Self = shift; + + # debug info + if ( $Self->{Debug} ) { + $Kernel::OM->Get('Kernel::System::Log')->Log( + Priority => 'debug', + Message => 'Global handle stopped.', + ); + } + + return 1; +} + +# CAS Custom +=item + Check if it is a CAS logout - if true, logs out user for the session ticket +=cut +sub CASLogout { + my $Self = shift; + + # get post data + my $cgi = new CGI; + my $request = uri_unescape($cgi->query_string()); + + # check if it is CAS logout + if ($request =~ /(logoutRequest=([^<]+)/); + if (! $ticket) { + return 1; + } else { + } + + my $DBObject = $Kernel::OM->Get("Kernel::System::DB"); + + # get login for session + my $sqlLogin = 'SELECT UserLogin from cas_session where Ticket=?'; + $DBObject->Prepare(SQL => $sqlLogin, Bind => [\$ticket]); + my $login; + while (my @Row = $DBObject->FetchrowArray()) { + $login = $Row[0]; + } + + # if a login was found, kill the user session + if ($login) { + $DBObject->Do(SQL => "DELETE FROM sessions WHERE session_id IN (select session_id from (select session_id FROM sessions WHERE data_key='UserLogin' AND data_value=?) as x)", + Bind => [\$login]); + } else { + } + return 1; + } else { + return 0; + } +} +# CAS Custom + +1; + +=head1 TERMS AND CONDITIONS + +This software is part of the OTRS project (L). + +This software comes with ABSOLUTELY NO WARRANTY. For details, see +the enclosed file COPYING for license information (AGPL). If you +did not receive this file, see L. + +=cut diff --git a/Custom/Kernel/System/Web/Request.pm b/Custom/Kernel/System/Web/Request.pm new file mode 100644 index 0000000..dc0cdc8 --- /dev/null +++ b/Custom/Kernel/System/Web/Request.pm @@ -0,0 +1,582 @@ +# -- +# Copyright (C) 2001-2017 OTRS AG, http://otrs.com/ +# -- +# This software comes with ABSOLUTELY NO WARRANTY. For details, see +# the enclosed file COPYING for license information (AGPL). If you +# did not receive this file, see http://www.gnu.org/licenses/agpl.txt. +# +# Custom version for CAS authentication - rodrigo@goncalves.pro.br +# +# Version 2016-01-18 - RG - Version for OTRS 5.0.6 +# Version 2017-12-07 - RG - Version for OTRS 6.0.1 +# +# -- + +package Kernel::System::Web::Request; + +use strict; +use warnings; + +use CGI (); +use CGI::Carp; +use File::Path qw(); + +use Kernel::System::VariableCheck qw(:all); + +our @ObjectDependencies = ( + 'Kernel::Config', + 'Kernel::System::CheckItem', + 'Kernel::System::Encode', + 'Kernel::System::Web::UploadCache', + 'Kernel::System::FormDraft', +); + +=head1 NAME + +Kernel::System::Web::Request - global CGI interface + +=head1 DESCRIPTION + +All cgi param functions. + +=head1 PUBLIC INTERFACE + +=head2 new() + +create param object. Do not use it directly, instead use: + + use Kernel::System::ObjectManager; + local $Kernel::OM = Kernel::System::ObjectManager->new( + 'Kernel::System::Web::Request' => { + WebRequest => CGI::Fast->new(), # optional, e. g. if fast cgi is used + } + ); + my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request'); + +If Kernel::System::Web::Request is instantiated several times, they will share the +same CGI data (this can be helpful in filters which do not have access to the +ParamObject, for example. + +If you need to reset the CGI data before creating a new instance, use + + CGI::initialize_globals(); + +before calling Kernel::System::Web::Request->new(); + +=cut + +sub new { + my ( $Type, %Param ) = @_; + + # allocate new hash for object + my $Self = {}; + bless( $Self, $Type ); + + # get config object + my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); + + # max 5 MB posts + $CGI::POST_MAX = $ConfigObject->Get('WebMaxFileUpload') || 1024 * 1024 * 5; ## no critic + + # query object (in case use already existing WebRequest, e. g. fast cgi) + $Self->{Query} = $Param{WebRequest} || CGI->new(); + + return $Self; +} + +=head2 Error() + +to get the error back + + if ( $ParamObject->Error() ) { + print STDERR $ParamObject->Error() . "\n"; + } + +=cut + +sub Error { + my ( $Self, %Param ) = @_; + + # Workaround, do not check cgi_error() with perlex, CGI module is not + # working with perlex. + if ( $ENV{'GATEWAY_INTERFACE'} && $ENV{'GATEWAY_INTERFACE'} =~ /^CGI-PerlEx/ ) { + return; + } + + return if !$Self->{Query}->cgi_error(); + ## no critic + return $Self->{Query}->cgi_error() . ' - POST_MAX=' . ( $CGI::POST_MAX / 1024 ) . 'KB'; + ## use critic +} + +=head2 GetParam() + +to get single request parameters. By default, trimming is performed on the data. + + my $Param = $ParamObject->GetParam( + Param => 'ID', + Raw => 1, # optional, input data is not changed + ); + +=cut + +sub GetParam { + my ( $Self, %Param ) = @_; + + my $Value = $Self->{Query}->param( $Param{Param} ); + + # Fallback to query string for mixed requests. + my $RequestMethod = $Self->{Query}->request_method() // ''; + if ( $RequestMethod eq 'POST' && !defined $Value ) { + $Value = $Self->{Query}->url_param( $Param{Param} ); + } + + $Kernel::OM->Get('Kernel::System::Encode')->EncodeInput( \$Value ); + + my $Raw = defined $Param{Raw} ? $Param{Raw} : 0; + + if ( !$Raw ) { + + # If it is a plain string, perform trimming + if ( ref \$Value eq 'SCALAR' ) { + $Kernel::OM->Get('Kernel::System::CheckItem')->StringClean( + StringRef => \$Value, + TrimLeft => 1, + TrimRight => 1, + ); + } + } + + return $Value; +} + +=head2 GetParamNames() + +to get names of all parameters passed to the script. + + my @ParamNames = $ParamObject->GetParamNames(); + +Example: + +Called URL: index.pl?Action=AdminSystemConfiguration;Subaction=Save;Name=Config::Option::Valid + + my @ParamNames = $ParamObject->GetParamNames(); + print join " :: ", @ParamNames; + #prints Action :: Subaction :: Name + +=cut + +sub GetParamNames { + my $Self = shift; + + # fetch all names + my @ParamNames = $Self->{Query}->param(); + + # Fallback to query string for mixed requests. + my $RequestMethod = $Self->{Query}->request_method() // ''; + if ( $RequestMethod eq 'POST' ) { + my %POSTNames; + @POSTNames{@ParamNames} = @ParamNames; + my @GetNames = $Self->{Query}->url_param(); + GETNAME: + for my $GetName (@GetNames) { + next GETNAME if !defined $GetName; + push @ParamNames, $GetName if !exists $POSTNames{$GetName}; + } + } + + for my $Name (@ParamNames) { + $Kernel::OM->Get('Kernel::System::Encode')->EncodeInput( \$Name ); + } + + return @ParamNames; +} + +=head2 GetArray() + +to get array request parameters. +By default, trimming is performed on the data. + + my @Param = $ParamObject->GetArray( + Param => 'ID', + Raw => 1, # optional, input data is not changed + ); + +=cut + +sub GetArray { + my ( $Self, %Param ) = @_; + + my @Values = $Self->{Query}->multi_param( $Param{Param} ); + + # Fallback to query string for mixed requests. + my $RequestMethod = $Self->{Query}->request_method() // ''; + if ( $RequestMethod eq 'POST' && !@Values ) { + @Values = $Self->{Query}->url_param( $Param{Param} ); + } + + $Kernel::OM->Get('Kernel::System::Encode')->EncodeInput( \@Values ); + + my $Raw = defined $Param{Raw} ? $Param{Raw} : 0; + + if ( !$Raw ) { + + # get check item object + my $CheckItemObject = $Kernel::OM->Get('Kernel::System::CheckItem'); + + VALUE: + for my $Value (@Values) { + + # don't validate CGI::File::Temp objects from file uploads + next VALUE if !$Value || ref \$Value ne 'SCALAR'; + + $CheckItemObject->StringClean( + StringRef => \$Value, + TrimLeft => 1, + TrimRight => 1, + ); + } + } + + return @Values; +} + +=head2 GetUploadAll() + +gets file upload data. + + my %File = $ParamObject->GetUploadAll( + Param => 'FileParam', # the name of the request parameter containing the file data + ); + + returns ( + Filename => 'abc.txt', + ContentType => 'text/plain', + Content => 'Some text', + ); + +=cut + +sub GetUploadAll { + my ( $Self, %Param ) = @_; + + # get upload + my $Upload = $Self->{Query}->upload( $Param{Param} ); + return if !$Upload; + + # get real file name + my $UploadFilenameOrig = $Self->GetParam( Param => $Param{Param} ) || 'unknown'; + + my $NewFileName = "$UploadFilenameOrig"; # use "" to get filename of anony. object + $Kernel::OM->Get('Kernel::System::Encode')->EncodeInput( \$NewFileName ); + + # replace all devices like c: or d: and dirs for IE! + $NewFileName =~ s/.:\\(.*)/$1/g; + $NewFileName =~ s/.*\\(.+?)/$1/g; + + # return a string + my $Content = ''; + while (<$Upload>) { + $Content .= $_; + } + close $Upload; + + my $ContentType = $Self->_GetUploadInfo( + Filename => $UploadFilenameOrig, + Header => 'Content-Type', + ); + + return ( + Filename => $NewFileName, + Content => $Content, + ContentType => $ContentType, + ); +} + +sub _GetUploadInfo { + my ( $Self, %Param ) = @_; + + # get file upload info + my $FileInfo = $Self->{Query}->uploadInfo( $Param{Filename} ); + + # return if no upload info exists + return 'application/octet-stream' if !$FileInfo; + + # return if no content type of upload info exists + return 'application/octet-stream' if !$FileInfo->{ $Param{Header} }; + + # return content type of upload info + return $FileInfo->{ $Param{Header} }; +} + +=head2 SetCookie() + +set a cookie + + $ParamObject->SetCookie( + Key => ID, + Value => 123456, + Expires => '+3660s', + Path => 'otrs/', # optional, only allow cookie for given path + Secure => 1, # optional, set secure attribute to disable cookie on HTTP (HTTPS only) + HTTPOnly => 1, # optional, sets HttpOnly attribute of cookie to prevent access via JavaScript + ); + +=cut + +sub SetCookie { + my ( $Self, %Param ) = @_; + + $Param{Path} ||= ''; + + return $Self->{Query}->cookie( + -name => $Param{Key}, + -value => $Param{Value}, + -expires => $Param{Expires}, + -secure => $Param{Secure} || '', + -httponly => $Param{HTTPOnly} || '', + -path => '/' . $Param{Path}, + ); +} + +=head2 GetCookie() + +get a cookie + + my $String = $ParamObject->GetCookie( + Key => ID, + ); + +=cut + +sub GetCookie { + my ( $Self, %Param ) = @_; + + return $Self->{Query}->cookie( $Param{Key} ); +} + +=head2 IsAJAXRequest() + +checks if the current request was sent by AJAX + + my $IsAJAXRequest = $ParamObject->IsAJAXRequest(); + +=cut + +sub IsAJAXRequest { + my ( $Self, %Param ) = @_; + + return ( $Self->{Query}->http('X-Requested-With') // '' ) eq 'XMLHttpRequest' ? 1 : 0; +} + +=head2 LoadFormDraft() + +Load specified draft. +This will read stored draft data and inject it into the param object +for transparent use by frontend module. + + my $FormDraftID = $ParamObject->LoadFormDraft( + FormDraftID => 123, + UserID => 1, + ); + +=cut + +sub LoadFormDraft { + my ( $Self, %Param ) = @_; + + return if !$Param{FormDraftID} || !$Param{UserID}; + + # get draft data + my $FormDraft = $Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftGet( + FormDraftID => $Param{FormDraftID}, + UserID => $Param{UserID}, + ); + return if !IsHashRefWithData($FormDraft); + + # Verify action. + my $Action = $Self->GetParam( Param => 'Action' ); + return if $FormDraft->{Action} ne $Action; + + # add draft name to form data + $FormDraft->{FormData}->{FormDraftTitle} = $FormDraft->{Title}; + + # create FormID and add to form data + my $FormID = $Kernel::OM->Get('Kernel::System::Web::UploadCache')->FormIDCreate(); + $FormDraft->{FormData}->{FormID} = $FormID; + + # set form data to param object, depending on type + KEY: + for my $Key ( sort keys %{ $FormDraft->{FormData} } ) { + my $Value = $FormDraft->{FormData}->{$Key} // ''; + + # array value + if ( IsArrayRefWithData($Value) ) { + $Self->{Query}->param( + -name => $Key, + -values => $Value, + ); + next KEY; + } + + # scalar value + $Self->{Query}->param( + -name => $Key, + -value => $Value, + ); + } + + # add UploadCache data + my $UploadCacheObject = $Kernel::OM->Get('Kernel::System::Web::UploadCache'); + for my $File ( @{ $FormDraft->{FileData} } ) { + return if !$UploadCacheObject->FormIDAddFile( + %{$File}, + FormID => $FormID, + ); + } + + return $Param{FormDraftID}; +} + +=head2 SaveFormDraft() + +Create or replace draft using data from param object and upload cache. +Specified params can be overwritten if necessary. + + my $FormDraftID = $ParamObject->SaveFormDraft( + UserID => 1 + ObjectType => 'Ticket', + ObjectID => 123, + OverrideParams => { # optional, can contain strings and array references + Subaction => undef, + UserID => 1, + CustomParam => [ 1, 2, 3, ], + ... + }, + ); + +=cut + +sub SaveFormDraft { + my ( $Self, %Param ) = @_; + + # check params + return if !$Param{UserID} || !$Param{ObjectType} || !IsInteger( $Param{ObjectID} ); + + # gather necessary data for backend + my %MetaParams; + for my $Param (qw(Action FormDraftID FormDraftTitle FormID)) { + $MetaParams{$Param} = $Self->GetParam( + Param => $Param, + ); + } + return if !$MetaParams{Action}; + + # determine session name param (SessionUseCookie = 0) for exclusion + my $SessionName = $Kernel::OM->Get('Kernel::Config')->Get('SessionName') || 'SessionID'; + + # compile override list + my %OverrideParams = IsHashRefWithData( $Param{OverrideParams} ) ? %{ $Param{OverrideParams} } : (); + + # these params must always be excluded for safety, they take precedence + for my $Name ( + qw(Action ChallengeToken FormID FormDraftID FormDraftTitle FormDraftAction LoadFormDraftID), + $SessionName + ) + { + $OverrideParams{$Name} = undef; + } + + # Gather all params. + # Exclude, add or override by using OverrideParams if necessary. + my @ParamNames = $Self->GetParamNames(); + my %ParamSeen; + my %FormData; + PARAM: + for my $Param ( @ParamNames, sort keys %OverrideParams ) { + next PARAM if $ParamSeen{$Param}++; + my $Value; + + # check for overrides first + if ( exists $OverrideParams{$Param} ) { + + # allow only strings and array references as value + if ( + IsStringWithData( $OverrideParams{$Param} ) + || IsArrayRefWithData( $OverrideParams{$Param} ) + ) + { + $Value = $OverrideParams{$Param}; + } + + # skip all other parameters (including those specified to be excluded by using 'undef') + else { + next PARAM; + } + } + + # get other values from param object + if ( !defined $Value ) { + my @Values = $Self->GetArray( Param => $Param ); + next PARAM if !IsArrayRefWithData( \@Values ); + + # store single occurances as string + if ( scalar @Values == 1 ) { + $Value = $Values[0]; + } + + # store multiple occurances as array reference + else { + $Value = \@Values; + } + } + + $FormData{$Param} = $Value; + } + + # get files from upload cache + my @FileData = $Kernel::OM->Get('Kernel::System::Web::UploadCache')->FormIDGetAllFilesData( + FormID => $MetaParams{FormID}, + ); + + # prepare data to add or update draft + my %FormDraft = ( + FormData => \%FormData, + FileData => \@FileData, + FormDraftID => $MetaParams{FormDraftID}, + ObjectType => $Param{ObjectType}, + ObjectID => $Param{ObjectID}, + Action => $MetaParams{Action}, + Title => $MetaParams{FormDraftTitle}, + UserID => $Param{UserID}, + ); + + # update draft + if ( $MetaParams{FormDraftID} ) { + return if !$Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftUpdate(%FormDraft); + return 1; + } + + # create new draft + return if !$Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftAdd(%FormDraft); + return 1; +} + +# CAS Custom +sub GetMethod { + my ( $Self, %Param ) = @_; + return $Self->{Query}->request_method(); +} +# CAS Custom + +1; + +=head1 TERMS AND CONDITIONS + +This software is part of the OTRS project (L). + +This software comes with ABSOLUTELY NO WARRANTY. For details, see +the enclosed file COPYING for license information (AGPL). If you +did not receive this file, see L. + +=cut diff --git a/Kernel/System/Auth/CAS.pm b/Kernel/System/Auth/CAS.pm new file mode 100644 index 0000000..2bfec97 --- /dev/null +++ b/Kernel/System/Auth/CAS.pm @@ -0,0 +1,154 @@ +# -- +# Kernel/System/Auth/CAS.pm - provides the CAS authentication through Jasig +# +# Copyright (C) 2015-2017 - Rodrigo Gonçalves - rodrigo@goncalves.pro.br +# -- +# $Id: CAS.pm,v 2.0 2015/01/05 15:16:05 mb Exp $ +# +# Version 2015/01/15 - RG - Adjusts for OTRS4 +# Version 2016-01-18 - RG - Fixes for OTRS 5.0.6 +# Version 2017-12-07 - RG - Fixes for OTRS 6.0.1 +# +# +# -- +# This software comes with ABSOLUTELY NO WARRANTY. For details, see +# the enclosed file COPYING for license information (AGPL). If you +# did not receive this file, see http://www.gnu.org/licenses/agpl.txt. +# -- +# Note: +# +# If you use this module, you should use as fallback the following config settings: +# +# If use isn't login through apache ($ENV{REMOTE_USER} or $ENV{HTTP_REMOTE_USER}) +# $Self->{CustomerPanelLoginURL} = 'http://host.example.com/not-authorised-for-otrs.html'; +# +# $Self->{CustomerPanelLogoutURL} = 'http://host.example.com/thanks-for-using-otrs.html'; +# +# -- +package Kernel::System::Auth::CAS; + +use strict; +use warnings; +use CGI; +use AuthCAS; +use Data::Dumper; +use CGI::Carp qw( fatalsToBrowser ); +use URI::Escape; + +our @ObjectDependencies = ( "Kernel::Config", "Kernel::System::Log", "Kernel::System::DB" ); + +sub new { + my ( $Type, %Param ) = @_; + + # allocate new hash for object + my $Self = {}; + bless( $Self, $Type ); + + # Debug 0=off 1=on + $Self->{Debug} = 1; + $Self->{Count} = $Param{Count} || ''; + + return $Self; +} + +sub GetOption { + my ( $Self, %Param ) = @_; + + # check needed stuff + if ( !$Param{What} ) { + $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need What!" ); + return; + } + + # module options + my %Option = ( PreAuth => 1, ); + + # return option + return $Option{ $Param{What} }; +} + +sub Auth { + my ( $Self, %Param ) = @_; + + my $QueryString = $ENV{"HTTP_REFERER"} || ''; + + my $ConfigObject = $Kernel::OM->Get("Kernel::Config"); + + my $cas = new AuthCAS( casUrl => $ConfigObject->Get('AuthModule::CAS::CASUrl') ); + my $app_url = $ConfigObject->Get('AuthModule::CAS::ServiceUrl'); + my $Gateway = $ConfigObject->Get('AuthModule::CAS::Gateway'); + my $User = ''; + + if ( $Gateway == 1 ) { + + # TEST MODE + if ( $QueryString =~ /ticket/ ) { + $QueryString =~ /ticket%3D([^&]+)/; + my $ST = $1; + my $User = $cas->validateST( $app_url, $ST ); + return $User; + } + + if ( $QueryString =~ /checked_cas/ ) { + return ''; + } + + my $login_url = $cas->getServerLoginGatewayURL( $app_url . '?checked_cas=1' ); + my $q = CGI->new(); + print $q->redirect( -URL => $login_url ); + } + else { + $Self->Debug("Autenticando: " . $QueryString); + + # If no ticket passed, redirect to CAS to authenticate/get token + unless ( $QueryString =~ /ticket=/ || $QueryString =~ /ticket%3D/ ) { + my $redurl = $app_url . "?" . $Param{RequestedURL}; + $redurl = uri_escape($redurl); + my $login_url = $cas->getServerLoginURL( $redurl ); + my $q = CGI->new(); + print $q->redirect( -URL => $login_url ); + } + else { + $Self->Debug("Recebida URL com ticket: " . $QueryString); + + # CAS session created - record id + $QueryString =~ /ticket=([^&]+)/; + my $ST = $1; + if (! $ST) { + $QueryString =~ /ticket%3D([^&]+)/; + $ST = $1; + } + + my $requrl = $Param{RequestedURL}; + my $substring = substr($requrl, 0, index($requrl, "&ticket=ST")); + + my $redurl = $app_url . "?" . $substring; + $redurl = uri_escape($redurl); + + $Self->Debug("Validando URL $redurl com ticket $ST"); + $User = $cas->validateST( $redurl, $ST ); + + $Self->Debug("Autenticou... $User"); + if ($User) { + $Kernel::OM->Get("Kernel::System::DB")->Do( + SQL => 'DELETE FROM cas_session WHERE UserLogin=?', + Bind => [ \$User ], + ); + + $Kernel::OM->Get("Kernel::System::DB")->Do( + SQL => 'INSERT INTO cas_session (UserLogin,Ticket) VALUES (?, ?)', + Bind => [ \$User, \$ST, ], + ); + } + } + } + return $User; +} + +sub Debug { + my $Self = shift; + my $msg = shift; + $Kernel::OM->Get("Kernel::System::Log")->Log( Priority => 'debug', Message => $msg ); +} + +1; diff --git a/Kernel/System/CustomerAuth/CAS.pm b/Kernel/System/CustomerAuth/CAS.pm new file mode 100755 index 0000000..e03beff --- /dev/null +++ b/Kernel/System/CustomerAuth/CAS.pm @@ -0,0 +1,154 @@ +# -- +# Kernel/System/CustomerAuth/CAS.pm - provides the CAS authentication through Jasig +# +# Copyright (C) 2015-2017 - Rodrigo Gonçalves - rodrigo@goncalves.pro.br +# -- +# $Id: CAS.pm,v 2.0 2015/01/05 15:16:05 mb Exp $ +# +# Version 2015/01/15 - RG - Adjusts for OTRS4 +# Version 2016-01-18 - RG - Fixes for OTRS 5.0.6 +# Version 2017-12-07 - RG - Fixes for OTRS 6.0.1 +# +# -- +# This software comes with ABSOLUTELY NO WARRANTY. For details, see +# the enclosed file COPYING for license information (AGPL). If you +# did not receive this file, see http://www.gnu.org/licenses/agpl.txt. +# -- +# Note: +# +# If you use this module, you should use as fallback the following config settings: +# +# If use isn't login through apache ($ENV{REMOTE_USER} or $ENV{HTTP_REMOTE_USER}) +# $Self->{CustomerPanelLoginURL} = 'http://host.example.com/not-authorised-for-otrs.html'; +# +# $Self->{CustomerPanelLogoutURL} = 'http://host.example.com/thanks-for-using-otrs.html'; +# +# -- +package Kernel::System::CustomerAuth::CAS; + +use strict; +use warnings; +use CGI; +use AuthCAS; +use Data::Dumper; +use CGI::Carp qw( fatalsToBrowser ); +use URI::Escape; + +our @ObjectDependencies = ( "Kernel::Config", "Kernel::System::Log", "Kernel::System::DB" ); + +sub new { + my ( $Type, %Param ) = @_; + + # allocate new hash for object + my $Self = {}; + bless( $Self, $Type ); + + # Debug 0=off 1=on + $Self->{Debug} = 0; + $Self->{Count} = $Param{Count} || ''; + + return $Self; +} + +sub GetOption { + my ( $Self, %Param ) = @_; + + # check needed stuff + if ( !$Param{What} ) { + $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need What!" ); + return; + } + + # module options + my %Option = ( PreAuth => 1, ); + + # return option + return $Option{ $Param{What} }; +} + +sub Auth { + my ( $Self, %Param ) = @_; + + my $QueryString = $ENV{"HTTP_REFERER"} || ''; + + my $ConfigObject = $Kernel::OM->Get("Kernel::Config"); + + my $cas = new AuthCAS( casUrl => $ConfigObject->Get('Customer::AuthModule::CAS::CASUrl') ); + my $app_url = $ConfigObject->Get('Customer::AuthModule::CAS::ServiceUrl'); + my $Gateway = $ConfigObject->Get('Customer::AuthModule::CAS::Gateway'); + my $User = ''; + + if ( $Gateway == 1 ) { + + # TEST MODE + if ( $QueryString =~ /ticket/ ) { + $QueryString =~ /ticket%3D([^&]+)/; + my $ST = $1; + my $User = $cas->validateST( $app_url, $ST ); + return $User; + } + + if ( $QueryString =~ /checked_cas/ ) { + return ''; + } + + my $login_url = $cas->getServerLoginGatewayURL( $app_url . '?checked_cas=1' ); + my $q = CGI->new(); + print $q->redirect( -URL => $login_url ); + } + else { + $Self->Debug("Autenticando: " . $QueryString); + + # If no ticket passed, redirect to CAS to authenticate/get token + unless ( $QueryString =~ /ticket=/ || $QueryString =~ /ticket%3D/ ) { + my $redurl = $app_url . "?" . $Param{RequestedURL}; + $redurl = uri_escape($redurl); + my $login_url = $cas->getServerLoginURL( $redurl ); + my $q = CGI->new(); + print $q->redirect( -URL => $login_url ); + } + else { + $Self->Debug("Recebida URL com ticket: " . $QueryString); + + # CAS session created - record id + $QueryString =~ /ticket=([^&]+)/; + my $ST = $1; + if (! $ST) { + $QueryString =~ /ticket%3D([^&]+)/; + $ST = $1; + } + + my $requrl = $Param{RequestedURL}; + my $substring = substr($requrl, 0, index($requrl, "&ticket=ST")); + + my $redurl = $app_url . "?" . $substring; + $redurl = uri_escape($redurl); + + $Self->Debug("Validando URL $redurl com ticket $ST"); + $User = $cas->validateST( $redurl, $ST ); + + $Self->Debug("Autenticou... $User"); + if ($User) { + $Kernel::OM->Get("Kernel::System::DB")->Do( + SQL => 'DELETE FROM cas_session WHERE UserLogin=?', + Bind => [ \$User ], + ); + + $Kernel::OM->Get("Kernel::System::DB")->Do( + SQL => 'INSERT INTO cas_session (UserLogin,Ticket) VALUES (?, ?)', + Bind => [ \$User, \$ST, ], + ); + } + } + } + + return $User; +} + +sub Debug { + my $Self = shift; + my $msg = shift; + $Kernel::OM->Get("Kernel::System::Log")->Log( Priority => 'debug', Message => $msg ); +} + +1; diff --git a/README.md b/README.md new file mode 100644 index 0000000..043351b --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +CAS Authentication (Jasig-based) for OTRS +========================================= + + +Required PERL Packages: +----------------------- + +* AuthCAS (`apt install libauthcas-perl` on Ubuntu) + + +Config required: +---------------- + +*Kernel/Config.pm:* + + $Self->{'AuthModule'} = 'Kernel::System::Auth::CAS'; + $Self->{'AuthModule::CAS::Gateway'} = 0; + $Self->{'AuthModule::CAS::ServiceUrl'} = 'URL FOR OTRS AGENT INDEX (ex: https://host.com/otrs/index.pl)'; + $Self->{'AuthModule::CAS::CASUrl'} = 'URL FOR CAS SERVICE (WITHOUT TRAILING /) - ex: https://cas.systems.com'; + + $Self->{'Customer::AuthModule'} = 'Kernel::System::CustomerAuth::CAS'; + $Self->{'Customer::AuthModule::CAS::Gateway'} = 0; + $Self->{'Customer::AuthModule::CAS::ServiceUrl'} = 'URL FOR OTRS CUSTOMER INDEX (ex: https://host.com/otrs/customer.pl)'; + $Self->{'Customer::AuthModule::CAS::CASUrl'} = 'URL FOR CAS SERVICE (WITHOUT TRAILING /) - ex: https://cas.systems.com'; + + +Package building: +----------------- + +To build the package, do the following: + + cd dist + ./CreateOpm.sh + +The generated package will be on the same `dist` directory + + +Issues: +------- + +This module was developed in-house and thus is provided without warranty or support. If you have a problem, please open an issue on github. \ No newline at end of file diff --git a/dist/CreateOpm.sh b/dist/CreateOpm.sh new file mode 100755 index 0000000..b427e07 --- /dev/null +++ b/dist/CreateOpm.sh @@ -0,0 +1,8 @@ +#!/bin/bash +LOCAL="$PWD" + +echo Build package otrs-cas-authentication; +echo Package will be generated on the current path... +cd /opt/otrs +bin/otrs.Console.pl Dev::Package::Build "$LOCAL"/../CASAuthentication.sopm "$LOCAL"/ +cd $LOCAL \ No newline at end of file -- libgit2 0.21.2