SecureMethodInvocationsPass.php 7.2KB

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