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;
|
return $str_roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getRoleObjects()
|
||||||
|
{
|
||||||
|
return $this->roles;
|
||||||
|
}
|
||||||
|
|
||||||
public function getDateCreate()
|
public function getDateCreate()
|
||||||
{
|
{
|
||||||
return $this->date_create;
|
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']
|
tags: ['console.command']
|
||||||
|
|
||||||
Catalyst\APIBundle\Command\TestCommand:
|
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
|
class Generator
|
||||||
{
|
{
|
||||||
|
// TODO: make api_acl and acl yaml generator have its own bundle
|
||||||
protected $router;
|
protected $router;
|
||||||
protected $cache_dir;
|
protected $cache_dir;
|
||||||
protected $config_dir;
|
protected $config_dir;
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,21 @@ use App\Entity\SAPBattery;
|
||||||
use App\Entity\SAPBatterySize;
|
use App\Entity\SAPBatterySize;
|
||||||
use App\Entity\SAPBatteryBrand;
|
use App\Entity\SAPBatteryBrand;
|
||||||
|
|
||||||
|
use Catalyst\APIBundle\Access\Generator as ACLGenerator;
|
||||||
|
|
||||||
class BatteryController extends APIController
|
class BatteryController extends APIController
|
||||||
{
|
{
|
||||||
|
protected $acl_gen;
|
||||||
|
|
||||||
|
public function __construct(ACLGenerator $acl_gen)
|
||||||
|
{
|
||||||
|
$this->acl_gen = $acl_gen;
|
||||||
|
}
|
||||||
|
|
||||||
public function getBatteries(EntityManagerInterface $em)
|
public function getBatteries(EntityManagerInterface $em)
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('battery.list', null, 'No access.');
|
||||||
|
|
||||||
$batteries = $em->getRepository(SAPBattery::class)->findBy([], ['id' => 'ASC']);
|
$batteries = $em->getRepository(SAPBattery::class)->findBy([], ['id' => 'ASC']);
|
||||||
|
|
||||||
$result = [];
|
$result = [];
|
||||||
|
|
@ -38,6 +49,7 @@ class BatteryController extends APIController
|
||||||
|
|
||||||
public function getBrands(EntityManagerInterface $em)
|
public function getBrands(EntityManagerInterface $em)
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('batterybrand.list', null, 'No access.');
|
||||||
$brands = $em->getRepository(SAPBatteryBrand::class)->findBy([], ['name' => 'ASC']);
|
$brands = $em->getRepository(SAPBatteryBrand::class)->findBy([], ['name' => 'ASC']);
|
||||||
|
|
||||||
$result = [];
|
$result = [];
|
||||||
|
|
@ -58,6 +70,8 @@ class BatteryController extends APIController
|
||||||
|
|
||||||
public function getSizes(EntityManagerInterface $em)
|
public function getSizes(EntityManagerInterface $em)
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('batterysize.list', null, 'No access.');
|
||||||
|
|
||||||
$sizes = $em->getRepository(SAPBatterySize::class)->findBy([], ['name' => 'ASC']);
|
$sizes = $em->getRepository(SAPBatterySize::class)->findBy([], ['name' => 'ASC']);
|
||||||
|
|
||||||
$result = [];
|
$result = [];
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,21 @@ use Catalyst\APIBundle\Response\APIResponse;
|
||||||
use App\Entity\Vehicle;
|
use App\Entity\Vehicle;
|
||||||
use App\Entity\VehicleManufacturer;
|
use App\Entity\VehicleManufacturer;
|
||||||
|
|
||||||
|
use Catalyst\APIBundle\Access\Generator as ACLGenerator;
|
||||||
|
|
||||||
class VehicleController extends APIController
|
class VehicleController extends APIController
|
||||||
{
|
{
|
||||||
|
protected $acl_gen;
|
||||||
|
|
||||||
|
public function __construct(ACLGenerator $acl_gen)
|
||||||
|
{
|
||||||
|
$this->acl_gen = $acl_gen;
|
||||||
|
}
|
||||||
|
|
||||||
public function getManufacturers(EntityManagerInterface $em)
|
public function getManufacturers(EntityManagerInterface $em)
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('vmanufacturer.list', null, 'No access.');
|
||||||
|
|
||||||
$mfgs = $em->getRepository(VehicleManufacturer::class)->findBy([], ['name' => 'ASC']);
|
$mfgs = $em->getRepository(VehicleManufacturer::class)->findBy([], ['name' => 'ASC']);
|
||||||
|
|
||||||
$result = [];
|
$result = [];
|
||||||
|
|
@ -35,6 +46,8 @@ class VehicleController extends APIController
|
||||||
|
|
||||||
public function list(EntityManagerInterface $em)
|
public function list(EntityManagerInterface $em)
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('vehicle.list', null, 'No access.');
|
||||||
|
|
||||||
$vehicles = $em->getRepository(Vehicle::class)->findBy([], ['manufacturer' => 'ASC', 'make' => 'ASC']);
|
$vehicles = $em->getRepository(Vehicle::class)->findBy([], ['manufacturer' => 'ASC', 'make' => 'ASC']);
|
||||||
|
|
||||||
$result = [];
|
$result = [];
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,17 @@ use App\Ramcar\WarrantyClass;
|
||||||
use App\Ramcar\WarrantyStatus;
|
use App\Ramcar\WarrantyStatus;
|
||||||
use DateTime;
|
use DateTime;
|
||||||
|
|
||||||
|
use Catalyst\APIBundle\Access\Generator as ACLGenerator;
|
||||||
|
|
||||||
class WarrantyController extends APIController
|
class WarrantyController extends APIController
|
||||||
{
|
{
|
||||||
|
protected $acl_gen;
|
||||||
|
|
||||||
|
public function __construct(ACLGenerator $acl_gen)
|
||||||
|
{
|
||||||
|
$this->acl_gen = $acl_gen;
|
||||||
|
}
|
||||||
|
|
||||||
protected function cleanSerial($serial)
|
protected function cleanSerial($serial)
|
||||||
{
|
{
|
||||||
return trim(strtoupper($serial));
|
return trim(strtoupper($serial));
|
||||||
|
|
@ -65,6 +74,8 @@ class WarrantyController extends APIController
|
||||||
|
|
||||||
public function find($serial, EntityManagerInterface $em)
|
public function find($serial, EntityManagerInterface $em)
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('warranty.find.serial', null, 'No access.');
|
||||||
|
|
||||||
$clean_serial = $this->cleanSerial($serial);
|
$clean_serial = $this->cleanSerial($serial);
|
||||||
$warr = $em->getRepository(Warranty::class)->findOneBy(['serial' => $clean_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)
|
public function getAll(Request $req, EntityManagerInterface $em)
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('warranty.list', null, 'No access.');
|
||||||
|
|
||||||
$order = $req->query->get('order');
|
$order = $req->query->get('order');
|
||||||
if ($order == null)
|
if ($order == null)
|
||||||
$order = 'ASC';
|
$order = 'ASC';
|
||||||
|
|
@ -117,6 +130,8 @@ class WarrantyController extends APIController
|
||||||
|
|
||||||
public function register(Request $req, EntityManagerInterface $em)
|
public function register(Request $req, EntityManagerInterface $em)
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('warranty.register.battery', null, 'No access.');
|
||||||
|
|
||||||
// required parameters
|
// required parameters
|
||||||
$params = [
|
$params = [
|
||||||
'serial',
|
'serial',
|
||||||
|
|
@ -220,6 +235,8 @@ class WarrantyController extends APIController
|
||||||
|
|
||||||
public function claim(Request $req, EntityManagerInterface $em, $id)
|
public function claim(Request $req, EntityManagerInterface $em, $id)
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('warranty.claim', null, 'No access.');
|
||||||
|
|
||||||
// required parameters
|
// required parameters
|
||||||
$params = [
|
$params = [
|
||||||
'serial',
|
'serial',
|
||||||
|
|
@ -275,6 +292,8 @@ class WarrantyController extends APIController
|
||||||
|
|
||||||
public function getPlateWarranties($plate_number, EntityManagerInterface $em)
|
public function getPlateWarranties($plate_number, EntityManagerInterface $em)
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('warranty.find.platenumber', null, 'No access.');
|
||||||
|
|
||||||
$warranties = $em->getRepository(Warranty::class)
|
$warranties = $em->getRepository(Warranty::class)
|
||||||
->findBy(['plate_number' => $plate_number], ['date_purchase' => 'DESC']);
|
->findBy(['plate_number' => $plate_number], ['date_purchase' => 'DESC']);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue