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