implement audit logger
This commit is contained in:
parent
948b36c80d
commit
9e72a9881b
74
src/Database/AuditLoggerBase.php
Normal file
74
src/Database/AuditLoggerBase.php
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<?php namespace SokoWeb\Database;
|
||||||
|
|
||||||
|
use SokoWeb\Interfaces\Database\IConnection;
|
||||||
|
|
||||||
|
abstract class AuditLoggerBase
|
||||||
|
{
|
||||||
|
const LOG_TYPE_INSERT = 'insert';
|
||||||
|
const LOG_TYPE_UPDATE = 'update';
|
||||||
|
const LOG_TYPE_DELETE = 'delete';
|
||||||
|
|
||||||
|
private IConnection $connection;
|
||||||
|
|
||||||
|
private string $logTable;
|
||||||
|
|
||||||
|
public function __construct(IConnection $connection, string $logTable)
|
||||||
|
{
|
||||||
|
$this->connection = $connection;
|
||||||
|
$this->logTable = $logTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function logInsert(string $localTable, $localId)
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'local_table' => $localTable,
|
||||||
|
'local_id' => $localId,
|
||||||
|
'type' => static::LOG_TYPE_INSERT,
|
||||||
|
'modifier_id' => $this->getModifierId(),
|
||||||
|
];
|
||||||
|
|
||||||
|
$query = 'INSERT INTO ' . Utils::backtick($this->logTable) . ' SET ' . Utils::generateColumnsWithBinding(array_keys($data));
|
||||||
|
$stmt = $this->connection->prepare($query);
|
||||||
|
$stmt->execute($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function logUpdate(string $localTable, $localId, array $diff)
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'local_table' => $localTable,
|
||||||
|
'local_id' => $localId,
|
||||||
|
'type' => static::LOG_TYPE_UPDATE,
|
||||||
|
'modifier_id' => $this->getModifierId(),
|
||||||
|
'column' => null,
|
||||||
|
'old' => null,
|
||||||
|
'new' => null,
|
||||||
|
];
|
||||||
|
|
||||||
|
$query = 'INSERT INTO ' . Utils::backtick($this->logTable) . ' SET ' . Utils::generateColumnsWithBinding(array_keys($data));
|
||||||
|
$stmt = $this->connection->prepare($query);
|
||||||
|
|
||||||
|
foreach ($diff as $name => $values) {
|
||||||
|
$data['column'] = $name;
|
||||||
|
$data['old'] = $values['old'];
|
||||||
|
$data['new'] = $values['new'];
|
||||||
|
$stmt->execute($data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function logDelete(string $localTable, $localId, array $attributes)
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'local_table' => $localTable,
|
||||||
|
'local_id' => $localId,
|
||||||
|
'type' => static::LOG_TYPE_DELETE,
|
||||||
|
'modifier_id' => $this->getModifierId(),
|
||||||
|
'old' => $attributes,
|
||||||
|
];
|
||||||
|
|
||||||
|
$query = 'INSERT INTO ' . Utils::backtick($this->logTable) . ' SET ' . Utils::generateColumnsWithBinding(array_keys($data));
|
||||||
|
$stmt = $this->connection->prepare($query);
|
||||||
|
$stmt->execute($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract protected function getModifierId();
|
||||||
|
}
|
@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
use SokoWeb\Interfaces\Database\IConnection;
|
use SokoWeb\Interfaces\Database\IConnection;
|
||||||
use SokoWeb\Database\Utils;
|
use SokoWeb\Database\Utils;
|
||||||
|
use SokoWeb\Database\AuditLoggerBase;
|
||||||
|
use SokoWeb\Interfaces\Database\IAuditLogger;
|
||||||
|
use SokoWeb\Interfaces\Database\IResultSet;
|
||||||
|
|
||||||
class Modify
|
class Modify
|
||||||
{
|
{
|
||||||
@ -9,6 +12,8 @@ class Modify
|
|||||||
|
|
||||||
private string $table;
|
private string $table;
|
||||||
|
|
||||||
|
private ?AuditLoggerBase $auditLogger;
|
||||||
|
|
||||||
private string $idName = 'id';
|
private string $idName = 'id';
|
||||||
|
|
||||||
private array $attributes = [];
|
private array $attributes = [];
|
||||||
@ -17,10 +22,13 @@ class Modify
|
|||||||
|
|
||||||
private bool $autoIncrement = true;
|
private bool $autoIncrement = true;
|
||||||
|
|
||||||
public function __construct(IConnection $connection, string $table)
|
private ?array $diff = null;
|
||||||
|
|
||||||
|
public function __construct(IConnection $connection, string $table, ?AuditLoggerBase $auditLogger = null)
|
||||||
{
|
{
|
||||||
$this->connection = $connection;
|
$this->connection = $connection;
|
||||||
$this->table = $table;
|
$this->table = $table;
|
||||||
|
$this->auditLogger = $auditLogger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setIdName(string $idName): Modify
|
public function setIdName(string $idName): Modify
|
||||||
@ -65,6 +73,13 @@ class Modify
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setDiff(array $diff): Modify
|
||||||
|
{
|
||||||
|
$this->diff = $diff;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function getId()
|
public function getId()
|
||||||
{
|
{
|
||||||
return $this->attributes[$this->idName];
|
return $this->attributes[$this->idName];
|
||||||
@ -89,6 +104,10 @@ class Modify
|
|||||||
|
|
||||||
$stmt = $this->connection->prepare($query);
|
$stmt = $this->connection->prepare($query);
|
||||||
$stmt->execute([$this->idName => $this->attributes[$this->idName]]);
|
$stmt->execute([$this->idName => $this->attributes[$this->idName]]);
|
||||||
|
|
||||||
|
if ($this->auditLogger !== null) {
|
||||||
|
$this->auditLogger->logDelete($this->attributes[$this->idName], $this->attributes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function insert(): void
|
private function insert(): void
|
||||||
@ -99,7 +118,7 @@ class Modify
|
|||||||
$this->attributes[$this->idName] = $this->generateKey();
|
$this->attributes[$this->idName] = $this->generateKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
$set = $this->generateColumnsWithBinding(array_keys($this->attributes));
|
$set = Utils::generateColumnsWithBinding(array_keys($this->attributes));
|
||||||
|
|
||||||
$query = 'INSERT INTO ' . Utils::backtick($this->table) . ' SET ' . $set;
|
$query = 'INSERT INTO ' . Utils::backtick($this->table) . ' SET ' . $set;
|
||||||
|
|
||||||
@ -109,32 +128,60 @@ class Modify
|
|||||||
if ($this->autoIncrement) {
|
if ($this->autoIncrement) {
|
||||||
$this->attributes[$this->idName] = $this->connection->lastId();
|
$this->attributes[$this->idName] = $this->connection->lastId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->auditLogger !== null) {
|
||||||
|
$this->auditLogger->logInsert($this->attributes[$this->idName]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function update(): void
|
private function update(): void
|
||||||
{
|
{
|
||||||
|
$this->generateDiff();
|
||||||
|
if (count($this->diff) === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$attributes = $this->attributes;
|
$attributes = $this->attributes;
|
||||||
unset($attributes[$this->idName]);
|
unset($attributes[$this->idName]);
|
||||||
|
|
||||||
$set = $this->generateColumnsWithBinding(array_keys($attributes));
|
$set = Utils::generateColumnsWithBinding(array_keys($attributes));
|
||||||
|
|
||||||
$query = 'UPDATE ' . Utils::backtick($this->table) . ' SET ' . $set . ' WHERE ' . Utils::backtick($this->idName) . '=?';
|
$query = 'UPDATE ' . Utils::backtick($this->table) . ' SET ' . $set . ' WHERE ' . Utils::backtick($this->idName) . '=?';
|
||||||
|
|
||||||
$stmt = $this->connection->prepare($query);
|
$stmt = $this->connection->prepare($query);
|
||||||
$stmt->execute(array_merge($attributes, [$this->idName => $this->attributes[$this->idName]]));
|
$stmt->execute(array_merge($attributes, [$this->idName => $this->attributes[$this->idName]]));
|
||||||
|
|
||||||
|
if ($this->auditLogger !== null) {
|
||||||
|
$this->auditLogger->logUpdate($this->attributes[$this->idName], $this->diff);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function generateColumnsWithBinding(array $columns): string
|
|
||||||
{
|
|
||||||
array_walk($columns, function(&$value, $key) {
|
|
||||||
$value = Utils::backtick($value) . '=?';
|
|
||||||
});
|
|
||||||
|
|
||||||
return implode(',', $columns);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generateKey(): string
|
private function generateKey(): string
|
||||||
{
|
{
|
||||||
return substr(hash('sha256', serialize($this->attributes) . random_bytes(5) . microtime()), 0, 7);
|
return substr(hash('sha256', serialize($this->attributes) . random_bytes(5) . microtime()), 0, 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function generateDiff(): void
|
||||||
|
{
|
||||||
|
if (isset($this->diff)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->diff = [];
|
||||||
|
$original = $this->readFromDb(array_keys($this->attributes));
|
||||||
|
foreach ($original as $key => $value) {
|
||||||
|
if ($value !== $this->attributes[$key]) {
|
||||||
|
$this->diff[$key] = ['old' => $value, 'new' => $this->attributes[$key]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function readFromDb($columns): array
|
||||||
|
{
|
||||||
|
return (new Select($this->connection, $this->table))
|
||||||
|
->columns($columns)
|
||||||
|
->where($this->idName, '=', $this->attributes[$this->idName])
|
||||||
|
->execute()
|
||||||
|
->fetch(IResultSet::FETCH_ASSOC);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,4 +5,13 @@ class Utils {
|
|||||||
{
|
{
|
||||||
return '`' . $name . '`';
|
return '`' . $name . '`';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function generateColumnsWithBinding(array $columns): string
|
||||||
|
{
|
||||||
|
array_walk($columns, function(&$value, $key) {
|
||||||
|
$value = static::backtick($value) . '=?';
|
||||||
|
});
|
||||||
|
|
||||||
|
return implode(',', $columns);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
10
src/Interfaces/Database/IAuditLogger.php
Normal file
10
src/Interfaces/Database/IAuditLogger.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php namespace SokoWeb\Interfaces\Database;
|
||||||
|
|
||||||
|
interface IAuditLogger
|
||||||
|
{
|
||||||
|
public function auditInsert(): void;
|
||||||
|
|
||||||
|
public function auditUpdate(): void;
|
||||||
|
|
||||||
|
public function auditDelete(): void;
|
||||||
|
}
|
@ -121,15 +121,19 @@ class PersistentDataManager
|
|||||||
|
|
||||||
if ($id !== null) {
|
if ($id !== null) {
|
||||||
$original = $model->getSnapshot();
|
$original = $model->getSnapshot();
|
||||||
|
$diff = [];
|
||||||
|
|
||||||
foreach ($original as $key => $value) {
|
foreach ($original as $key => $value) {
|
||||||
if ($value === $modified[$key]) {
|
if ($value === $modified[$key]) {
|
||||||
unset($modified[$key]);
|
unset($modified[$key]);
|
||||||
|
} else {
|
||||||
|
$diff[$key] = ['old' => $value, 'new' => $modified[$key]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count($modified) > 0) {
|
if (count($modified) > 0) {
|
||||||
$modify->setId($id);
|
$modify->setId($id);
|
||||||
|
$modify->setDiff($original);
|
||||||
$modify->fill($modified);
|
$modify->fill($modified);
|
||||||
$modify->save();
|
$modify->save();
|
||||||
}
|
}
|
||||||
|
102
src/Util/GoogleJwtValidator.php
Normal file
102
src/Util/GoogleJwtValidator.php
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<?php namespace RVR\Util;
|
||||||
|
|
||||||
|
use SokoWeb\Http\Request;
|
||||||
|
use SokoWeb\Interfaces\Http\IRequest;
|
||||||
|
use phpseclib3\Math\BigInteger;
|
||||||
|
use phpseclib3\Crypt\RSA;
|
||||||
|
use Firebase\JWT\JWT;
|
||||||
|
use Firebase\JWT\Key;
|
||||||
|
|
||||||
|
class GoogleOAuth
|
||||||
|
{
|
||||||
|
private static string $dialogUrlBase = 'https://accounts.google.com/o/oauth2/v2/auth';
|
||||||
|
|
||||||
|
private static string $tokenUrlBase = 'https://oauth2.googleapis.com/token';
|
||||||
|
|
||||||
|
private static string $certsUrl = 'https://www.googleapis.com/oauth2/v3/certs';
|
||||||
|
|
||||||
|
private IRequest $request;
|
||||||
|
|
||||||
|
public function __construct(IRequest $request)
|
||||||
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDialogUrl(string $state, string $redirectUrl, ?string $nonce = null, ?string $loginHint = null): string
|
||||||
|
{
|
||||||
|
$oauthParams = [
|
||||||
|
'response_type' => 'code',
|
||||||
|
'client_id' => $_ENV['GOOGLE_OAUTH_CLIENT_ID'],
|
||||||
|
'scope' => 'openid email',
|
||||||
|
'redirect_uri' => $redirectUrl,
|
||||||
|
'state' => $state,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($nonce !== null) {
|
||||||
|
$oauthParams['nonce'] = $nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($loginHint !== null) {
|
||||||
|
$oauthParams['login_hint'] = $loginHint;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$dialogUrlBase . '?' . http_build_query($oauthParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getToken(string $code, string $redirectUrl): array
|
||||||
|
{
|
||||||
|
$tokenParams = [
|
||||||
|
'code' => $code,
|
||||||
|
'client_id' => $_ENV['GOOGLE_OAUTH_CLIENT_ID'],
|
||||||
|
'client_secret' => $_ENV['GOOGLE_OAUTH_CLIENT_SECRET'],
|
||||||
|
'redirect_uri' => $redirectUrl,
|
||||||
|
'grant_type' => 'authorization_code',
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->request->setUrl(self::$tokenUrlBase);
|
||||||
|
$this->request->setMethod(IRequest::HTTP_POST);
|
||||||
|
$this->request->setQuery($tokenParams);
|
||||||
|
$response = $this->request->send();
|
||||||
|
|
||||||
|
return json_decode($response->getBody(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validateJwt($jwt): ?array
|
||||||
|
{
|
||||||
|
$request = new Request(self::$certsUrl, IRequest::HTTP_GET);
|
||||||
|
$response = $request->send();
|
||||||
|
$certs = json_decode($response->getBody(), true)['keys'];
|
||||||
|
|
||||||
|
foreach ($certs as $cert) {
|
||||||
|
$publicKey = $this->getPublicKey($cert);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return (array) JWT::decode($jwt, new Key($publicKey, 'RS256'));
|
||||||
|
} catch (ExpiredException $e) {
|
||||||
|
return null;
|
||||||
|
} catch (SignatureInvalidException $e) {
|
||||||
|
//continue
|
||||||
|
} catch (DomainException $e) {
|
||||||
|
//continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getPublicKey($cert): string
|
||||||
|
{
|
||||||
|
$modulus = new BigInteger($this->base64Decode($cert['n']), 256);
|
||||||
|
$exponent = new BigInteger($this->base64Decode($cert['e']), 256);
|
||||||
|
$component = ['n' => $modulus, 'e' => $exponent];
|
||||||
|
$rsa = new RSA();
|
||||||
|
$rsa->loadKey($component);
|
||||||
|
return $rsa->getPublicKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function base64Decode($input): string
|
||||||
|
{
|
||||||
|
$input = str_replace(['_', '-'], ['/', '+'], $input);
|
||||||
|
return base64_decode($input);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user