Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
lazka committed Dec 19, 2024
1 parent 26f9d51 commit ddfe051
Show file tree
Hide file tree
Showing 17 changed files with 213 additions and 163 deletions.
59 changes: 59 additions & 0 deletions src/Authorization/AuthorizationService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

declare(strict_types=1);

namespace Dbp\Relay\EsignBundle\Authorization;

use Dbp\Relay\CoreBundle\Authorization\AbstractAuthorizationService;
use Dbp\Relay\EsignBundle\Configuration\BundleConfig;
use Dbp\Relay\EsignBundle\DependencyInjection\Configuration;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

class AuthorizationService extends AbstractAuthorizationService
{
public function __construct(private readonly BundleConfig $config, private readonly AuthorizationCheckerInterface $authorizationChecker)
{
parent::__construct();
}

/**
* Throws if the user is not allowed to sign things.
*/
public function checkCanSign(): void
{
$this->denyAccessUnlessIsGrantedRole(Configuration::ROLE_SIGNER);
}

/**
* Throws if the current user doesn't have permissions to sign with the given profile.
*/
public function checkCanSignWithProfile(string $profileName): void
{
$profile = $this->config->getProfile($profileName);
if ($profile === null) {
// We don't want to leak which profiles exist
throw new AccessDeniedException();
}

// In case the legacy symfony role is specified it takes precedence over the new system
$legacyRole = $profile->getRole();
if ($legacyRole !== null) {
if ($this->authorizationChecker->isGranted($legacyRole)) {
return;
}
throw new AccessDeniedException();
}

$resource = new ProfileData($profile->getName());
$this->denyAccessUnlessIsGrantedResourcePermission(Configuration::ROLE_PROFILE_SIGNER, $resource);
}

/**
* Throws if the user is not allowed to verify signatures.
*/
public function checkCanVerify(): void
{
$this->denyAccessUnlessIsGrantedRole(Configuration::ROLE_VERIFIER);
}
}
17 changes: 17 additions & 0 deletions src/Authorization/ProfileData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace Dbp\Relay\EsignBundle\Authorization;

class ProfileData
{
public function __construct(private readonly string $name)
{
}

public function getName(): string
{
return $this->name;
}
}
20 changes: 20 additions & 0 deletions src/Configuration/BundleConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,26 @@ public function getProfiles(): array
return $profiles;
}

public function getProfile(string $name): ?Profile
{
$qualifiedConfig = $this->getQualified();
if ($qualifiedConfig !== null) {
$profile = $qualifiedConfig->getProfile($name);
if ($profile !== null) {
return $profile;
}
}
$advancedConfig = $this->getAdvanced();
if ($advancedConfig !== null) {
$profile = $advancedConfig->getProfile($name);
if ($profile !== null) {
return $profile;
}
}

return null;
}

public function getQualified(): ?QualifiedConfig
{
if (array_key_exists('qualified_signature', $this->config)) {
Expand Down
19 changes: 4 additions & 15 deletions src/Controller/CreateAdvancedlySignedDocumentAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Dbp\Relay\EsignBundle\Controller;

use Dbp\Relay\CoreBundle\Exception\ApiError;
use Dbp\Relay\EsignBundle\Authorization\AuthorizationService;
use Dbp\Relay\EsignBundle\Entity\AdvancedlySignedDocument;
use Dbp\Relay\EsignBundle\Helpers\Tools;
use Dbp\Relay\EsignBundle\Service\SignatureProviderInterface;
Expand All @@ -17,41 +18,29 @@
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

final class CreateAdvancedlySignedDocumentAction extends BaseSigningController
{
protected $api;

public function __construct(SignatureProviderInterface $api)
public function __construct(SignatureProviderInterface $api, private readonly AuthorizationService $authorizationService)
{
$this->api = $api;
}

public function checkProfilePermissions(string $profileName)
{
try {
$role = $this->api->getAdvancedlySignRequiredRole($profileName);
} catch (SigningException $e) {
throw new AccessDeniedException($e->getMessage());
}

$this->denyAccessUnlessGranted($role);
}

/**
* @throws HttpException
*/
public function __invoke(Request $request): AdvancedlySignedDocument
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
$this->authorizationService->checkCanSign();

$profileName = self::requestGet($request, 'profile');
if ($profileName === null) {
throw new BadRequestHttpException('Missing "profile"');
}

$this->checkProfilePermissions($profileName);
$this->authorizationService->checkCanSignWithProfile($profileName);

/** @var ?UploadedFile $uploadedFile */
$uploadedFile = $request->files->get('file');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
namespace Dbp\Relay\EsignBundle\Controller;

use Dbp\Relay\CoreBundle\Exception\ApiError;
use Dbp\Relay\EsignBundle\Authorization\AuthorizationService;
use Dbp\Relay\EsignBundle\Configuration\BundleConfig;
use Dbp\Relay\EsignBundle\Entity\ElectronicSignature;
use Dbp\Relay\EsignBundle\Entity\ElectronicSignatureVerificationReport;
use Dbp\Relay\EsignBundle\Helpers\Tools;
Expand All @@ -24,7 +26,7 @@ final class CreateElectronicSignatureVerificationReportAction extends AbstractCo
{
protected $api;

public function __construct(SignatureProviderInterface $api)
public function __construct(SignatureProviderInterface $api, private readonly AuthorizationService $authorizationService, private readonly BundleConfig $bundleConfig)
{
$this->api = $api;
}
Expand All @@ -36,8 +38,11 @@ public function __construct(SignatureProviderInterface $api)
*/
public function __invoke(Request $request): ElectronicSignatureVerificationReport
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
$this->denyAccessUnlessGranted('ROLE_SCOPE_VERIFY-SIGNATURE');
$this->authorizationService->checkCanVerify();

if (!$this->bundleConfig->hasVerification()) {
throw new SigningException('verification is not enabled');
}

/** @var ?UploadedFile $uploadedFile */
$uploadedFile = $request->files->get('file');
Expand Down
19 changes: 4 additions & 15 deletions src/Controller/CreateQualifiedSigningRequestAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Dbp\Relay\EsignBundle\Controller;

use Dbp\Relay\CoreBundle\Exception\ApiError;
use Dbp\Relay\EsignBundle\Authorization\AuthorizationService;
use Dbp\Relay\EsignBundle\Entity\QualifiedSigningRequest;
use Dbp\Relay\EsignBundle\Helpers\Tools;
use Dbp\Relay\EsignBundle\Service\SignatureProviderInterface;
Expand All @@ -17,42 +18,30 @@
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

final class CreateQualifiedSigningRequestAction extends BaseSigningController
{
protected $api;

public function __construct(SignatureProviderInterface $api)
public function __construct(SignatureProviderInterface $api, private readonly AuthorizationService $authorizationService)
{
$this->api = $api;
}

public function checkProfilePermissions(string $profileName)
{
try {
$role = $this->api->getQualifiedlySignRequiredRole($profileName);
} catch (SigningException $e) {
throw new AccessDeniedException($e->getMessage());
}

$this->denyAccessUnlessGranted($role);
}

/**
* @throws HttpException
*/
public function __invoke(Request $request): QualifiedSigningRequest
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
$this->authorizationService->checkCanSign();

$profileName = self::requestGet($request, 'profile');

if ($profileName === null) {
throw new BadRequestHttpException('Missing "profile"');
}

$this->checkProfilePermissions($profileName);
$this->authorizationService->checkCanSignWithProfile($profileName);

/** @var ?UploadedFile $uploadedFile */
$uploadedFile = $request->files->get('file');
Expand Down
18 changes: 18 additions & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@

namespace Dbp\Relay\EsignBundle\DependencyInjection;

use Dbp\Relay\CoreBundle\Authorization\AuthorizationConfigDefinition;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

class Configuration implements ConfigurationInterface
{
public const ROLE_SIGNER = 'ROLE_SIGNER';
public const ROLE_VERIFIER = 'ROLE_VERIFIER';
public const ROLE_PROFILE_SIGNER = 'ROLE_PROFILE_SIGNER';

private function getUserTextNode(): ArrayNodeDefinition
{
$builder = new ArrayNodeDefinition('user_text');
Expand Down Expand Up @@ -51,6 +57,15 @@ private function getUserTextNode(): ArrayNodeDefinition
return $builder;
}

private function getAuthNode(): NodeDefinition
{
return AuthorizationConfigDefinition::create()
->addRole(self::ROLE_SIGNER, 'user.isAuthenticated()', 'Returns true if the user is allowed to sign things in general.')
->addRole(self::ROLE_VERIFIER, 'false', 'Returns true if the user is allowed to verify signatures.')
->addResourcePermission(self::ROLE_PROFILE_SIGNER, 'false', 'Returns true if the user can sign with the given profile.')
->getNodeDefinition();
}

public function getConfigTreeBuilder(): TreeBuilder
{
$treeBuilder = new TreeBuilder('dbp_relay_esign');
Expand Down Expand Up @@ -85,6 +100,7 @@ public function getConfigTreeBuilder(): TreeBuilder
->scalarNode('role')
->info('The Symfony role required to use this profile')
->example('ROLE_FOOBAR')
->setDeprecated('dbp/relay-esign-bundle', '???', 'The "%node%" option is deprecated. Use the global authorization node instead.')
->end()
->scalarNode('profile_id')
->info('The PDF-AS signature profile ID to use')
Expand Down Expand Up @@ -115,6 +131,7 @@ public function getConfigTreeBuilder(): TreeBuilder
->scalarNode('role')
->info('The Symfony role required to use this profile')
->example('ROLE_FOOBAR')
->setDeprecated('dbp/relay-esign-bundle', '???', 'The "%node%" option is deprecated. Use the global authorization node instead.')
->end()
->scalarNode('key_id')
->info('The PDF-AS signature key ID used for singing')
Expand All @@ -132,6 +149,7 @@ public function getConfigTreeBuilder(): TreeBuilder
->end()
->end()
->end()
->append($this->getAuthNode())
->end()
;

Expand Down
4 changes: 4 additions & 0 deletions src/DependencyInjection/DbpRelayEsignExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Dbp\Relay\EsignBundle\DependencyInjection;

use Dbp\Relay\CoreBundle\Extension\ExtensionTrait;
use Dbp\Relay\EsignBundle\Authorization\AuthorizationService;
use Dbp\Relay\EsignBundle\Configuration\BundleConfig;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
Expand Down Expand Up @@ -55,5 +56,8 @@ public function loadInternal(array $mergedConfig, ContainerBuilder $container):

$definition = $container->getDefinition(BundleConfig::class);
$definition->addMethodCall('setConfig', [$mergedConfig]);

$definition = $container->getDefinition(AuthorizationService::class);
$definition->addMethodCall('setConfig', [$mergedConfig]);
}
}
18 changes: 9 additions & 9 deletions src/Resources/config/api_resources.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ resources:
operations:

ApiPlatform\Metadata\GetCollection:
provider: Dbp\Relay\EsignBundle\State\DummyProvider
provider: Dbp\Relay\EsignBundle\State\DummySignProvider
uriTemplate: "/advancedly-signed-documents"
openapiContext:
tags: ["Electronic Signatures"]

ApiPlatform\Metadata\Get:
provider: Dbp\Relay\EsignBundle\State\DummyProvider
provider: Dbp\Relay\EsignBundle\State\DummySignProvider
uriTemplate: "/advancedly-signed-documents/{identifier}"
openapiContext:
tags: ["Electronic Signatures"]
Expand Down Expand Up @@ -107,7 +107,7 @@ resources:
operations:

ApiPlatform\Metadata\GetCollection:
provider: Dbp\Relay\EsignBundle\State\DummyProvider
provider: Dbp\Relay\EsignBundle\State\DummySignProvider
uriTemplate: "/qualifiedly-signed-documents"
openapiContext:
tags: ["Electronic Signatures"]
Expand Down Expand Up @@ -164,13 +164,13 @@ resources:
operations:

ApiPlatform\Metadata\GetCollection:
provider: Dbp\Relay\EsignBundle\State\DummyProvider
provider: Dbp\Relay\EsignBundle\State\DummySignProvider
uriTemplate: "/qualified-signing-requests"
openapiContext:
tags: ["Electronic Signatures"]

ApiPlatform\Metadata\Get:
provider: Dbp\Relay\EsignBundle\State\DummyProvider
provider: Dbp\Relay\EsignBundle\State\DummySignProvider
uriTemplate: "/qualified-signing-requests/{identifier}"
openapiContext:
tags: ["Electronic Signatures"]
Expand Down Expand Up @@ -256,13 +256,13 @@ resources:
operations:

ApiPlatform\Metadata\GetCollection:
provider: Dbp\Relay\EsignBundle\State\DummyProvider
provider: Dbp\Relay\EsignBundle\State\DummyVerifyProvider
uriTemplate: "/electronic-signatures"
openapiContext:
tags: ["Electronic Signatures"]

ApiPlatform\Metadata\Get:
provider: Dbp\Relay\EsignBundle\State\DummyProvider
provider: Dbp\Relay\EsignBundle\State\DummyVerifyProvider
uriTemplate: "/electronic-signatures/{identifier}"
openapiContext:
tags: ["Electronic Signatures"]
Expand Down Expand Up @@ -299,13 +299,13 @@ resources:
operations:

ApiPlatform\Metadata\GetCollection:
provider: Dbp\Relay\EsignBundle\State\DummyProvider
provider: Dbp\Relay\EsignBundle\State\DummyVerifyProvider
uriTemplate: "/electronic-signature-verification-reports"
openapiContext:
tags: ["Electronic Signatures"]

ApiPlatform\Metadata\Get:
provider: Dbp\Relay\EsignBundle\State\DummyProvider
provider: Dbp\Relay\EsignBundle\State\DummyVerifyProvider
uriTemplate: "/electronic-signature-verification-reports/{identifier}"
openapiContext:
tags: ["Electronic Signatures"]
Expand Down
Loading

0 comments on commit ddfe051

Please sign in to comment.