vendor/bundles/JMS/SecurityExtraBundle/DependencyInjection/Compiler/SecureMethodInvocationsPass.php
changeset 0 7f95f8617b0b
equal deleted inserted replaced
-1:000000000000 0:7f95f8617b0b
       
     1 <?php
       
     2 
       
     3 /*
       
     4  * Copyright 2010 Johannes M. Schmitt <schmittjoh@gmail.com>
       
     5  *
       
     6  * Licensed under the Apache License, Version 2.0 (the "License");
       
     7  * you may not use this file except in compliance with the License.
       
     8  * You may obtain a copy of the License at
       
     9  *
       
    10  * http://www.apache.org/licenses/LICENSE-2.0
       
    11  *
       
    12  * Unless required by applicable law or agreed to in writing, software
       
    13  * distributed under the License is distributed on an "AS IS" BASIS,
       
    14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       
    15  * See the License for the specific language governing permissions and
       
    16  * limitations under the License.
       
    17  */
       
    18 
       
    19 namespace JMS\SecurityExtraBundle\DependencyInjection\Compiler;
       
    20 
       
    21 use JMS\SecurityExtraBundle\Analysis\ServiceAnalyzer;
       
    22 use JMS\SecurityExtraBundle\Metadata\ServiceMetadata;
       
    23 use JMS\SecurityExtraBundle\Metadata\ClassMetadata;
       
    24 use JMS\SecurityExtraBundle\Generator\ProxyClassGenerator;
       
    25 use JMS\SecurityExtraBundle\Metadata\Driver\DriverChain;
       
    26 use \ReflectionClass;
       
    27 use \ReflectionMethod;
       
    28 use Symfony\Component\Config\Resource\FileResource;
       
    29 use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
       
    30 use Symfony\Component\DependencyInjection\ContainerBuilder;
       
    31 use Symfony\Component\DependencyInjection\Definition;
       
    32 use Symfony\Component\DependencyInjection\Reference;
       
    33 use Symfony\Component\Security\Core\SecurityContext;
       
    34 
       
    35 /**
       
    36  * Modifies the container, and sets the proxy classes where needed
       
    37  *
       
    38  * @author Johannes M. Schmitt <schmittjoh@gmail.com>
       
    39  */
       
    40 class SecureMethodInvocationsPass implements CompilerPassInterface
       
    41 {
       
    42     private $cacheDir;
       
    43     private $generator;
       
    44     private $cacheMetadata;
       
    45 
       
    46     public function __construct($cacheDir)
       
    47     {
       
    48         $cacheDir .= '/security/';
       
    49         if (!file_exists($cacheDir)) {
       
    50             @mkdir($cacheDir, 0777, true);
       
    51         }
       
    52         if (false === is_writable($cacheDir)) {
       
    53             throw new \RuntimeException('Cannot write to cache folder: '.$cacheDir);
       
    54         }
       
    55         $this->cacheDir = $cacheDir;
       
    56 
       
    57         if (!file_exists($cacheDir.'SecurityProxies/')) {
       
    58             @mkdir($cacheDir.'SecurityProxies/', 0777, true);
       
    59         }
       
    60         if (false === is_writeable($cacheDir.'SecurityProxies/')) {
       
    61             throw new \RuntimeException('Cannot write to cache folder: '.$cacheDir.'SecurityProxies/');
       
    62         }
       
    63 
       
    64         $this->generator = new ProxyClassGenerator();
       
    65         $this->createOrLoadCacheMetadata();
       
    66     }
       
    67 
       
    68     /**
       
    69      * {@inheritDoc}
       
    70      */
       
    71     public function process(ContainerBuilder $container)
       
    72     {
       
    73         if (!$container->hasDefinition('security.access.method_interceptor')) {
       
    74             return;
       
    75         }
       
    76 
       
    77         $services = $container->findTaggedServiceIds('security.secure_service');
       
    78         $secureNotAll = !$container->getParameter('security.extra.secure_all_services');
       
    79 
       
    80         $parameterBag = $container->getParameterBag();
       
    81         foreach ($container->getDefinitions() as $id => $definition) {
       
    82             if ($secureNotAll && !isset($services[$id])) {
       
    83                 continue;
       
    84             }
       
    85 
       
    86             if ((null === $class = $definition->getClass()) || !class_exists($class)) {
       
    87                 if ($secureNotAll) {
       
    88                     throw new \RuntimeException(sprintf('Could not find class "%s" for "%s".', $class, $id));
       
    89                 }
       
    90 
       
    91                 continue;
       
    92             }
       
    93 
       
    94             if (null !== $definition->getFactoryMethod() || $definition->isAbstract() || $definition->isSynthetic()) {
       
    95                 if ($secureNotAll) {
       
    96                     throw new \RuntimeException(sprintf('You cannot secure service "%s", because it is either created by a factory, or an abstract/synthetic service.', $id));
       
    97                 }
       
    98 
       
    99                 continue;
       
   100             }
       
   101 
       
   102             $this->processDefinition($container, $id, $definition);
       
   103         }
       
   104 
       
   105         $this->writeCacheMetadata();
       
   106     }
       
   107 
       
   108     private function processDefinition(ContainerBuilder $container, $id, Definition $definition)
       
   109     {
       
   110         if ($this->needsReAssessment($id, $definition)) {
       
   111             $analyzer = new ServiceAnalyzer($definition->getClass(), $container->get('annotation_reader'));
       
   112             $analyzer->analyze();
       
   113 
       
   114             $files = array();
       
   115             foreach ($analyzer->getFiles() as $file) {
       
   116                 $container->addResource($file = new FileResource($file));
       
   117                 $files[] = $file;
       
   118             }
       
   119 
       
   120             $metadata = $analyzer->getMetadata();
       
   121             $proxyClass = $path = null;
       
   122             if (true === $metadata->isProxyRequired()) {
       
   123                 list($newClassName, $content) = $this->generator->generate($definition, $metadata);
       
   124                 file_put_contents($path = $this->cacheDir.'SecurityProxies/'.$newClassName.'.php', $content);
       
   125                 $definition->setFile($path);
       
   126                 $definition->setClass($proxyClass = 'SecurityProxies\\'.$newClassName);
       
   127                 $definition->addMethodCall('jmsSecurityExtraBundle__setMethodSecurityInterceptor', array(new Reference('security.access.method_interceptor')));
       
   128             } else if (isset($this->cacheMetadata[$id]['proxy_class'])) {
       
   129                 @unlink($this->cacheDir.$this->cacheMetadata[$id]['proxy_class'].'.php');
       
   130             }
       
   131 
       
   132             $this->cacheMetadata[$id] = array(
       
   133                 'class' => $definition->getClass(),
       
   134                 'proxy_class' => $proxyClass,
       
   135                 'proxy_file'  => $path,
       
   136             		'analyze_time' => time(),
       
   137                 'files' => $files,
       
   138             );
       
   139         } else {
       
   140             foreach ($this->cacheMetadata[$id]['files'] as $file) {
       
   141                 $container->addResource($file);
       
   142             }
       
   143 
       
   144             if (null !== $proxyClass = $this->cacheMetadata[$id]['proxy_class']) {
       
   145                 $definition->setFile($this->cacheMetadata[$id]['proxy_file']);
       
   146                 $definition->setClass($proxyClass);
       
   147                 $definition->addMethodCall('jmsSecurityExtraBundle__setMethodSecurityInterceptor', array(new Reference('security.access.method_interceptor')));
       
   148             }
       
   149         }
       
   150     }
       
   151 
       
   152     private function needsReAssessment($id, Definition $definition)
       
   153     {
       
   154         if (!isset($this->cacheMetadata[$id])) {
       
   155             return true;
       
   156         }
       
   157 
       
   158         $metadata = $this->cacheMetadata[$id];
       
   159         if ($metadata['class'] !== $definition->getClass()) {
       
   160             return true;
       
   161         }
       
   162 
       
   163         $lastAnalyzed = $metadata['analyze_time'];
       
   164         foreach ($metadata['files'] as $file) {
       
   165             if (false === $file->isFresh($lastAnalyzed)) {
       
   166                 return true;
       
   167             }
       
   168         }
       
   169 
       
   170         return false;
       
   171     }
       
   172 
       
   173     private function createOrLoadCacheMetadata()
       
   174     {
       
   175         if (file_exists($this->cacheDir.'cache.meta')) {
       
   176             if (!is_readable($this->cacheDir.'cache.meta')) {
       
   177                 throw new \RuntimeException('Cannot load security cache meta data from: '.$this->cacheDir.'cache.meta');
       
   178             }
       
   179 
       
   180             $this->cacheMetadata = unserialize(file_get_contents($this->cacheDir.'cache.meta'));
       
   181         } else {
       
   182             set_time_limit(0);
       
   183             $this->cacheMetadata = array();
       
   184         }
       
   185     }
       
   186 
       
   187     private function writeCacheMetadata()
       
   188     {
       
   189         if (false === file_put_contents($this->cacheDir.'cache.meta', serialize($this->cacheMetadata))) {
       
   190             throw new \RuntimeException('Could not write to cache file: '.$this->cacheDir.'cache.meta');
       
   191         }
       
   192     }
       
   193 }