Add ACL support for the API #194

This commit is contained in:
Korina Cordero 2019-03-21 14:28:18 +00:00
parent eeffdb981a
commit 53332b989a
10 changed files with 350 additions and 1 deletions

View 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
];
}
}

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

View 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');
}
}

View file

@ -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
View 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

View file

@ -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"

View file

@ -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;

View file

@ -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 = [];

View file

@ -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 = [];

View file

@ -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']);