|
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 } |