Add ACL support for the API #194
This commit is contained in:
parent
eeffdb981a
commit
53332b989a
10 changed files with 350 additions and 1 deletions
111
catalyst/api-bundle/Access/Generator.php
Normal file
111
catalyst/api-bundle/Access/Generator.php
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
namespace Catalyst\APIBundle\Access;
|
||||
|
||||
use Symfony\Component\Routing\Exception\RouteNotFoundException;
|
||||
use Symfony\Component\Yaml\Parser as YamlParser;
|
||||
use Symfony\Component\Config\ConfigCache;
|
||||
use Symfony\Component\Config\Resource\FileResource;
|
||||
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
|
||||
class Generator
|
||||
{
|
||||
// TODO: make api_acl and acl yaml generator have its own bundle
|
||||
protected $router;
|
||||
protected $cache_dir;
|
||||
protected $config_dir;
|
||||
|
||||
public function __construct(RouterInterface $router, string $cache_dir, string $config_dir)
|
||||
{
|
||||
$this->router = $router;
|
||||
$this->cache_dir = $cache_dir;
|
||||
$this->config_dir = $config_dir;
|
||||
}
|
||||
|
||||
public function getACL()
|
||||
{
|
||||
$key = 'api_access_keys';
|
||||
|
||||
// cache config
|
||||
$cache_file = $this->cache_dir . '/' . $key . '.serial';
|
||||
$cache = new ConfigCache($cache_file, true);
|
||||
|
||||
// check if cache is fresh
|
||||
if (!$cache->isFresh())
|
||||
{
|
||||
$files = [];
|
||||
$resources = [];
|
||||
|
||||
try
|
||||
{
|
||||
// get location of api_acl.yaml
|
||||
$path = $this->config_dir . '/api_acl.yaml';
|
||||
|
||||
$files[] = $path;
|
||||
$resources[] = new FileResource($path);
|
||||
|
||||
// process api acl config file
|
||||
$data = $this->parseACL($path, $key);
|
||||
}
|
||||
catch (\InvalidArgumentException $e)
|
||||
{
|
||||
error_log($e->getMessage());
|
||||
error_log($key . ' key not found in api_acl.yaml file.');
|
||||
return $data;
|
||||
}
|
||||
|
||||
$acl_serial = serialize($data);
|
||||
$cache->write($acl_serial, $resources);
|
||||
}
|
||||
else
|
||||
{
|
||||
$acl_serial = file_get_contents($cache_file);
|
||||
$data = unserialize($acl_serial);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function parseACL($path, $key)
|
||||
{
|
||||
|
||||
$parser = new YamlParser();
|
||||
$config = $parser->parse(file_get_contents($path));
|
||||
|
||||
// check if we have access keys
|
||||
if (!isset($config[$key]))
|
||||
{
|
||||
error_log('No ' . $key . ' found for ' . $path);
|
||||
return;
|
||||
}
|
||||
|
||||
$acl_hierarchy = [];
|
||||
$acl_index = [];
|
||||
|
||||
// go through each one
|
||||
foreach ($config[$key] as $acl_data)
|
||||
{
|
||||
// build hierarchy
|
||||
$acl_hierarchy[$acl_data['id']] = [
|
||||
'label' => $acl_data['label'],
|
||||
'acls' => []
|
||||
];
|
||||
|
||||
foreach ($acl_data['acls'] as $acl)
|
||||
{
|
||||
$id = $acl['id'];
|
||||
$label = $acl['label'];
|
||||
|
||||
// set hierarchy and index
|
||||
$acl_hierarchy[$acl_data['id']]['acls'][$id] = $label;
|
||||
$acl_index[$id] = $label;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'hierarchy' => $acl_hierarchy,
|
||||
'index' => $acl_index
|
||||
];
|
||||
}
|
||||
}
|
||||
45
catalyst/api-bundle/Access/Voter.php
Normal file
45
catalyst/api-bundle/Access/Voter.php
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Catalyst\APIBundle\Access;
|
||||
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter as BaseVoter;
|
||||
|
||||
class Voter extends BaseVoter
|
||||
{
|
||||
protected $acl_gen;
|
||||
|
||||
public function __construct(Generator $acl_gen)
|
||||
{
|
||||
$this->acl_gen = $acl_gen;
|
||||
}
|
||||
|
||||
protected function supports($attribute, $subject)
|
||||
{
|
||||
$acl_data = $this->acl_gen->getACL();
|
||||
|
||||
// check if the attribute is in our acl key index
|
||||
if (isset($acl_data['index'][$attribute]))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
|
||||
{
|
||||
$user = $token->getUser();
|
||||
|
||||
// check if any of the user's roles have access
|
||||
$roles = $user->getRoleObjects();
|
||||
|
||||
foreach ($roles as $role)
|
||||
{
|
||||
// NOTE: ideally, we separate acl from the role object, but this will do for now
|
||||
if ($role->hasACLAccess($attribute))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
88
catalyst/api-bundle/Command/TestAPICommand.php
Normal file
88
catalyst/api-bundle/Command/TestAPICommand.php
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
namespace Catalyst\APIBundle\Command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
|
||||
|
||||
use Catalyst\APIBundle\Connector\Client as APIClient;
|
||||
|
||||
|
||||
class TestAPICommand extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('api:test-connector-all')
|
||||
->setDescription('Test API connector with all commands.')
|
||||
->setHelp('Test API Connector with all commands.')
|
||||
->addArgument('protocol', InputArgument::REQUIRED, 'protocol')
|
||||
->addArgument('server', InputArgument::REQUIRED, 'server')
|
||||
->addArgument('api_key', InputArgument::REQUIRED, 'api_key')
|
||||
->addArgument('secret_key', InputArgument::REQUIRED, 'secret_key');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$protocol = $input->getArgument('protocol');
|
||||
$server = $input->getArgument('server');
|
||||
$api_key = $input->getArgument('api_key');
|
||||
$secret_key = $input->getArgument('secret_key');
|
||||
|
||||
// api client
|
||||
$api = new APIClient($server, $api_key, $secret_key);
|
||||
$api->setProtocol($protocol);
|
||||
|
||||
// test
|
||||
$api->get('/capi/test');
|
||||
|
||||
// TODO: shift this out of the bundle, since it's project specific
|
||||
|
||||
|
||||
// warranty register
|
||||
$serial = 'AJ34LJADR12134LKJL5';
|
||||
$plate_num = 'XEN918';
|
||||
$params = [
|
||||
'serial' => $serial,
|
||||
'plate_number' => $plate_num,
|
||||
'warranty_class' => 'private',
|
||||
'sku' => 'WMEB24CB-CPN00-LX',
|
||||
'date_purchase' => '20181001',
|
||||
'date_expire' => '20191001',
|
||||
'first_name' => 'First',
|
||||
'last_name' => 'Last',
|
||||
'mobile_number' => '12345678910',
|
||||
];
|
||||
$api->post('/capi/warranties', $params);
|
||||
|
||||
// get all warranties
|
||||
$api->get('/capi/warranties');
|
||||
|
||||
|
||||
// warranty find
|
||||
$api->get('/capi/warranties/' . $serial);
|
||||
|
||||
// warranty claim
|
||||
$id = 86811;
|
||||
$serial = 'AJ34LJADR12134LKJL5';
|
||||
$params = [
|
||||
'serial' => $serial,
|
||||
];
|
||||
$api->post('/capi/warranties/' . $id . '/claim', $params);
|
||||
|
||||
// plate warranty
|
||||
$api->get('/capi/plates/' . $plate_num . '/warranties');
|
||||
|
||||
// battery
|
||||
$api->get('/capi/battery_brands');
|
||||
$api->get('/capi/battery_sizes');
|
||||
$api->get('/capi/batteries');
|
||||
|
||||
// vehicle
|
||||
$api->get('/capi/vehicle_manufacturers');
|
||||
$api->get('/capi/vehicles');
|
||||
}
|
||||
}
|
||||
|
|
@ -106,6 +106,11 @@ class User implements UserInterface
|
|||
return $str_roles;
|
||||
}
|
||||
|
||||
public function getRoleObjects()
|
||||
{
|
||||
return $this->roles;
|
||||
}
|
||||
|
||||
public function getDateCreate()
|
||||
{
|
||||
return $this->date_create;
|
||||
|
|
|
|||
39
config/api_acl.yaml
Normal file
39
config/api_acl.yaml
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
api_access_keys:
|
||||
- id: warranty
|
||||
label: Warranty Access
|
||||
acls:
|
||||
- id: warranty.list
|
||||
label: List
|
||||
- id: warranty.find.serial
|
||||
label: Find by Serial
|
||||
- id: warranty.find.platenumber
|
||||
label: Find by Plate Number
|
||||
- id: warranty.register.battery
|
||||
label: Register Battery
|
||||
- id: warranty.claim
|
||||
label: Claim
|
||||
- id: batterybrand
|
||||
label: Battery Brand Access
|
||||
acls:
|
||||
- id: batterybrand.list
|
||||
label: List
|
||||
- id: batterysize
|
||||
label: Battery Size Access
|
||||
acls:
|
||||
- id: batterysize.list
|
||||
label: List
|
||||
- id: battery
|
||||
label: Battery Access
|
||||
acls:
|
||||
- id: battery.list
|
||||
label: List
|
||||
- id: vmanufacturer
|
||||
label: Vehicle Manufacturer Access
|
||||
acls:
|
||||
- id: vmanufacturer.list
|
||||
label: List
|
||||
- id: vehicle
|
||||
label: Vehicle Access
|
||||
acls:
|
||||
- id: vehicle.list
|
||||
label: List
|
||||
|
|
@ -89,5 +89,19 @@ services:
|
|||
tags: ['console.command']
|
||||
|
||||
Catalyst\APIBundle\Command\TestCommand:
|
||||
tags: ['console.command']
|
||||
tags: ['console.command']
|
||||
|
||||
Catalyst\APIBundle\Access\Voter:
|
||||
arguments:
|
||||
$acl_gen: "@Catalyst\\APIBundle\\Access\\Generator"
|
||||
tags: ['security.voter']
|
||||
|
||||
Catalyst\APIBundle\Command\TestAPICommand:
|
||||
tags: ['console.command']
|
||||
|
||||
Catalyst\APIBundle\Access\Generator:
|
||||
arguments:
|
||||
$router: "@router.default"
|
||||
$cache_dir: "%kernel.cache_dir%"
|
||||
$config_dir: "%kernel.root_dir%/../config"
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use Symfony\Component\Routing\RouterInterface;
|
|||
|
||||
class Generator
|
||||
{
|
||||
// TODO: make api_acl and acl yaml generator have its own bundle
|
||||
protected $router;
|
||||
protected $cache_dir;
|
||||
protected $config_dir;
|
||||
|
|
|
|||
|
|
@ -13,10 +13,21 @@ use App\Entity\SAPBattery;
|
|||
use App\Entity\SAPBatterySize;
|
||||
use App\Entity\SAPBatteryBrand;
|
||||
|
||||
use Catalyst\APIBundle\Access\Generator as ACLGenerator;
|
||||
|
||||
class BatteryController extends APIController
|
||||
{
|
||||
protected $acl_gen;
|
||||
|
||||
public function __construct(ACLGenerator $acl_gen)
|
||||
{
|
||||
$this->acl_gen = $acl_gen;
|
||||
}
|
||||
|
||||
public function getBatteries(EntityManagerInterface $em)
|
||||
{
|
||||
$this->denyAccessUnlessGranted('battery.list', null, 'No access.');
|
||||
|
||||
$batteries = $em->getRepository(SAPBattery::class)->findBy([], ['id' => 'ASC']);
|
||||
|
||||
$result = [];
|
||||
|
|
@ -38,6 +49,7 @@ class BatteryController extends APIController
|
|||
|
||||
public function getBrands(EntityManagerInterface $em)
|
||||
{
|
||||
$this->denyAccessUnlessGranted('batterybrand.list', null, 'No access.');
|
||||
$brands = $em->getRepository(SAPBatteryBrand::class)->findBy([], ['name' => 'ASC']);
|
||||
|
||||
$result = [];
|
||||
|
|
@ -58,6 +70,8 @@ class BatteryController extends APIController
|
|||
|
||||
public function getSizes(EntityManagerInterface $em)
|
||||
{
|
||||
$this->denyAccessUnlessGranted('batterysize.list', null, 'No access.');
|
||||
|
||||
$sizes = $em->getRepository(SAPBatterySize::class)->findBy([], ['name' => 'ASC']);
|
||||
|
||||
$result = [];
|
||||
|
|
|
|||
|
|
@ -11,10 +11,21 @@ use Catalyst\APIBundle\Response\APIResponse;
|
|||
use App\Entity\Vehicle;
|
||||
use App\Entity\VehicleManufacturer;
|
||||
|
||||
use Catalyst\APIBundle\Access\Generator as ACLGenerator;
|
||||
|
||||
class VehicleController extends APIController
|
||||
{
|
||||
protected $acl_gen;
|
||||
|
||||
public function __construct(ACLGenerator $acl_gen)
|
||||
{
|
||||
$this->acl_gen = $acl_gen;
|
||||
}
|
||||
|
||||
public function getManufacturers(EntityManagerInterface $em)
|
||||
{
|
||||
$this->denyAccessUnlessGranted('vmanufacturer.list', null, 'No access.');
|
||||
|
||||
$mfgs = $em->getRepository(VehicleManufacturer::class)->findBy([], ['name' => 'ASC']);
|
||||
|
||||
$result = [];
|
||||
|
|
@ -35,6 +46,8 @@ class VehicleController extends APIController
|
|||
|
||||
public function list(EntityManagerInterface $em)
|
||||
{
|
||||
$this->denyAccessUnlessGranted('vehicle.list', null, 'No access.');
|
||||
|
||||
$vehicles = $em->getRepository(Vehicle::class)->findBy([], ['manufacturer' => 'ASC', 'make' => 'ASC']);
|
||||
|
||||
$result = [];
|
||||
|
|
|
|||
|
|
@ -22,8 +22,17 @@ use App\Ramcar\WarrantyClass;
|
|||
use App\Ramcar\WarrantyStatus;
|
||||
use DateTime;
|
||||
|
||||
use Catalyst\APIBundle\Access\Generator as ACLGenerator;
|
||||
|
||||
class WarrantyController extends APIController
|
||||
{
|
||||
protected $acl_gen;
|
||||
|
||||
public function __construct(ACLGenerator $acl_gen)
|
||||
{
|
||||
$this->acl_gen = $acl_gen;
|
||||
}
|
||||
|
||||
protected function cleanSerial($serial)
|
||||
{
|
||||
return trim(strtoupper($serial));
|
||||
|
|
@ -65,6 +74,8 @@ class WarrantyController extends APIController
|
|||
|
||||
public function find($serial, EntityManagerInterface $em)
|
||||
{
|
||||
$this->denyAccessUnlessGranted('warranty.find.serial', null, 'No access.');
|
||||
|
||||
$clean_serial = $this->cleanSerial($serial);
|
||||
$warr = $em->getRepository(Warranty::class)->findOneBy(['serial' => $clean_serial]);
|
||||
|
||||
|
|
@ -81,6 +92,8 @@ class WarrantyController extends APIController
|
|||
|
||||
public function getAll(Request $req, EntityManagerInterface $em)
|
||||
{
|
||||
$this->denyAccessUnlessGranted('warranty.list', null, 'No access.');
|
||||
|
||||
$order = $req->query->get('order');
|
||||
if ($order == null)
|
||||
$order = 'ASC';
|
||||
|
|
@ -117,6 +130,8 @@ class WarrantyController extends APIController
|
|||
|
||||
public function register(Request $req, EntityManagerInterface $em)
|
||||
{
|
||||
$this->denyAccessUnlessGranted('warranty.register.battery', null, 'No access.');
|
||||
|
||||
// required parameters
|
||||
$params = [
|
||||
'serial',
|
||||
|
|
@ -220,6 +235,8 @@ class WarrantyController extends APIController
|
|||
|
||||
public function claim(Request $req, EntityManagerInterface $em, $id)
|
||||
{
|
||||
$this->denyAccessUnlessGranted('warranty.claim', null, 'No access.');
|
||||
|
||||
// required parameters
|
||||
$params = [
|
||||
'serial',
|
||||
|
|
@ -275,6 +292,8 @@ class WarrantyController extends APIController
|
|||
|
||||
public function getPlateWarranties($plate_number, EntityManagerInterface $em)
|
||||
{
|
||||
$this->denyAccessUnlessGranted('warranty.find.platenumber', null, 'No access.');
|
||||
|
||||
$warranties = $em->getRepository(Warranty::class)
|
||||
->findBy(['plate_number' => $plate_number], ['date_purchase' => 'DESC']);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue