Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add healthcheck to UserStorage, UserSecretStorage and StateStorage classes #54

Merged
merged 1 commit into from
Jun 12, 2024
Merged
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## 4.3.0
* Add healthCheck() to the UserStorage, UserSecretStorage and StateSTorage classes (#).

## 4.2.0
* Require PHP 8.2
* Use SQL "REPLACE INTO" syntax for the state storage. This requires a mysql or sqlite backend.
Expand Down
19 changes: 19 additions & 0 deletions library/tiqr/Tiqr/HealthCheck/Interface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

interface Tiqr_HealthCheck_Interface
{
/**
* Do a health check
*
* @param string &$statusMessage: a message that describes the status of the health check.
*
* This message is set by the health check when it fails to provide more information about the failure.
* This message should be a short string that can be displayed to a user or logged.
* It must not contain any sensitive information.
*
* @return bool: true when the healthcheck is successful, false otherwise
*
* A class that does not implement a health check always returns true
*/
public function healthCheck(string &$statusMessage = ''): bool;
}
16 changes: 12 additions & 4 deletions library/tiqr/Tiqr/StateStorage/Abstract.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,19 @@
* @author ivo
*
*/
abstract class Tiqr_StateStorage_Abstract implements Tiqr_StateStorage_StateStorageInterface
abstract class Tiqr_StateStorage_Abstract implements Tiqr_StateStorage_StateStorageInterface, Tiqr_HealthCheck_Interface
{
/**
* The options for the storage. Derived classes can access this
* to retrieve options configured for the state storage.
* @var array
*/
protected $_options = array();
protected array $_options = array();

/**
* @var LoggerInterface
*/
protected $logger;
protected LoggerInterface $logger;

/**
* An initializer that will be called directly after instantiating
Expand All @@ -60,9 +60,17 @@ public function init(): void
* a state storage instance of a certain type.
* @param array $options An array of options for the state storage
*/
public function __construct($options, LoggerInterface $logger)
public function __construct(array $options, LoggerInterface $logger)
{
$this->logger = $logger;
$this->_options = $options;
}

/**
* @see Tiqr_HealthCheck_Interface::healthCheck()
*/
public function healthCheck(string &$statusMessage = ''): bool
{
return true; // Health check is always successful when not implemented
}
}
20 changes: 19 additions & 1 deletion library/tiqr/Tiqr/StateStorage/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
* @author ivo
*
*/
class Tiqr_StateStorage_File implements Tiqr_StateStorage_StateStorageInterface
class Tiqr_StateStorage_File implements Tiqr_StateStorage_StateStorageInterface, Tiqr_HealthCheck_Interface
{
private $logger;

Expand Down Expand Up @@ -131,4 +131,22 @@ public function init(): void
{
# Nothing to do here
}

/**
* @see Tiqr_HealthCheck_Interface::healthCheck()
*/
public function healthCheck(string &$statusMessage = ''): bool
{
try {
// Generate a random key and use it to store a value
$key = bin2hex(random_bytes(16));
$this->setValue($key, 'healthcheck', 10);
$this->unsetValue($key); // Cleanup
} catch (Exception $e) {
$statusMessage = 'Tiqr_StateStorage_File: error setting key: ' . $e->getMessage();
return false;
}

return true;
}
}
21 changes: 19 additions & 2 deletions library/tiqr/Tiqr/StateStorage/Memcache.php
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,29 @@ public function getValue(string $key)

$result = $this->_memcache->get($key);
if ($result === false) {
// Memcache interface does not provide error information, either the key does not exists or
// Memcache interface does not provide error information, either the key does not exist or
// there was an error communicating with the memcache
$this->logger->info( sprintf('Unable to get key "%s" from memcache StateStorage', $key) );
return null;
}
return $result;
}


/**
* @see Tiqr_HealthCheck_Interface::healthCheck()
*/
public function healthCheck(string &$statusMessage = ''): bool
{
try {
// Generate a random key and use it to store a value in the memcache
$key = bin2hex(random_bytes(16));
$this->setValue($key, 'healthcheck', 10);
} catch (Exception $e) {
$statusMessage = 'Unable to store key in memcache: ' . $e->getMessage();
return false;
}

return true;
}

}
16 changes: 16 additions & 0 deletions library/tiqr/Tiqr/StateStorage/Pdo.php
Original file line number Diff line number Diff line change
Expand Up @@ -230,4 +230,20 @@ public function getValue(string $key)
return $result;
}

/**
* @see Tiqr_HealthCheck_Interface::healthCheck()
*/
public function healthCheck(string &$statusMessage = ''): bool
{
try {
// Retrieve a random row from the table, this checks that the table exists and is readable
$sth = $this->handle->prepare('SELECT `value`, `key`, `expire` FROM ' . $this->tablename . ' LIMIT 1');
$sth->execute();
}
catch (Exception $e) {
$statusMessage = sprintf('Error performing health check on PDO StateStorage: %s', $e->getMessage());
return false;
}
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,40 @@

use Psr\Log\LoggerInterface;

/**
* Copyright 2022 SURF B.V.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

trait UserSecretStorageTrait
abstract class Tiqr_UserSecretStorage_Abstract implements Tiqr_UserSecretStorage_Interface, Tiqr_HealthCheck_Interface
{
/**
* @var Tiqr_UserSecretStorage_Encryption_Interface
*/
private $encryption;
protected LoggerInterface $logger;
private Tiqr_UserSecretStorage_Encryption_Interface $encryption;

/**
* @var array() of type_id (prefix) => Tiqr_UserSecretStorage_Encryption_Interface
*/
private array $decryption;

public function __construct(LoggerInterface $logger, Tiqr_UserSecretStorage_Encryption_Interface $encryption, array $decryption = array())
{
$this->logger = $logger;
$this->encryption = $encryption;
$this->decryption = $decryption;
}

private $decryption;
/**
* Get the user's secret
* @param String $userId
* @return String The user's secret
* @throws Exception
*/
abstract protected function getUserSecret(string $userId): string;

/**
* @var LoggerInterface
* Set the user's secret
*
* @param String $userId
* @param String $secret The user's secret
* @throws Exception
*/
private $logger;
abstract protected function setUserSecret(string $userId, string $secret): void;


/**
* Get the user's secret
Expand All @@ -48,15 +49,15 @@ public function getSecret(string $userId): string
$pos = strpos($encryptedSecret, ':');
if ($pos === false) {
// If the secret is not prefixed with the encryption type_id, it is assumed to be unencrypted.
$this->logger->info("Secret for user '$userId' is not prefixed with the encryption type, assuming that it is not unencrypted");
$this->logger->info("Secret for user '$userId' is not prefixed with the encryption type, assuming that it is not encrypted");
return $encryptedSecret;
}

$prefix = substr($encryptedSecret, 0, $pos);
if ($prefix === $this->encryption->get_type()) {
// Decrypt the secret if it is prefixed with the current encryption type
// Remove the encryption type prefix before decrypting
return $this->encryption->decrypt( substr($encryptedSecret, $pos+1) );
return $this->encryption->decrypt( substr($encryptedSecret, $pos+1) );
}

// Check the decryption array for the encryption type to see if there is an encryption
Expand All @@ -81,4 +82,12 @@ public function setSecret(string $userId, string $secret): void
// Prefix the user secret with the encryption type
$this->setUserSecret($userId, $this->encryption->get_type() . ':' . $encryptedSecret);
}

/**
* @see Tiqr_HealthCheck_Interface::healthCheck()
*/
public function healthCheck(string &$statusMessage = ''): bool
{
return true; // Health check is always successful when not implemented
}
}
12 changes: 4 additions & 8 deletions library/tiqr/Tiqr/UserSecretStorage/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,8 @@
*

*/
class Tiqr_UserSecretStorage_File implements Tiqr_UserSecretStorage_Interface
class Tiqr_UserSecretStorage_File extends Tiqr_UserSecretStorage_Abstract
{
use UserSecretStorageTrait;
use FileTrait;

private $path;
Expand All @@ -47,10 +46,7 @@ public function __construct(
LoggerInterface $logger,
array $decryption = array()
) {
// See UserSecretStorageTrait
$this->encryption = $encryption;
$this->decryption = $decryption;
$this->logger = $logger;
parent::__construct($logger, $encryption, $decryption);

// See FileTrait
$this->path = $path;
Expand All @@ -64,7 +60,7 @@ public function __construct(
* @return String The user's secret
* @throws Exception
*/
private function getUserSecret(string $userId): string
protected function getUserSecret(string $userId): string
{
if ($data = $this->_loadUser($userId)) {
if (isset($data["secret"])) {
Expand All @@ -82,7 +78,7 @@ private function getUserSecret(string $userId): string
* @param String $secret
* @throws Exception
*/
private function setUserSecret(string $userId, string $secret): void
protected function setUserSecret(string $userId, string $secret): void
{
$data=array();
if ($this->_userExists($userId)) {
Expand Down
2 changes: 1 addition & 1 deletion library/tiqr/Tiqr/UserSecretStorage/OathServiceClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class Tiqr_UserSecretStorage_OathServiceClient implements Tiqr_UserSecretStorage
/**
* @var LoggerInterface
*/
private $logger;
private LoggerInterface $logger;

public function __construct(Tiqr_API_Client $client, LoggerInterface $logger)
{
Expand Down
31 changes: 22 additions & 9 deletions library/tiqr/Tiqr/UserSecretStorage/Pdo.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,8 @@
*
*/

class Tiqr_UserSecretStorage_Pdo implements Tiqr_UserSecretStorage_Interface
class Tiqr_UserSecretStorage_Pdo extends Tiqr_UserSecretStorage_Abstract
{
use UserSecretStorageTrait;

private $tableName;

private $handle;
Expand All @@ -74,10 +72,7 @@ public function __construct(
string $tableName,
array $decryption = array()
) {
// See UserSecretStorageTrait
$this->encryption = $encryption;
$this->logger = $logger;
$this->decryption = $decryption;
parent::__construct($logger, $encryption, $decryption);

// Set our own properties
$this->handle = $handle;
Expand Down Expand Up @@ -109,7 +104,7 @@ public function userExists(string $userId): bool
* @return string
* @throws Exception
*/
private function getUserSecret(string $userId): string
protected function getUserSecret(string $userId): string
{
try {
$sth = $this->handle->prepare('SELECT secret FROM ' . $this->tableName . ' WHERE userid = ?');
Expand Down Expand Up @@ -137,7 +132,7 @@ private function getUserSecret(string $userId): string
*
* @throws Exception
*/
private function setUserSecret(string $userId, string $secret): void
protected function setUserSecret(string $userId, string $secret): void
{
// UserSecretStorage can be used in a separate table. In this case the table has its own userid column
// This means that when a user has been created using in the UserStorage, it does not exists in the
Expand All @@ -164,4 +159,22 @@ private function setUserSecret(string $userId, string $secret): void
throw ReadWriteException::fromOriginalException($e);
}
}

/**
* @see Tiqr_UserSecretStorage_Interface::healthCheck()
*/
public function healthCheck(string &$statusMessage = ''): bool
{
// Check whether the table exists by reading a random row
try {
$sth = $this->handle->prepare('SELECT secret FROM '.$this->tableName.' LIMIT 1');
$sth->execute();
}
catch (Exception $e) {
$statusMessage = "UserSecretStorage_PDO error: " . $e->getMessage();
return false;
}

return true;
}
}
12 changes: 10 additions & 2 deletions library/tiqr/Tiqr/UserStorage/Abstract.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
*
* @author peter
*/
abstract class Tiqr_UserStorage_Abstract implements Tiqr_UserStorage_Interface
abstract class Tiqr_UserStorage_Abstract implements Tiqr_UserStorage_Interface, Tiqr_HealthCheck_Interface
{
protected $logger;
protected LoggerInterface $logger;

public function __construct(array $config, LoggerInterface $logger)
{
Expand All @@ -45,4 +45,12 @@ public function getAdditionalAttributes(string $userId): array
{
return array();
}

/**
* @see Tiqr_HealthCheck_Interface::healthCheck()
*/
public function healthCheck(string &$statusMessage = ''): bool
{
return true; // Health check is always successful when not implemented
}
}
Loading
Loading