1<?php 2/** 3 * Copyright since 2007 PrestaShop SA and Contributors 4 * PrestaShop is an International Registered Trademark & Property of PrestaShop SA 5 * 6 * NOTICE OF LICENSE 7 * 8 * This source file is subject to the Open Software License (OSL 3.0) 9 * that is bundled with this package in the file LICENSE.md. 10 * It is also available through the world-wide-web at this URL: 11 * https://opensource.org/licenses/OSL-3.0 12 * If you did not receive a copy of the license and are unable to 13 * obtain it through the world-wide-web, please send an email 14 * to license@prestashop.com so we can send you a copy immediately. 15 * 16 * DISCLAIMER 17 * 18 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer 19 * versions in the future. If you wish to customize PrestaShop for your 20 * needs please refer to https://devdocs.prestashop.com/ for more information. 21 * 22 * @author PrestaShop SA and Contributors <contact@prestashop.com> 23 * @copyright Since 2007 PrestaShop SA and Contributors 24 * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) 25 */ 26 27namespace PrestaShopBundle\Command; 28 29use PrestaShopBundle\Routing\Linter\Exception\LinterException; 30use PrestaShopBundle\Routing\Linter\SecurityAnnotationLinter; 31use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; 32use Symfony\Component\Console\Input\InputArgument; 33use Symfony\Component\Console\Input\InputInterface; 34use Symfony\Component\Console\Output\OutputInterface; 35use Symfony\Component\Console\Style\SymfonyStyle; 36use Symfony\Component\Routing\Route; 37 38/** 39 * Checks if all admin routes have @AdminSecurity configured 40 * 41 * @see \PrestaShopBundle\Security\Annotation\AdminSecurity 42 */ 43final class SecurityAnnotationLinterCommand extends ContainerAwareCommand 44{ 45 public const ACTION_LIST_ALL = 'list'; 46 public const ACTION_FIND_MISSING = 'find-missing'; 47 48 /** 49 * @param string $expression 50 * 51 * @return string 52 */ 53 public static function parseExpression($expression) 54 { 55 $pattern1 = '#\[(.*)\]#'; 56 $pattern2 = '#is_granted\((.*),#'; 57 $matches1 = []; 58 $matches2 = []; 59 preg_match($pattern1, $expression, $matches1); 60 61 if (count($matches1) > 1) { 62 return $matches1[1]; 63 } 64 preg_match($pattern2, $expression, $matches2); 65 if (count($matches2) > 1) { 66 return $matches2[1]; 67 } 68 69 return ''; 70 } 71 72 /** 73 * @return string[] 74 */ 75 public static function getAvailableActions() 76 { 77 return [self::ACTION_LIST_ALL, self::ACTION_FIND_MISSING]; 78 } 79 80 /** 81 * {@inheritdoc} 82 */ 83 public function configure() 84 { 85 $description = 'Checks if Back Office route controllers has configured Security annotations.'; 86 $actionDescription = sprintf( 87 'Action to perform, must be one of: %s', 88 implode(', ', self::getAvailableActions()) 89 ); 90 91 $this 92 ->setName('prestashop:linter:security-annotation') 93 ->setDescription($description) 94 ->addArgument('action', InputArgument::REQUIRED, $actionDescription); 95 } 96 97 /** 98 * {@inheritdoc} 99 */ 100 protected function execute(InputInterface $input, OutputInterface $output) 101 { 102 $actionToPerform = $input->getArgument('action'); 103 104 if (!in_array($actionToPerform, self::getAvailableActions())) { 105 throw new \InvalidArgumentException(sprintf( 106 'Action must be one of: %s', 107 implode(', ', self::getAvailableActions()) 108 ) 109 ); 110 } 111 112 switch ($actionToPerform) { 113 case self::ACTION_LIST_ALL: 114 $this->listAllRoutesAndRelatedPermissions($input, $output); 115 break; 116 case self::ACTION_FIND_MISSING: 117 $this->findRoutesWithMissingSecurityAnnotations($input, $output); 118 break; 119 120 default: 121 throw new \RuntimeException(sprintf('Unknown action %s', $actionToPerform)); 122 } 123 124 return 0; 125 } 126 127 /** 128 * @param InputInterface $input 129 * @param OutputInterface $output 130 */ 131 private function listAllRoutesAndRelatedPermissions(InputInterface $input, OutputInterface $output) 132 { 133 $container = $this->getContainer(); 134 135 $adminRouteProvider = $container 136 ->get('prestashop.bundle.routing.linter.admin_route_provider'); 137 /** @var SecurityAnnotationLinter $securityAnnotationLinter */ 138 $securityAnnotationLinter = $container 139 ->get('prestashop.bundle.routing.linter.security_annotation_linter'); 140 141 $listing = []; 142 143 foreach ($adminRouteProvider->getRoutes() as $routeName => $route) { 144 /* @var Route $route */ 145 try { 146 $annotation = $securityAnnotationLinter->getRouteSecurityAnnotation($routeName, $route); 147 $listing[] = [ 148 $route->getDefault('_controller'), 149 implode(', ', $route->getMethods()), 150 'Yes', 151 self::parseExpression($annotation->getExpression()), 152 ]; 153 } catch (LinterException $e) { 154 $listing[] = [ 155 $route->getDefault('_controller'), 156 implode(', ', $route->getMethods()), 157 'No', 158 '', 159 ]; 160 } 161 } 162 163 $io = new SymfonyStyle($input, $output); 164 $headers = ['Controller action', 'Methods', 'Is secured ?', 'Permissions']; 165 166 $io->table($headers, $listing); 167 } 168 169 /** 170 * @param InputInterface $input 171 * @param OutputInterface $output 172 */ 173 private function findRoutesWithMissingSecurityAnnotations(InputInterface $input, OutputInterface $output) 174 { 175 $container = $this->getContainer(); 176 177 $adminRouteProvider = $container 178 ->get('prestashop.bundle.routing.linter.admin_route_provider'); 179 /** @var SecurityAnnotationLinter $securityAnnotationLinter */ 180 $securityAnnotationLinter = $container 181 ->get('prestashop.bundle.routing.linter.security_annotation_linter'); 182 183 $notConfiguredRoutes = []; 184 185 /** @var Route $route */ 186 foreach ($adminRouteProvider->getRoutes() as $routeName => $route) { 187 try { 188 $securityAnnotationLinter->lint($routeName, $route); 189 } catch (LinterException $e) { 190 $notConfiguredRoutes[] = $routeName; 191 } 192 } 193 194 $io = new SymfonyStyle($input, $output); 195 196 if (!empty($notConfiguredRoutes)) { 197 $io->warning(sprintf( 198 '%s routes are not configured with @AdminSecurity annotation:', 199 count($notConfiguredRoutes) 200 )); 201 $io->listing($notConfiguredRoutes); 202 203 return; 204 } 205 206 $io->success('All admin routes are secured with @AdminSecurity.'); 207 } 208} 209