eZ Publish  [4.0]
ezsslzone.php
Go to the documentation of this file.
00001 <?php
00002 //
00003 // Definition of eZSSLZone class
00004 //
00005 // Created on: <12-Jul-2005 13:01:07 vs>
00006 //
00007 // ## BEGIN COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
00008 // SOFTWARE NAME: eZ Publish
00009 // SOFTWARE RELEASE: 4.0.x
00010 // COPYRIGHT NOTICE: Copyright (C) 1999-2008 eZ Systems AS
00011 // SOFTWARE LICENSE: GNU General Public License v2.0
00012 // NOTICE: >
00013 //   This program is free software; you can redistribute it and/or
00014 //   modify it under the terms of version 2.0  of the GNU General
00015 //   Public License as published by the Free Software Foundation.
00016 //
00017 //   This program is distributed in the hope that it will be useful,
00018 //   but WITHOUT ANY WARRANTY; without even the implied warranty of
00019 //   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00020 //   GNU General Public License for more details.
00021 //
00022 //   You should have received a copy of version 2.0 of the GNU General
00023 //   Public License along with this program; if not, write to the Free
00024 //   Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
00025 //   MA 02110-1301, USA.
00026 //
00027 //
00028 // ## END COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
00029 //
00030 
00031 
00032 /*!
00033  \class eZSSLZone ezsslzone.php
00034  \brief SSL zones handling functionality.
00035 
00036  Using functionality of this class you can mark certain parts of you site
00037  as "SSL zones". After that users will be able to access those parts only over SSL.
00038  When entering an SSL zone, user will be automatically switched to SSL.
00039  When leaving an SSL zone, user will be automatically switched to plain HTTP.
00040  Such a switch is called "access mode change" in the comments below.
00041 
00042  SSL zones may be defined on either module/view basis, or on subtree basis.
00043 
00044  For more details pleaase see doc/feautures/3.8/ssl_zones.txt
00045 */
00046 
00047 //include_once( 'lib/ezutils/classes/ezini.php' );
00048 //include_once( 'lib/ezutils/classes/ezsys.php' );
00049 
00050 class eZSSLZone
00051 {
00052     const DEFAULT_SSL_PORT = 443;
00053 
00054     /*! \privatesection */
00055 
00056     /**
00057      * \static
00058      * Returns true if the SSL zones functionality is enabled, false otherwise.
00059      * The result is cached in memory to save time on multiple invocations.
00060      */
00061     static function enabled()
00062     {
00063         if ( isset( $GLOBALS['eZSSLZoneEnabled'] ) )
00064             return $GLOBALS['eZSSLZoneEnabled'];
00065 
00066         $enabled = false;
00067         $ini = eZINI::instance();
00068         if ( $ini->hasVariable( 'SSLZoneSettings', 'SSLZones' ) )
00069             $enabled = ( $ini->variable( 'SSLZoneSettings', 'SSLZones' ) == 'enabled' );
00070 
00071         return $GLOBALS['eZSSLZoneEnabled'] = $enabled;
00072     }
00073 
00074     /**
00075      * \static
00076      */
00077     static function cacheFileName()
00078     {
00079         //include_once( 'lib/ezutils/classes/ezsys.php' );
00080         //include_once( 'lib/ezfile/classes/ezdir.php' );
00081         return eZDir::path( array( eZSys::cacheDirectory(), 'ssl_zones_cache.php' ) );
00082     }
00083 
00084     /**
00085      * \static
00086      */
00087     static function clearCacheIfNeeded()
00088     {
00089         if ( eZSSLZone::enabled() )
00090             eZSSLZone::clearCache();
00091     }
00092 
00093     /**
00094      * \static
00095      */
00096     static function clearCache()
00097     {
00098         eZDebugSetting::writeDebug( 'kernel-ssl-zone', 'Clearing caches.' );
00099 
00100         // clear in-memory cache
00101         unset( $GLOBALS['eZSSLZonesCachedPathStrings'] );
00102 
00103         // and remove cache file
00104         $cacheFileName = eZSSLZone::cacheFileName();
00105         if ( file_exists( $cacheFileName ) )
00106             unlink( $cacheFileName );
00107     }
00108 
00109     /**
00110      * \static
00111      * Load content SSL zones definitions.
00112      * Substitute URIs with corresponding path strings
00113      * (e.g. "/news" would be subsituted with "/1/2/50").
00114      * The result is cached in memory to save time on multiple invocations.
00115      * It is also saved in a cache file that is usually updated by eZContentCacheManager along with content cache.
00116      */
00117     static function getSSLZones()
00118     {
00119         if ( !isset( $GLOBALS['eZSSLZonesCachedPathStrings'] ) ) // if in-memory cache does not exist
00120         {
00121             $cacheFileName = eZSSLZone::cacheFileName();
00122             $cacheDirName = eZSys::cacheDirectory();
00123 
00124             // if file cache does not exist then create it
00125             if ( !is_readable( $cacheFileName ) )
00126             {
00127                 $ini = eZINI::instance();
00128                 $sslSubtrees = $ini->variable( 'SSLZoneSettings', 'SSLSubtrees' );
00129 
00130                 if ( !isset( $sslSubtrees ) || !$sslSubtrees )
00131                     return array();
00132 
00133                 // if there are some content SSL zones defined in the ini settings
00134                 // then let's calculate path strings for them
00135                 $pathStringsArray = array();
00136                 foreach ( $sslSubtrees as $uri )
00137                 {
00138                     //include_once( 'kernel/classes/ezurlaliasml.php' );
00139                     $elements = eZURLAliasML::fetchByPath( $uri );
00140                     if ( count( $elements ) == 0 )
00141                     {
00142                         eZDebug::writeError( "Cannot fetch URI '$uri'", 'eZSSLZone::getSSLZones' );
00143                         continue;
00144                     }
00145                     $action = $elements[0]->attribute( 'action' );
00146                     if ( !preg_match( "#^eznode:(.+)#", $action, $matches ) )
00147                     {
00148                         eZDebug::writeError( "Cannot decode action '$action' for URI '$uri'", 'eZSSLZone::getSSLZones' );
00149                         continue;
00150                     }
00151                     $nodeID = (int)$matches[1];
00152                     $node = eZContentObjectTreeNode::fetch( $nodeID );
00153                     if ( !$node instanceof eZContentObjectTreeNode )
00154                     {
00155                         eZDebug::writeError( "cannot fetch node by URI '$uri'", 'eZSSLZone::getSSLZones' );
00156                         continue;
00157                     }
00158                     $pathStringsArray[$uri] = $node->attribute( 'path_string' );
00159                     unset( $node );
00160                 }
00161 
00162                 // write calculated path strings to the file
00163                 if ( !file_exists( $cacheDirName ) )
00164                 {
00165                     eZDir::mkdir( $cacheDirName, false, true );
00166                 }
00167 
00168                 $fh = fopen( $cacheFileName, 'w' );
00169                 if ( $fh )
00170                 {
00171                     fwrite( $fh, "<?php\n\$pathStringsArray = " . var_export( $pathStringsArray, true ) . ";\n?>" );
00172                     fclose( $fh );
00173 
00174                     $perm = eZINI::instance()->variable( 'FileSettings', 'StorageFilePermissions' );
00175                     chmod( $cacheFileName, octdec( $perm ) );
00176                 }
00177 
00178                 return $GLOBALS['eZSSLZonesCachedPathStrings'] = $pathStringsArray;
00179             }
00180             else // if the cache file exists
00181             {
00182                 // let's read its contents and return them
00183                 include_once( $cacheFileName ); // stores array to $pathStringsArray
00184                 return $GLOBALS['eZSSLZonesCachedPathStrings'] = $pathStringsArray;
00185             }
00186         }
00187 
00188         // else if in-memory cache already exists then return its contents
00189         $pathStringsArray = $GLOBALS['eZSSLZonesCachedPathStrings'];
00190 
00191         return $pathStringsArray;
00192     }
00193 
00194     /**
00195      * \static
00196      * Checks if a given module/view pair is in the given list of views.
00197      * Wildcard matching on view name is done.
00198      *
00199      * \return 2        if wildcard match occurs on the given view
00200      *         1        if exact match occurs on the given view
00201      *         0        if the view is not found in the list
00202      */
00203     static function viewIsInArray( $module, $view, $moduleViews )
00204     {
00205         if ( in_array( "$module/$view", $moduleViews ) )
00206             return 2;
00207         if ( in_array( "$module/*", $moduleViews ) )
00208             return 1;
00209         return 0;
00210     }
00211 
00212     /**
00213      * \static
00214      * \return true if the view is defined as 'keep'
00215      */
00216     static function isKeepModeView( $module, $view )
00217     {
00218         $ini = eZINI::instance();
00219         $viewsModes  = $ini->variable( 'SSLZoneSettings', 'ModuleViewAccessMode' );
00220         $sslViews      = array_keys( $viewsModes, 'ssl' );
00221         $keepModeViews = array_keys( $viewsModes, 'keep' );
00222 
00223         if ( eZSSLZone::viewIsInArray( $module, $view, $keepModeViews ) <
00224              eZSSLZone::viewIsInArray( $module, $view, $sslViews ) )
00225         {
00226             eZDebugSetting::writeDebug( 'kernel-ssl-zone', 'The view cannot choose access mode itself.' );
00227             return false;
00228         }
00229 
00230         return true;
00231     }
00232 
00233     /**
00234      * \static
00235      * \param  $inSSL  The desired access mode.
00236      *
00237      * Change access mode (HTTP/HTTPS):
00238      * - If previous mode was HHTP but $inSSL is true, we switch to SSL.
00239      * - If previous mode was SSL  but $inSSL is false, we switch to HTTP.
00240      * - Otherwise no mode change is occured.
00241      *
00242      * Mode change is done by redirect to the same URL, but with changed
00243      * protocol (http/https) and TCP port.
00244      *
00245      * In case of mode change this method does not return (exit() is called).
00246      */
00247     static function switchIfNeeded( $inSSL )
00248     {
00249         // if it's undefined whether we should redirect  we do nothing
00250         if ( !isset( $inSSL ) )
00251             return;
00252 
00253         // $nowSSl is true if current access mode is HTTPS.
00254         $nowSSL = eZSys::isSSLNow();
00255 
00256         $requestURI = eZSys::requestURI();
00257         $indexDir = eZSys::indexDir( false );
00258 
00259         $sslZoneRedirectionURL = false;
00260         if ( $nowSSL && !$inSSL )
00261         {
00262             // switch to plain HTTP
00263             $ini = eZINI::instance();
00264             $host = $ini->variable( 'SiteSettings', 'SiteURL' );
00265             $sslZoneRedirectionURL = "http://" . $host . $indexDir . $requestURI;
00266         }
00267         elseif ( !$nowSSL && $inSSL )
00268         {
00269             // switch to HTTPS
00270             $host = eZSys::serverVariable( 'HTTP_HOST' );
00271             $host = preg_replace( '/:\d+$/', '', $host );
00272 
00273             $ini = eZINI::instance();
00274             $sslPort = $ini->variable( 'SiteSettings', 'SSLPort' );
00275             $sslPortString = ( $sslPort == eZSSLZone::DEFAULT_SSL_PORT ) ? '' : ":$sslPort";
00276             $sslZoneRedirectionURL = "https://" . $host  . $sslPortString . $indexDir . $requestURI;
00277         }
00278 
00279         if ( $sslZoneRedirectionURL ) // if a redirection URL is found
00280         {
00281             eZDebugSetting::writeDebug( 'kernel-ssl-zone', "redirecting to [$sslZoneRedirectionURL]" );
00282             eZHTTPTool::redirect( $sslZoneRedirectionURL, array(), false, false );
00283             eZExecution::cleanExit();
00284         }
00285     }
00286 
00287     /*! \publicsection */
00288 
00289     /**
00290      * \static
00291      * Check whether the given node should cause access mode change.
00292      * It it should, this method does not return.
00293      *
00294      * \see checkNode()
00295      */
00296     static function checkNodeID( $module, $view, $nodeID )
00297     {
00298         if ( !eZSSLZone::enabled() )
00299             return;
00300 
00301         /* If the given module/view is not in the list of 'keep mode' views,
00302          * i.e. it cannot choose access mode itself,
00303          * then do nothing.
00304          */
00305         if ( !eZSSLZone::isKeepModeView( $module, $view ) )
00306             return;
00307 
00308         // Fetch path string for the given node.
00309         $pathStrings = eZPersistentObject::fetchObjectList(
00310             eZContentObjectTreeNode::definition(), // def
00311             array( 'path_string' ),                // field_filters
00312             array( 'node_id' => $nodeID ),         // conds
00313             null,                                  // sorts
00314             null,                                  // limit
00315             false                                  // asObject
00316         );
00317 
00318         if ( !$pathStrings )
00319         {
00320             eZDebug::writeError( "Node #$nodeID not found", "eZSSLZone::checkNodeID" );
00321             return;
00322         }
00323 
00324         eZSSLZone::checkNodePath( $module, $view, $pathStrings[0]['path_string'] );
00325     }
00326 
00327     /**
00328      * \static
00329      * Check whether the given node should cause access mode change.
00330      * It it should, this method does not return.
00331      */
00332     static function checkNode( $module, $view, &$node, $redirect = true )
00333     {
00334         if ( !eZSSLZone::enabled() )
00335             return;
00336 
00337         /* If the given module/view is not in the list of 'keep mode' views,
00338          * i.e. it cannot choose access mode itself,
00339          * then do nothing.
00340          */
00341         if ( !$redirect && !eZSSLZone::isKeepModeView( $module, $view ) )
00342             return;
00343 
00344         //include_once( 'kernel/classes/ezcontentobjecttreenode.php' );
00345         $pathString = $node->attribute( 'path_string' );
00346 
00347         return eZSSLZone::checkNodePath( $module, $view, $pathString, $redirect );
00348     }
00349 
00350     /**
00351      * \static
00352      * Check whether the given node should cause access mode change.
00353      * It it should, this method does not return.
00354      */
00355     static function checkNodePath( $module, $view, $pathString, $redirect = true )
00356     {
00357         if ( !eZSSLZone::enabled() )
00358             return;
00359 
00360         /* If the given module/view is not in the list of 'keep mode' views,
00361          * i.e. it cannot choose access mode itself,
00362          * then do nothing.
00363          */
00364         if ( !$redirect && !eZSSLZone::isKeepModeView( $module, $view ) )
00365             return;
00366 
00367         // Decide whether the node belongs to an SSL zone or not.
00368         $sslZones  = eZSSLZone::getSSLZones();
00369 
00370         $inSSLZone = false;
00371         foreach ( $sslZones as $sslZonePathString )
00372         {
00373             if ( strpos( $pathString, $sslZonePathString ) === 0 )
00374             {
00375                 $inSSLZone = true;
00376                 break;
00377             }
00378         }
00379 
00380         eZDebugSetting::writeDebug( 'kernel-ssl-zone',
00381                                     ( $inSSLZone ? 'yes' : 'no' ),
00382                                     "Does the node having path $pathString belong to an SSL zone?" );
00383 
00384         if ( $redirect )
00385             eZSSLZone::switchIfNeeded( $inSSLZone );
00386 
00387         return $inSSLZone;
00388     }
00389 
00390     /**
00391      * \static
00392      * Check whether the given object should cause access mode change.
00393      * It it should, this method does not return.
00394      */
00395     static function checkObject( $module, $view, $object )
00396     {
00397         if ( !eZSSLZone::enabled() )
00398             return;
00399 
00400         /* If the given module/view is not in the list of 'keep mode' views,
00401          * i.e. it cannot choose access mode itself,
00402          * then do nothing.
00403          */
00404         if ( !eZSSLZone::isKeepModeView( $module, $view ) )
00405             return;
00406 
00407         $pathStringList = eZPersistentObject::fetchObjectList(
00408             eZContentObjectTreeNode::definition(),                     // def
00409             array( 'path_string' ),                                    // field_filters
00410             array( 'contentobject_id' => $object->attribute( 'id' ) ), // conds
00411             null,                                                      // sorts
00412             null,                                                      // limit
00413             false                                                      // asObject
00414         );
00415 
00416         if ( is_array( $pathStringList ) && count( $pathStringList ) )
00417         {
00418             /* The object has some assigned nodes.
00419              * If at least one of those nodes belongs to an SSL zone,
00420              * we switch to SSL.
00421              */
00422 
00423             // "flatten" the array
00424             array_walk( $pathStringList, create_function( '&$a', '$a = $a[\'path_string\'];' ) );
00425         }
00426         else
00427         {
00428             /* The object has no assigned nodes.
00429              * Let's work with its parent nodes.
00430              * If at least one of the parent nodes belongs to an SSL zone,
00431              * we switch to SSL.
00432              */
00433             $pathStringList = array();
00434             $nodes = $object->parentNodes( $object->attribute( 'current' ) );
00435             if ( !is_array( $nodes ) )
00436             {
00437                 eZDebug::writeError( 'Object ' . $object->attribute( 'is' ) .
00438                                      'does not have neither assigned nor parent nodes.' );
00439             }
00440             else
00441             {
00442                 foreach( $nodes as $node )
00443                 {
00444                     $pathStringList[] = $node->attribute( 'path_string' );
00445                 }
00446             }
00447         }
00448 
00449         $inSSL = false; // does the object belong to an SSL zone?
00450         foreach ( $pathStringList as $pathString )
00451         {
00452             if ( eZSSLZone::checkNodePath( $module, $view, $pathString, false ) )
00453             {
00454                 $inSSL = true;
00455                 break;
00456             }
00457         }
00458 
00459         eZSSLZone::switchIfNeeded( $inSSL );
00460     }
00461 
00462     /**
00463      * \static
00464      * Decide whether we should change access mode for this module view or not.
00465      * Called from index.php.
00466      */
00467     static function checkModuleView( $module, $view )
00468     {
00469         if ( !eZSSLZone::enabled() )
00470             return;
00471 
00472         $ini = eZINI::instance();
00473         $viewsModes  = $ini->variable( 'SSLZoneSettings', 'ModuleViewAccessMode' );
00474 
00475         $sslViews      = array_keys( $viewsModes, 'ssl' );
00476         $keepModeViews = array_keys( $viewsModes, 'keep' );
00477 
00478         $sslPriority      = eZSSLZone::viewIsInArray( $module, $view, $sslViews      );
00479         $keepModePriority = eZSSLZone::viewIsInArray( $module, $view, $keepModeViews );
00480 
00481         if ( $sslPriority && $keepModePriority && $sslPriority == $keepModePriority )
00482         {
00483             eZDebug::writeError( "Configuration error: view $module/$view is defined both as 'ssl' and 'keep'",
00484                                  'eZSSLZone' );
00485             return;
00486         }
00487 
00488         /* If the view belongs to the list of views we should not change access mode for,
00489          * then do nothing.
00490          * (however, the view may do access mode switch itself later)
00491          */
00492         if ( $keepModePriority > $sslPriority )
00493         {
00494             eZDebugSetting::writeDebug( 'kernel-ssl-zone', 'Keeping current access mode...' );
00495             return;
00496         }
00497 
00498         /* Otherwise we look if the view is in the list of SSL views,
00499          * and if it is, we switch to SSL. Else, if it's not, we switch to plain HTTP.
00500          */
00501         $inSSL = ( $sslPriority > 0 );
00502 
00503         eZDebugSetting::writeDebug( 'kernel-ssl-zone',
00504                                     ( isset( $inSSL ) ? ( $inSSL?'yes':'no') : 'dunno' ),
00505                                     'Should we use SSL for this view?' );
00506 
00507         // Change access mode if we need to.
00508         eZSSLZone::switchIfNeeded( $inSSL );
00509     }
00510 }
00511 ?>