Commit 057c1b5b authored by hlarget's avatar hlarget 👹
Browse files

Merge branch 'release/3.0.0'

parents 9152379b cbebf9ec
<?php
namespace Aboutgoods\KonnectBundle;
use AboutGoods\Antar\Client;
use AboutGoods\Antar\Models\Company;
use AboutGoods\Antar\Models\Feature;
class AntarClient
{
/**
* @var Client
*/
private $antar;
public function __construct(Configuration $configuration)
{
$configurationList = $configuration->getConfiguration(Configuration::ANTAR);
$this->antar = new Client($configurationList["antarUrl"], $configurationList["hydraUrl"], $configurationList["token"], $configurationList["secret"]);
}
public function __invoke($impersonate = null): Client
{
if (null !== $impersonate) {
return $this->antar->impersonate($impersonate);
}
return $this->antar;
}
public function getFeature($companyUid, $featureName): ?Feature
{
//$this() for the invoke method.
$company = $this()->companies->get($companyUid);
if ($company->getLevel() == Company::SECURITY_LEVEL_ADMIN) {
return new Feature($featureName, -1);
}
foreach ($company->getFeatures() as $feature) {
if ($feature->getName() == $featureName) {
return $feature;
}
}
return null;
}
public function isGrantedAndHasEnoughQuota($companyUid, $featureName)
{
$feature = $this->getFeature($companyUid, $featureName);
if (null !== $feature) {
return $feature->getQuota() !== 0;
}
return false;
}
}
\ No newline at end of file
......@@ -13,17 +13,24 @@ class Configuration
{
const AUTHENTICATION = "authentication";
const IMGPROXY = "imgproxy";
const ANTAR = "antar";
private $configuration = [
self::AUTHENTICATION => [
"token" => "token",
"secret" => "secret",
"url" => null
"url" => null,
],
self::IMGPROXY => [
self::IMGPROXY => [
"key" => "key",
"salt" => "salt",
"url" => null
"url" => null,
],
self::ANTAR => [
"antarUrl" => "https://receipt.kweeri.io/",
"hydraUrl" => "https://security.agkit.io/",
"token" => "aboutgoods",
"secret" => null,
],
];
......@@ -38,9 +45,9 @@ class Configuration
*/
public function setConfiguration($config)
{
// Recursive replace with the config passed as parameter
// Replace missing values with the default configuration on top of the class
foreach ($this->configuration as $key => $value) {
if(is_array($value)) {
if (is_array($value)) {
foreach ($value as $subKey => $subValue) {
if (isset($config[$key][$subKey])) {
$this->configuration[$key][$subKey] = $config[$key][$subKey];
......
......@@ -33,21 +33,4 @@ class UserController extends AbstractController
{
return $this->redirectToRoute('auth0_logout');
}
/**
* @Route("/user/services")
* List all user services from the current connected user.
*
* @param KonnectClient $konnectClient
*
* @return Response
*/
public function getUserServices(KonnectClient $konnectClient)
{
$client = $konnectClient->getGuzzleClient();
$userId = $this->getUser()->getId();
$response = $client->get("api/user/$userId/services");
return new Response($response->getBody()->getContents(), Response::HTTP_OK, ['Content-Type' => 'application/json']);
}
}
\ No newline at end of file
......@@ -7,7 +7,7 @@ use Aboutgoods\KonnectBundle\Configuration as Config;
/**
* This is the class that validates and merges configuration from your app/config files.
*
*
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/configuration.html}
*/
class Configuration implements ConfigurationInterface
......@@ -28,6 +28,14 @@ class Configuration implements ConfigurationInterface
->scalarNode('url')->defaultValue(null)->end()
->end()
->end()
->arrayNode(Config::ANTAR)
->children()
->scalarNode('antarUrl')->defaultValue(null)->end()
->scalarNode('hydraUrl')->defaultValue(null)->end()
->scalarNode('token')->end()
->scalarNode('secret')->end()
->end()
->end()
->arrayNode(Config::IMGPROXY)
->children()
->scalarNode('key')->defaultValue("changeMe")->end()
......
......@@ -4,6 +4,13 @@ services:
public: true
arguments:
$configuration: '@Aboutgoods\KonnectBundle\Configuration'
antar.client:
class: Aboutgoods\KonnectBundle\AntarClient
public: true
arguments:
$configuration: '@Aboutgoods\KonnectBundle\Configuration'
Aboutgoods\KonnectBundle\AntarClient:
alias: antar.client
Aboutgoods\KonnectBundle\KonnectClient:
alias: konnect.client
imgproxy.client:
......@@ -51,3 +58,9 @@ services:
class: Aboutgoods\KonnectBundle\Security\Provider\UserProvider
Aboutgoods\KonnectBundle\Security\Provider\UserProvider:
alias: konnect.user.provider
konnect.voter:
class: Aboutgoods\KonnectBundle\Security\AntarVoter
arguments: ['@Aboutgoods\KonnectBundle\AntarClient']
tags: ['security.voter']
Aboutgoods\KonnectBundle\Security\AntarVoter:
alias: konnect.voter
\ No newline at end of file
<?php
namespace Aboutgoods\KonnectBundle\Security;
use AboutGoods\Antar\Models\Feature;
use Aboutgoods\KonnectBundle\AntarClient;
use Aboutgoods\KonnectBundle\Model\User;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class AntarVoter extends Voter
{
private $features_available = [
Feature::FEATURE_CSV_EXPORT,
Feature::FEATURE_QUALITY_CHECK,
Feature::FEATURE_PROMOTIONS,
Feature::FEATURE_RECEIPT,
Feature::FEATURE_SHERLOCK,
];
/**
* @var AntarClient
*/
private $antarClient;
/**
* AntarVoter constructor.
* @param AntarClient $antarClient
*/
public function __construct(AntarClient $antarClient)
{
$this->antarClient = $antarClient;
}
protected function supports($attribute, $subject)
{
// allow FEATURE_NAME and FEATURE_NAME.QUOTA with this arraymap
return in_array($attribute, $this->features_available)
|| in_array($attribute, array_map(function ($tag) {
return $tag . ".QUOTA";
}, $this->features_available));
}
/*
* This function is called (if the support method is supports it) at any isGranted or denyUnlessGranted call.
* It will check if the user has the feature in Antar passed as parameter.
* If the feature name is suffixed with .QUOTA, it will also check if there is remaining quota. (aka: not 0 quota)
*/
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
$user = $token->getUser();
if (!($user instanceof User)) {
return false;
}
$keys = explode(".", $attribute);
if (isset($keys[1]) && $keys[1] === "QUOTA") {
return $this->antarClient->isGrantedAndHasEnoughQuota($user->getCompanyId(), $keys[0]);
}
//Explode without dot is always set on key 0 so I could have used $attribute as well.
return $this->antarClient->getFeature($user->getCompanyId(), $keys[0]) !== null;
}
}
\ No newline at end of file
......@@ -5,5 +5,14 @@
"psr-4": {
"Aboutgoods\\KonnectBundle\\": ""
}
},
"repositories": [
{
"type": "vcs",
"url": "https://gitlab.agoods.fr/delivery/public/open-source/antar-client"
}
],
"require": {
"aboutgoods/antar": "^1.2"
}
}
This diff is collapsed.
......@@ -131,6 +131,38 @@ security:
After this you should connect to your application and call the routes in this bundle to call Konnect.
You can find RAML documentation [on dev-documentation.](https://doc.agoods.fr/v2/api/konnectBundle.html).
## Permissions and voter
Voters has been added to the project :
Just use them as classic voters. Available voter roles :
```
CSV_EXPORT => Useful for checking if the feature CSV Export is enabled
PROMOTIONS => Check if can manage campaigns
QC => Check if plan contains Quality Check
RECEIPT => Basic platform Access
SHERLOCK => Check if anomaly detection should be shown
```
Same as previous voters, but with quota not at 0 check
```
CSV_EXPORT.QUOTA
PROMOTIONS.QUOTA
QC.QUOTA
RECEIPT.QUOTA
SHERLOCK.QUOTA
```
For example, before calling campaigns list, you can add at the top of the controller:
```php
$this->denyUnlessGranted("PROMOTION");
```
Or if we add quota to export
```php
if(!$this->isGranted("CSV_EXPORT.QUOTA")){
// redirect to plans/refill page
}
```
More information about how to use voters on [symfony documentation](https://symfony.com/doc/current/security/voters.html#setup-checking-for-access-in-a-controller).
### img-proxy
The route `/konnect/resource` is available to serve resources from our own img-proxy server (uri set with the `IMGPROXY_URI` env key).
......@@ -162,4 +194,5 @@ You can find [a detailed documentation here](https://gitlab.agoods.fr/documentat
##### You need to add a file to use it on modules ?
- Put your files on a folder
- Use it on the module by specifying the Konnect-Bundle namespace
\ No newline at end of file
- Use it on the module by specifying the Konnect-Bundle namespace
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment