--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/vendor/bundles/JMS/SecurityExtraBundle/Analysis/ServiceAnalyzer.php Sat Sep 24 15:40:41 2011 +0200
@@ -0,0 +1,266 @@
+<?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\Analysis;
+
+use Doctrine\Common\Annotations\Reader;
+
+use JMS\SecurityExtraBundle\Metadata\Driver\AnnotationDriver;
+
+use JMS\SecurityExtraBundle\Metadata\MethodMetadata;
+use JMS\SecurityExtraBundle\Metadata\ClassMetadata;
+use JMS\SecurityExtraBundle\Metadata\ServiceMetadata;
+use Metadata\Driver\DriverChain;
+use \ReflectionClass;
+
+/**
+ * Analyzes a service class including parent classes. The gathered information
+ * is then used to built a proxy class if necessary.
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class ServiceAnalyzer
+{
+ private $reflection;
+ private $files;
+ private $driver;
+ private $pdepend;
+ private $analyzed;
+ private $hierarchy;
+ private $metadata;
+
+ public function __construct($class, Reader $reader)
+ {
+ $this->reflection = new ReflectionClass($class);
+ $this->files = array();
+ $this->hierarchy = array();
+ $this->driver = new DriverChain(array(
+ new AnnotationDriver($reader),
+ ));
+ $this->analyzed = false;
+ }
+
+ public function analyze()
+ {
+ if (true === $this->analyzed) {
+ return;
+ }
+
+ $this->collectFiles();
+ $this->buildClassHierarchy();
+ $this->collectServiceMetadata();
+
+ if ($this->metadata->isProxyRequired()) {
+ $this->normalizeMetadata();
+ $this->analyzeControlFlow();
+ }
+
+ $this->analyzed = true;
+ }
+
+ public function getFiles()
+ {
+ if (!$this->analyzed) {
+ throw new \LogicException('Data not yet available, run analyze() first.');
+ }
+
+ return $this->files;
+ }
+
+ public function getMetadata()
+ {
+ if (!$this->analyzed) {
+ throw new \LogicException('Data not yet available, run analyze() first.');
+ }
+
+ return $this->metadata;
+ }
+
+ private function buildClassHierarchy()
+ {
+ $hierarchy = array();
+ $class = $this->reflection;
+
+ // add classes
+ while (false !== $class) {
+ $hierarchy[] = $class;
+ $class = $class->getParentClass();
+ }
+
+ // add interfaces
+ $addedInterfaces = array();
+ $newHierarchy = array();
+
+ foreach (array_reverse($hierarchy) as $class) {
+ foreach ($class->getInterfaces() as $interface) {
+ if (isset($addedInterfaces[$interface->getName()])) {
+ continue;
+ }
+ $addedInterfaces[$interface->getName()] = true;
+
+ $newHierarchy[] = $interface;
+ }
+
+ $newHierarchy[] = $class;
+ }
+
+ $this->hierarchy = array_reverse($newHierarchy);
+ }
+
+ private function collectFiles()
+ {
+ $this->files[] = $this->reflection->getFileName();
+
+ foreach ($this->reflection->getInterfaces() as $interface) {
+ if (false !== $filename = $interface->getFileName()) {
+ $this->files[] = $filename;
+ }
+ }
+
+ $parent = $this->reflection;
+ while (false !== $parent = $parent->getParentClass()) {
+ if (false !== $filename = $parent->getFileName()) {
+ $this->files[] = $filename;
+ }
+ }
+ }
+
+ private function normalizeMetadata()
+ {
+ $secureMethods = array();
+ foreach ($this->metadata->classMetadata as $class) {
+ if ($class->reflection->isFinal()) {
+ throw new \RuntimeException('Final classes cannot be secured.');
+ }
+
+ foreach ($class->methodMetadata as $name => $method) {
+ if ($method->reflection->isStatic() || $method->reflection->isFinal()) {
+ throw new \RuntimeException('Annotations cannot be defined on final, or static methods.');
+ }
+
+ if (!isset($secureMethods[$name])) {
+ $this->metadata->addMethodMetadata($method);
+ $secureMethods[$name] = $method;
+ } else if ($method->reflection->isAbstract()) {
+ $secureMethods[$name]->merge($method);
+ } else if (false === $secureMethods[$name]->satisfiesParentSecurityPolicy
+ && $method->reflection->getDeclaringClass()->getName() !== $secureMethods[$name]->reflection->getDeclaringClass()->getName()) {
+ throw new \RuntimeException(sprintf('Unresolved security metadata conflict for method "%s::%s" in "%s". Please copy the respective annotations, and add @SatisfiesParentSecurityPolicy to the child method.', $secureMethods[$name]->reflection->getDeclaringClass()->getName(), $name, $secureMethods[$name]->reflection->getDeclaringClass()->getFileName()));
+ }
+ }
+ }
+
+ foreach ($secureMethods as $name => $method) {
+ if ($method->reflection->isAbstract()) {
+ $previous = null;
+ $abstractClass = $method->reflection->getDeclaringClass()->getName();
+ foreach ($this->hierarchy as $refClass) {
+ if ($abstractClass === $fqcn = $refClass->getName()) {
+ $methodMetadata = new MethodMetadata($previous->getName(), $name);
+ $methodMetadata->merge($method);
+ $this->metadata->addMethodMetadata($methodMetadata);
+
+ continue 2;
+ }
+
+ if (!$refClass->isInterface() && $this->hasMethod($refClass, $name)) {
+ $previous = $refClass;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * We only perform a very lightweight control flow analysis. If we stumble upon
+ * something suspicous, we will simply break, and require additional metadata
+ * to resolve the situation.
+ *
+ * @throws \RuntimeException
+ * @return void
+ */
+ private function analyzeControlFlow()
+ {
+ $secureMethods = $this->metadata->methodMetadata;
+ $rootClass = $this->hierarchy[0];
+
+ while (true) {
+ foreach ($rootClass->getMethods() as $method) {
+ if (!$this->hasMethod($rootClass, $method->getName())) {
+ continue;
+ }
+
+ if (!isset($secureMethods[$name = $method->getName()])) {
+ continue;
+ }
+
+ if ($secureMethods[$name]->reflection->getDeclaringClass()->getName() !== $rootClass->getName()) {
+ throw new \RuntimeException(sprintf(
+ 'You have overridden a secured method "%s::%s" in "%s". '
+ .'Please copy over the applicable security metadata, and '
+ .'also add @SatisfiesParentSecurityPolicy.',
+ $secureMethods[$name]->reflection->getDeclaringClass()->getName(),
+ $name,
+ $rootClass->getName()
+ ));
+ }
+
+ unset($secureMethods[$method->getName()]);
+ }
+
+ if (null === $rootClass = $rootClass->getParentClass()) {
+ break;
+ }
+
+ if (0 === count($secureMethods)) {
+ break;
+ }
+ }
+ }
+
+ private function collectServiceMetadata()
+ {
+ $this->metadata = new ServiceMetadata();
+ $classMetadata = null;
+ foreach ($this->hierarchy as $reflectionClass) {
+ if (null === $classMetadata) {
+ $classMetadata = new ClassMetadata($reflectionClass->getName());
+ }
+
+ if (null !== $aMetadata = $this->driver->loadMetadataForClass($reflectionClass)) {
+ if ($reflectionClass->isInterface()) {
+ $classMetadata->merge($aMetadata);
+ } else {
+ $this->metadata->addClassMetadata($classMetadata);
+
+ $classMetadata = $aMetadata;
+ }
+ }
+ }
+ $this->metadata->addClassMetadata($classMetadata);
+ }
+
+ private function hasMethod(\ReflectionClass $class, $name)
+ {
+ if (!$class->hasMethod($name)) {
+ return false;
+ }
+
+ return $class->getName() === $class->getMethod($name)->getDeclaringClass()->getName();
+ }
+}
\ No newline at end of file