vendor/bundles/JMS/SecurityExtraBundle/DependencyInjection/Compiler/SecureMethodInvocationsPass.php
<?php
/*
* Copyright 2010 Johannes M. Schmitt <schmittjoh@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace JMS\SecurityExtraBundle\DependencyInjection\Compiler;
use JMS\SecurityExtraBundle\Analysis\ServiceAnalyzer;
use JMS\SecurityExtraBundle\Metadata\ServiceMetadata;
use JMS\SecurityExtraBundle\Metadata\ClassMetadata;
use JMS\SecurityExtraBundle\Generator\ProxyClassGenerator;
use JMS\SecurityExtraBundle\Metadata\Driver\DriverChain;
use \ReflectionClass;
use \ReflectionMethod;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Security\Core\SecurityContext;
/**
* Modifies the container, and sets the proxy classes where needed
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class SecureMethodInvocationsPass implements CompilerPassInterface
{
private $cacheDir;
private $generator;
private $cacheMetadata;
public function __construct($cacheDir)
{
$cacheDir .= '/security/';
if (!file_exists($cacheDir)) {
@mkdir($cacheDir, 0777, true);
}
if (false === is_writable($cacheDir)) {
throw new \RuntimeException('Cannot write to cache folder: '.$cacheDir);
}
$this->cacheDir = $cacheDir;
if (!file_exists($cacheDir.'SecurityProxies/')) {
@mkdir($cacheDir.'SecurityProxies/', 0777, true);
}
if (false === is_writeable($cacheDir.'SecurityProxies/')) {
throw new \RuntimeException('Cannot write to cache folder: '.$cacheDir.'SecurityProxies/');
}
$this->generator = new ProxyClassGenerator();
$this->createOrLoadCacheMetadata();
}
/**
* {@inheritDoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('security.access.method_interceptor')) {
return;
}
$services = $container->findTaggedServiceIds('security.secure_service');
$secureNotAll = !$container->getParameter('security.extra.secure_all_services');
$parameterBag = $container->getParameterBag();
foreach ($container->getDefinitions() as $id => $definition) {
if ($secureNotAll && !isset($services[$id])) {
continue;
}
if ((null === $class = $definition->getClass()) || !class_exists($class)) {
if ($secureNotAll) {
throw new \RuntimeException(sprintf('Could not find class "%s" for "%s".', $class, $id));
}
continue;
}
if (null !== $definition->getFactoryMethod() || $definition->isAbstract() || $definition->isSynthetic()) {
if ($secureNotAll) {
throw new \RuntimeException(sprintf('You cannot secure service "%s", because it is either created by a factory, or an abstract/synthetic service.', $id));
}
continue;
}
$this->processDefinition($container, $id, $definition);
}
$this->writeCacheMetadata();
}
private function processDefinition(ContainerBuilder $container, $id, Definition $definition)
{
if ($this->needsReAssessment($id, $definition)) {
$analyzer = new ServiceAnalyzer($definition->getClass(), $container->get('annotation_reader'));
$analyzer->analyze();
$files = array();
foreach ($analyzer->getFiles() as $file) {
$container->addResource($file = new FileResource($file));
$files[] = $file;
}
$metadata = $analyzer->getMetadata();
$proxyClass = $path = null;
if (true === $metadata->isProxyRequired()) {
list($newClassName, $content) = $this->generator->generate($definition, $metadata);
file_put_contents($path = $this->cacheDir.'SecurityProxies/'.$newClassName.'.php', $content);
$definition->setFile($path);
$definition->setClass($proxyClass = 'SecurityProxies\\'.$newClassName);
$definition->addMethodCall('jmsSecurityExtraBundle__setMethodSecurityInterceptor', array(new Reference('security.access.method_interceptor')));
} else if (isset($this->cacheMetadata[$id]['proxy_class'])) {
@unlink($this->cacheDir.$this->cacheMetadata[$id]['proxy_class'].'.php');
}
$this->cacheMetadata[$id] = array(
'class' => $definition->getClass(),
'proxy_class' => $proxyClass,
'proxy_file' => $path,
'analyze_time' => time(),
'files' => $files,
);
} else {
foreach ($this->cacheMetadata[$id]['files'] as $file) {
$container->addResource($file);
}
if (null !== $proxyClass = $this->cacheMetadata[$id]['proxy_class']) {
$definition->setFile($this->cacheMetadata[$id]['proxy_file']);
$definition->setClass($proxyClass);
$definition->addMethodCall('jmsSecurityExtraBundle__setMethodSecurityInterceptor', array(new Reference('security.access.method_interceptor')));
}
}
}
private function needsReAssessment($id, Definition $definition)
{
if (!isset($this->cacheMetadata[$id])) {
return true;
}
$metadata = $this->cacheMetadata[$id];
if ($metadata['class'] !== $definition->getClass()) {
return true;
}
$lastAnalyzed = $metadata['analyze_time'];
foreach ($metadata['files'] as $file) {
if (false === $file->isFresh($lastAnalyzed)) {
return true;
}
}
return false;
}
private function createOrLoadCacheMetadata()
{
if (file_exists($this->cacheDir.'cache.meta')) {
if (!is_readable($this->cacheDir.'cache.meta')) {
throw new \RuntimeException('Cannot load security cache meta data from: '.$this->cacheDir.'cache.meta');
}
$this->cacheMetadata = unserialize(file_get_contents($this->cacheDir.'cache.meta'));
} else {
set_time_limit(0);
$this->cacheMetadata = array();
}
}
private function writeCacheMetadata()
{
if (false === file_put_contents($this->cacheDir.'cache.meta', serialize($this->cacheMetadata))) {
throw new \RuntimeException('Could not write to cache file: '.$this->cacheDir.'cache.meta');
}
}
}