Source of file SecureFileExtension.php
Size: 6,459 Bytes - Last Modified: 2021-12-23T10:34:10+00:00
/var/www/docs.ssmods.com/process/src/code/SecureFileExtension.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 | <?php /** * Extension that allows a CMS user to define view * access to a particular {@link Folder} and the {@link File}s within. * * An access file with rewrite rules is written into the {@link Folder} directory * once it's saved in the CMS (see {@link SecureFileExtension::onAfterWrite()}), * so that the webserver will force a rewrite on the requested assets file path, * turning it into a SilverStripe request so the file can be checked against * access settings. * * Beware that this will have a performance impact on file requests that exist in * a {@link Folder} that have been secured, as the file request will be treated * as a dynamic request instead of sent directly by the webserver as a static file. */ class SecureFileExtension extends DataExtension { private static $db = array( 'CanViewType' => 'Enum("Anyone,LoggedInUsers,OnlyTheseUsers,Inherit","Inherit")', ); private static $many_many = array( 'ViewerGroups' => 'Group', ); private static $current_access_config = null; private static $access_config = array(); /** * Tries to autodetect the current webserver and match it against a registered * webserver configuration through access_config. Check _config.php * in this module for an example of how those access files are registered through the * Config system. * * You can manually set the config by setting current_access_config yourself. * * @return array */ public function getAccessConfig() { $currentConfig = Config::inst()->get('SecureFileExtension', 'current_access_config'); if($currentConfig) return $currentConfig; $registeredConfigs = Config::inst()->get('SecureFileExtension', 'access_config'); if(!empty($_SERVER['SERVER_SOFTWARE'])) { if(strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== false) { return $registeredConfigs['Apache']; } elseif(strpos($_SERVER['SERVER_SOFTWARE'], 'IIS') !== false) { return $registeredConfigs['IIS']; } } // fallback to Apache return $registeredConfigs['Apache']; } public function canView($member = null) { if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) { $member = Member::currentUser(); } // admin override if($member && Permission::checkMember($member, "ADMIN")) { return true; } // allow owner to view own file if( $member && $member->ID == $this->owner->OwnerID ){ return true; } if ($this->owner instanceof Folder) { switch ($this->owner->CanViewType) { case 'LoggedInUsers': return (bool)$member; case 'Inherit': if ($this->owner->ParentID) return $this->owner->Parent()->canView($member); else return $this->owner->defaultPermissions($member); case 'OnlyTheseUsers': if ($member && $member->inGroups($this->owner->ViewerGroups())) return true; else return false; case 'Anyone': default: return true; } } // File DataObjects created by SearchForm don't have a ParentID, which we need // We fix this by re-getting the File object by it's ID if the ParentID is missing and use that $file = $this->owner; // we assume if a file doesn't have a parent, it's in the root of assets, and therefore not secured // because there's currently no way to secure the "root" assets folder if($file->Parent()->exists()) { return $file->Parent()->canView($member); } return $this->owner->defaultPermissions($member); } /** * Checks for any default access permissions and tests against them if found. Default permssions are set via the * Config system. * * @param Member $member * @return boolean */ public function defaultPermissions($member = null) { if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) { $member = Member::currentUser(); } if ($default = Config::inst()->get('SecureAssets', 'Defaults')) { switch ($default['Permission']) { case 'LoggedInUsers': return (bool) $member; case 'OnlyTheseUsers': if ($member && $member->inGroups($default['Groups'])) return true; else return false; case 'Anyone': default: return true; } } return true; } function needsAccessFile() { if(SapphireTest::is_running_test()) { return false; } switch ($this->owner->CanViewType) { case 'LoggedInUsers': case 'OnlyTheseUsers': return true; case 'Inherit': // We don't need an access file if access is set to 'inherit', because Apache also uses parent directories .htaccess files case 'Anyone': default: return false; } } /** * Access tab, copied from SiteTree */ public function updateCMSFields(FieldList $fields) { if(($this->owner instanceof Folder) && $this->owner->ID) { $options = array(); if($this->owner->ParentID) $options['Inherit'] = _t('SecureFile.INHERIT', "Inherit from parent folder"); $options['Anyone'] = _t('SiteTree.ACCESSANYONE', 'Anyone'); $options['LoggedInUsers'] = _t('SiteTree.ACCESSLOGGEDIN', 'Logged-in users'); $options['OnlyTheseUsers'] = _t('SiteTree.ACCESSONLYTHESE', 'Only these people (choose from list)'); $fields->push( new HeaderField( 'WhoCanViewHeader', _t('SecureFile.ACCESSHEADER', 'Who can view files in this folder?'), 2 ) ); $fields->push( new OptionsetField( 'CanViewType', '', $options ) ); $fields->push( new ListboxField( 'ViewerGroups', $this->owner->fieldLabel('ViewerGroups'), Group::get()->map()->toArray(), null, null, true ) ); } } /** * Add or remove access rules to the filesystem path. * CAUTION: This will not work properly in the presence of third-party .htaccess file */ function onAfterWrite() { parent::onAfterWrite(); // this will mess with tests like FolderTest, it'll expect an .htaccess to be there, // but onAfterWrite here will unintentionally remove it. We can workaround that by // skipping the access file writing if a unit test is currently running. if(SapphireTest::is_running_test()) { return false; } if($this->owner instanceof Folder) { $config = $this->getAccessConfig(); $accessFilePath = $this->owner->getFullPath() . $config['file']; if($this->needsAccessFile()) { if(!file_exists($accessFilePath)) file_put_contents($accessFilePath, $config['content']); } else { if(file_exists($accessFilePath)) unlink($accessFilePath); } } } } |