Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion config/services/compat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ services:
class: EngineBlock_Corto_Model_Consent_Factory
arguments:
- "@engineblock.compat.corto_filter_command_factory"
- "@engineblock.compat.database_connection_factory"
- "@engineblock.service.consent.ConsentHashService"

engineblock.compat.saml2_id_generator:
public: true
Expand Down
2 changes: 1 addition & 1 deletion config/services/controllers/api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ services:
- '@security.token_storage'
- '@security.access.decision_manager'
- '@OpenConext\EngineBlockBundle\Configuration\FeatureConfiguration'
- '@OpenConext\EngineBlock\Service\ConsentService'
- '@OpenConext\EngineBlock\Service\Consent\ConsentService'

OpenConext\EngineBlockBundle\Controller\Api\DeprovisionController:
arguments:
Expand Down
8 changes: 7 additions & 1 deletion config/services/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,13 @@ services:
- '@OpenConext\EngineBlock\Metadata\LoaRepository'
- '@logger'

OpenConext\EngineBlock\Service\ConsentService:
engineblock.service.consent.ConsentHashService:
class: OpenConext\EngineBlock\Service\Consent\ConsentHashService
public: false
arguments:
- '@OpenConext\EngineBlockBundle\Authentication\Repository\DbalConsentRepository'

OpenConext\EngineBlock\Service\Consent\ConsentService:
arguments:
- '@OpenConext\EngineBlockBundle\Authentication\Repository\DbalConsentRepository'
- '@OpenConext\EngineBlock\Service\MetadataService'
Expand Down
4 changes: 2 additions & 2 deletions library/EngineBlock/Application/DiContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,11 @@ public function getAuthenticationLoopGuard()
}

/**
* @return OpenConext\EngineBlock\Service\ConsentService
* @return OpenConext\EngineBlock\Service\Consent\ConsentService
*/
public function getConsentService()
{
return $this->container->get(\OpenConext\EngineBlock\Service\ConsentService::class);
return $this->container->get(\OpenConext\EngineBlock\Service\Consent\ConsentService::class);
}

/**
Expand Down
222 changes: 94 additions & 128 deletions library/EngineBlock/Corto/Model/Consent.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@
* limitations under the License.
*/

use Doctrine\DBAL\Statement;
use OpenConext\EngineBlock\Authentication\Value\ConsentHashQuery;
use OpenConext\EngineBlock\Authentication\Value\ConsentStoreParameters;
use OpenConext\EngineBlock\Authentication\Value\ConsentUpdateParameters;
use OpenConext\EngineBlock\Authentication\Value\ConsentVersion;
use OpenConext\EngineBlock\Metadata\Entity\ServiceProvider;
use OpenConext\EngineBlock\Authentication\Value\ConsentType;
use OpenConext\EngineBlock\Service\Consent\ConsentHashServiceInterface;

class EngineBlock_Corto_Model_Consent
{
Expand All @@ -37,15 +41,10 @@ class EngineBlock_Corto_Model_Consent
*/
private $_response;
/**
* @var array
* @var array All attributes as an associative array.
*/
private $_responseAttributes;

/**
* @var EngineBlock_Database_ConnectionFactory
*/
private $_databaseConnectionFactory;

/**
* A reflection of the eb.run_all_manipulations_prior_to_consent feature flag
*
Expand All @@ -61,63 +60,81 @@ class EngineBlock_Corto_Model_Consent
private $_consentEnabled;

/**
* @param string $tableName
* @param bool $mustStoreValues
* @param EngineBlock_Saml2_ResponseAnnotationDecorator $response
* @param array $responseAttributes
* @param EngineBlock_Database_ConnectionFactory $databaseConnectionFactory
* @var ConsentHashServiceInterface
*/
private $_hashService;

/**
* @param bool $amPriorToConsentEnabled Is the run_all_manipulations_prior_to_consent feature enabled or not
* @param bool $consentEnabled Is the feature_enable_consent feature enabled or not
*/
public function __construct(
$tableName,
$mustStoreValues,
string $tableName,
bool $mustStoreValues,
EngineBlock_Saml2_ResponseAnnotationDecorator $response,
array $responseAttributes,
EngineBlock_Database_ConnectionFactory $databaseConnectionFactory,
$amPriorToConsentEnabled,
$consentEnabled
)
{
bool $amPriorToConsentEnabled,
bool $consentEnabled,
ConsentHashServiceInterface $hashService
) {
$this->_tableName = $tableName;
$this->_mustStoreValues = $mustStoreValues;
$this->_response = $response;
$this->_responseAttributes = $responseAttributes;
$this->_databaseConnectionFactory = $databaseConnectionFactory;
$this->_amPriorToConsentEnabled = $amPriorToConsentEnabled;
$this->_hashService = $hashService;
$this->_consentEnabled = $consentEnabled;
}

public function explicitConsentWasGivenFor(ServiceProvider $serviceProvider)
public function explicitConsentWasGivenFor(ServiceProvider $serviceProvider): bool
{
return !$this->_consentEnabled ||
$this->_hasStoredConsent($serviceProvider, ConsentType::TYPE_EXPLICIT);
if (!$this->_consentEnabled) {
return true;
}
$consent = $this->_hasStoredConsent($serviceProvider, ConsentType::TYPE_EXPLICIT);
return $consent->given();
}

public function implicitConsentWasGivenFor(ServiceProvider $serviceProvider)
/**
* Although the user has given consent previously we want to upgrade the deprecated unstable consent
* to the new stable consent type.
* https://www.pivotaltracker.com/story/show/176513931
*/
public function upgradeAttributeHashFor(ServiceProvider $serviceProvider, string $consentType): void
{
return !$this->_consentEnabled ||
$this->_hasStoredConsent($serviceProvider, ConsentType::TYPE_IMPLICIT);
if (!$this->_consentEnabled) {
return;
}
$consentVersion = $this->_hasStoredConsent($serviceProvider, $consentType);
if ($consentVersion->isUnstable()) {
$this->_updateConsent($serviceProvider, $consentType);
}
}

public function implicitConsentWasGivenFor(ServiceProvider $serviceProvider): bool
{
if (!$this->_consentEnabled) {
return true;
}
$consent = $this->_hasStoredConsent($serviceProvider, ConsentType::TYPE_IMPLICIT);
return $consent->given();
}

public function giveExplicitConsentFor(ServiceProvider $serviceProvider)
public function giveExplicitConsentFor(ServiceProvider $serviceProvider): bool
{
return !$this->_consentEnabled ||
$this->_storeConsent($serviceProvider, ConsentType::TYPE_EXPLICIT);
}

public function giveImplicitConsentFor(ServiceProvider $serviceProvider)
public function giveImplicitConsentFor(ServiceProvider $serviceProvider): bool
{
return !$this->_consentEnabled ||
$this->_storeConsent($serviceProvider, ConsentType::TYPE_IMPLICIT);
}

/**
* @return Doctrine\DBAL\Connection
*/
protected function _getConsentDatabaseConnection()
public function countTotalConsent(): int
{
return $this->_databaseConnectionFactory->create();
$consentUid = $this->_getConsentUid();
return $this->_hashService->countTotalConsent($consentUid);
}

protected function _getConsentUid()
Expand All @@ -129,116 +146,65 @@ protected function _getConsentUid()
return $this->_response->getNameIdValue();
}

protected function _getAttributesHash($attributes)
protected function _getAttributesHash($attributes): string
{
$hashBase = NULL;
if ($this->_mustStoreValues) {
ksort($attributes);
$hashBase = serialize($attributes);
} else {
$names = array_keys($attributes);
sort($names);
$hashBase = implode('|', $names);
}
return sha1($hashBase);
return $this->_hashService->getUnstableAttributesHash($attributes, $this->_mustStoreValues);
}

private function _storeConsent(ServiceProvider $serviceProvider, $consentType)
protected function _getStableAttributesHash($attributes): string
{
$dbh = $this->_getConsentDatabaseConnection();
if (!$dbh) {
return false;
}
return $this->_hashService->getStableAttributesHash($attributes, $this->_mustStoreValues);
}

private function _storeConsent(ServiceProvider $serviceProvider, $consentType): bool
{
$consentUuid = $this->_getConsentUid();
if(! is_string($consentUuid)){
if (!is_string($consentUuid)) {
return false;
}

$query = "INSERT INTO consent (hashed_user_id, service_id, attribute, consent_type, consent_date, deleted_at)
VALUES (?, ?, ?, ?, NOW(), '0000-00-00 00:00:00')
ON DUPLICATE KEY UPDATE attribute=VALUES(attribute), consent_type=VALUES(consent_type), consent_date=NOW()";
$parameters = array(
sha1($consentUuid),
$serviceProvider->entityId,
$this->_getAttributesHash($this->_responseAttributes),
$consentType,
$parameters = new ConsentStoreParameters(
hashedUserId: sha1($consentUuid),
serviceId: $serviceProvider->entityId,
attributeStableHash: $this->_getStableAttributesHash($this->_responseAttributes),
consentType: $consentType,
);

$statement = $dbh->prepare($query);
if (!$statement) {
throw new EngineBlock_Exception(
"Unable to create a prepared statement to insert consent?!",
EngineBlock_Exception::CODE_CRITICAL
);
}
return $this->_hashService->storeConsentHash($parameters);
}

assert($statement instanceof Statement);
try{
foreach ($parameters as $index => $parameter){
$statement->bindValue($index + 1, $parameter);
}

$statement->executeStatement();
}catch (\Doctrine\DBAL\Exception $e){
throw new EngineBlock_Corto_Module_Services_Exception(
sprintf('Error storing consent: "%s"', var_export($e->getMessage(), true)),
EngineBlock_Exception::CODE_CRITICAL
);
private function _updateConsent(ServiceProvider $serviceProvider, $consentType): bool
{
$consentUid = $this->_getConsentUid();
if (!is_string($consentUid)) {
return false;
}
return true;

$parameters = new ConsentUpdateParameters(
attributeStableHash: $this->_getStableAttributesHash($this->_responseAttributes),
attributeHash: $this->_getAttributesHash($this->_responseAttributes),
hashedUserId: sha1($consentUid),
serviceId: $serviceProvider->entityId,
consentType: $consentType,
);

return $this->_hashService->updateConsentHash($parameters);
}

private function _hasStoredConsent(ServiceProvider $serviceProvider, $consentType)
private function _hasStoredConsent(ServiceProvider $serviceProvider, $consentType): ConsentVersion
{
try {
$dbh = $this->_getConsentDatabaseConnection();
if (!$dbh) {
return false;
}

$attributesHash = $this->_getAttributesHash($this->_responseAttributes);

$consentUuid = $this->_getConsentUid();
if (!is_string($consentUuid)) {
return false;
}

$query = "
SELECT *
FROM {$this->_tableName}
WHERE hashed_user_id = ?
AND service_id = ?
AND attribute = ?
AND consent_type = ?
AND deleted_at IS NULL
";
$hashedUserId = sha1($consentUuid);
$parameters = array(
$hashedUserId,
$serviceProvider->entityId,
$attributesHash,
$consentType,
);

$statement = $dbh->prepare($query);
assert($statement instanceof Statement);
foreach ($parameters as $position => $parameter) {
$statement->bindValue($position + 1, $parameter);
}
$rows = $statement->executeQuery();

if ($rows->rowCount() < 1) {
// No stored consent found
return false;
}

return true;
} catch (PDOException $e) {
throw new EngineBlock_Corto_ProxyServer_Exception(
sprintf('Consent retrieval failed! Error: "%s"', $e->getMessage()),
EngineBlock_Exception::CODE_ALERT
);
$consentUid = $this->_getConsentUid();
if (!is_string($consentUid)) {
return ConsentVersion::notGiven();
}

$query = new ConsentHashQuery(
hashedUserId: sha1($consentUid),
serviceId: $serviceProvider->entityId,
attributeHash: $this->_getAttributesHash($this->_responseAttributes),
attributeStableHash: $this->_getStableAttributesHash($this->_responseAttributes),
consentType: $consentType,
);
return $this->_hashService->retrieveConsentHash($query);
}
}
23 changes: 12 additions & 11 deletions library/EngineBlock/Corto/Model/Consent/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
* limitations under the License.
*/

use OpenConext\EngineBlock\Service\Consent\ConsentHashServiceInterface;

/**
* @todo write a test
*/
Expand All @@ -24,21 +26,20 @@ class EngineBlock_Corto_Model_Consent_Factory
/** @var EngineBlock_Corto_Filter_Command_Factory */
private $_filterCommandFactory;

/** @var EngineBlock_Database_ConnectionFactory */
private $_databaseConnectionFactory;

/**
* @var ConsentHashServiceInterface
*/
private $_hashService;

/**
/**
* @param EngineBlock_Corto_Filter_Command_Factory $filterCommandFactory
* @param EngineBlock_Database_ConnectionFactory $databaseConnectionFactory
*/
public function __construct(
EngineBlock_Corto_Filter_Command_Factory $filterCommandFactory,
EngineBlock_Database_ConnectionFactory $databaseConnectionFactory
)
{
ConsentHashServiceInterface $hashService
) {
$this->_filterCommandFactory = $filterCommandFactory;
$this->_databaseConnectionFactory = $databaseConnectionFactory;
$this->_hashService = $hashService;
}

/**
Expand Down Expand Up @@ -68,9 +69,9 @@ public function create(
$proxyServer->getConfig('ConsentStoreValues', true),
$response,
$attributes,
$this->_databaseConnectionFactory,
$amPriorToConsent,
$consentEnabled
$consentEnabled,
$this->_hashService
);
}
}
Loading