first commit
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Utils;
|
||||
|
||||
use WorkbloomERP\Services\CacheService;
|
||||
use WorkbloomERP\Exceptions\AppException;
|
||||
|
||||
class CacheUtil {
|
||||
public static function set(string $key, int $ttl, mixed $value): bool {
|
||||
return CacheService::set(
|
||||
key: $key,
|
||||
value: is_string($value) ? $value : json_encode(
|
||||
value: match (true) {
|
||||
is_array($value), is_object($value) => $value,
|
||||
default => throw new AppException(message: 'Valor para cache deve ser string, array ou objeto.', code: 400)
|
||||
},
|
||||
flags: JSON_UNESCAPED_UNICODE
|
||||
),
|
||||
ttl: max(-1, $ttl)
|
||||
);
|
||||
}
|
||||
|
||||
public static function get(string $key): ?array {
|
||||
$cachedValue = CacheService::get(key: $key);
|
||||
return $cachedValue ? json_decode($cachedValue, true) : null;
|
||||
}
|
||||
|
||||
public static function delete(array $keys): bool {
|
||||
return CacheService::del(keys: $keys);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Utils;
|
||||
|
||||
use Exception;
|
||||
use JsonException;
|
||||
|
||||
class CryptoUtil {
|
||||
/**
|
||||
* Criptografa uma string.
|
||||
*
|
||||
* @param string $data
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function encrypt(string $data): string {
|
||||
$algorithm = self::getAlgorithm();
|
||||
$key = self::getKey();
|
||||
|
||||
$ivLength = openssl_cipher_iv_length($algorithm);
|
||||
if ($ivLength === false) {
|
||||
throw new Exception('Unable to determine IV length');
|
||||
}
|
||||
|
||||
$iv = random_bytes($ivLength);
|
||||
$cipherText = openssl_encrypt(
|
||||
$data,
|
||||
$algorithm,
|
||||
$key,
|
||||
OPENSSL_RAW_DATA,
|
||||
$iv,
|
||||
$tag
|
||||
);
|
||||
|
||||
if ($cipherText === false) {
|
||||
throw new Exception('Failed to encrypt data');
|
||||
}
|
||||
|
||||
return self::encodePayload([
|
||||
'iv' => base64_encode($iv),
|
||||
'tag' => base64_encode($tag),
|
||||
'value' => base64_encode($cipherText)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Descriptografa uma string.
|
||||
*
|
||||
* @param string $payload
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function decrypt(string $payload): string {
|
||||
$algorithm = self::getAlgorithm();
|
||||
$key = self::getKey();
|
||||
|
||||
$decodedPayload = self::decodePayload($payload);
|
||||
|
||||
$plainText = openssl_decrypt(
|
||||
base64_decode($decodedPayload['value']),
|
||||
$algorithm,
|
||||
$key,
|
||||
OPENSSL_RAW_DATA,
|
||||
base64_decode($decodedPayload['iv']),
|
||||
base64_decode($decodedPayload['tag'])
|
||||
);
|
||||
if ($plainText === false) {
|
||||
throw new Exception('Failed to decrypt data');
|
||||
}
|
||||
|
||||
return $plainText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna o algoritmo configurado.
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function getAlgorithm(): string {
|
||||
$algorithm = $_ENV['SYSTEM_CRYPTO_ALGO'];
|
||||
if (!in_array($algorithm, openssl_get_cipher_methods(), true)) {
|
||||
throw new Exception('Invalid cipher algorithm');
|
||||
}
|
||||
return $algorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna a chave de criptografia.
|
||||
*
|
||||
* A chave deve ser uma string hexadecimal de 64 caracteres (32 bytes).
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function getKey(): string {
|
||||
$hexKey = $_ENV['SYSTEM_CRYPTO_KEY'];
|
||||
if (empty($hexKey)) {
|
||||
throw new Exception('Encryption key not configured');
|
||||
}
|
||||
|
||||
$key = hex2bin($hexKey);
|
||||
if ($key === false || strlen($key) !== 32) {
|
||||
throw new Exception(message: 'Invalid encryption key. Expected 32 bytes (64 hex characters).');
|
||||
}
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Codifica o payload para armazenamento.
|
||||
*
|
||||
* @param array $payload
|
||||
* @return string
|
||||
* @throws JsonException
|
||||
*/
|
||||
private static function encodePayload(array $payload): string {
|
||||
return base64_encode(
|
||||
json_encode(
|
||||
$payload,
|
||||
JSON_THROW_ON_ERROR
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodifica o payload criptografado.
|
||||
*
|
||||
* @param string $payload
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function decodePayload(string $payload): array {
|
||||
try {
|
||||
$decoded = json_decode(
|
||||
base64_decode($payload),
|
||||
true,
|
||||
512,
|
||||
JSON_THROW_ON_ERROR
|
||||
);
|
||||
} catch (JsonException $e) {
|
||||
throw new Exception(message: 'Invalid encrypted payload', previous: $e);
|
||||
}
|
||||
|
||||
foreach (['iv', 'tag', 'value'] as $field) {
|
||||
if (!isset($decoded[$field])) {
|
||||
throw new Exception(sprintf('Missing "%s" field in encrypted payload', $field));
|
||||
}
|
||||
}
|
||||
return $decoded;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Utils;
|
||||
|
||||
use Exception;
|
||||
|
||||
class CypherUtil {
|
||||
public static function hash(string $data): string {
|
||||
if (!isset($_ENV['SYSTEM_CYPHER_ALGO']) || !isset($_ENV['SYSTEM_CYPHER_PEPPER'])) {
|
||||
throw new Exception('Missing required environment variables for hashing');
|
||||
}
|
||||
return hash_hmac(algo: $_ENV['SYSTEM_CYPHER_ALGO'], data: $data, key: $_ENV['SYSTEM_CYPHER_PEPPER']);
|
||||
}
|
||||
|
||||
public static function verify(string $data, string $hash): bool {
|
||||
return hash_equals(known_string: self::hash($data), user_string: $hash);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Utils;
|
||||
|
||||
use Exception;
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
use DateTimeImmutable;
|
||||
|
||||
class JwtUtil {
|
||||
private int $ttl;
|
||||
private string $issuer;
|
||||
private string $secretKey;
|
||||
private string $algorithm;
|
||||
|
||||
public function __construct(string $secretKey, string $algorithm = 'HS256', int $ttl = 3600, string $issuer = 'workbloomerp') {
|
||||
if (empty($secretKey)) {
|
||||
throw new Exception(message: 'JWT secret não configurado.');
|
||||
}
|
||||
$this->ttl = $ttl;
|
||||
$this->secretKey = $secretKey;
|
||||
$this->algorithm = $algorithm;
|
||||
$this->issuer = $issuer;
|
||||
}
|
||||
|
||||
public function generate(array $payload): string {
|
||||
$defaultClaims = [];
|
||||
$defaultClaims['iat'] = $payload['iat'] ?? (new DateTimeImmutable())->getTimestamp();
|
||||
$defaultClaims['exp'] = $payload['exp'] ?? ((new DateTimeImmutable())->modify("+{$this->ttl} seconds")->getTimestamp());
|
||||
|
||||
$finalPayload = array_merge($payload, $defaultClaims);
|
||||
return JWT::encode(
|
||||
payload: $finalPayload,
|
||||
key: $this->secretKey,
|
||||
alg: $this->algorithm
|
||||
);
|
||||
}
|
||||
|
||||
public function validate(string $token): object {
|
||||
return JWT::decode(
|
||||
jwt: $token,
|
||||
keyOrKeyArray: new Key(
|
||||
keyMaterial: $this->secretKey,
|
||||
algorithm: $this->algorithm
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Utils;
|
||||
|
||||
class SanitizeUtil {
|
||||
public static function string(mixed $value): ?string {
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
$value = trim(string: $value);
|
||||
$value = strip_tags(string: $value);
|
||||
return $value;
|
||||
}
|
||||
|
||||
public static function email(mixed $value): ?string {
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
$value = filter_var(value: $value, filter: FILTER_SANITIZE_EMAIL);
|
||||
return $value ?: '';
|
||||
}
|
||||
|
||||
public static function int(mixed $value): ?int {
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
return (int) filter_var(value: $value, filter: FILTER_SANITIZE_NUMBER_INT);
|
||||
}
|
||||
|
||||
public static function float(mixed $value): ?float {
|
||||
if($value === null) {
|
||||
return null;
|
||||
}
|
||||
return (float) filter_var(
|
||||
value: $value,
|
||||
filter: FILTER_SANITIZE_NUMBER_FLOAT,
|
||||
options: FILTER_FLAG_ALLOW_FRACTION | FILTER_FLAG_ALLOW_THOUSAND
|
||||
);
|
||||
}
|
||||
|
||||
public static function document(mixed $value): ?string {
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
// remove espaços
|
||||
$value = trim($value);
|
||||
// converte caracteres especiais (Ç -> C, Á -> A, etc.)
|
||||
$value = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $value);
|
||||
// mantém apenas letras e números
|
||||
$value = preg_replace('/[^a-zA-Z0-9]/', '', $value);
|
||||
return $value;
|
||||
}
|
||||
|
||||
public static function boolean(mixed $value): ?bool {
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
if (is_bool($value)) {
|
||||
return $value;
|
||||
}
|
||||
if (is_string($value)) {
|
||||
$value = strtolower($value);
|
||||
if (in_array($value, ['true', '1', 'yes'], true)) {
|
||||
return true;
|
||||
}
|
||||
if (in_array($value, ['false', '0', 'no'], true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (is_int($value)) {
|
||||
return $value === 1;
|
||||
}
|
||||
// Se não for possível converter, retorna null
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function phone(mixed $value, bool $withCountryCode = false): ?string {
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
// remove tudo que não for número
|
||||
$value = preg_replace('/\D/', '', $value);
|
||||
if (!$value) {
|
||||
return '';
|
||||
}
|
||||
// adiciona DDI do Brasil se não tiver
|
||||
if ($withCountryCode) {
|
||||
if (strlen($value) === 10 || strlen($value) === 11) {
|
||||
$value = "55{$value}";
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
namespace WorkbloomERP\Utils;
|
||||
|
||||
class ValidateUtil {
|
||||
public static function cpf(?string $cpf): bool {
|
||||
if ($cpf == null) {
|
||||
return false;
|
||||
}
|
||||
// Remove tudo que não for número
|
||||
$cpf = preg_replace('/\D/', '', $cpf);
|
||||
// Precisa ter 11 dígitos
|
||||
if (strlen($cpf) !== 11) {
|
||||
return false;
|
||||
}
|
||||
// Bloqueia CPFs com todos os dígitos iguais (11111111111, 00000000000, etc)
|
||||
if (preg_match('/^(\d)\1{10}$/', $cpf)) {
|
||||
return false;
|
||||
}
|
||||
// Valida primeiro dígito verificador
|
||||
for ($t = 9; $t < 11; $t++) {
|
||||
$soma = 0;
|
||||
for ($i = 0; $i < $t; $i++) {
|
||||
$soma += $cpf[$i] * (($t + 1) - $i);
|
||||
}
|
||||
$digito = ((10 * $soma) % 11) % 10;
|
||||
if ($cpf[$t] != $digito) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function cnpj(?string $cnpj): bool {
|
||||
if ($cnpj == null) {
|
||||
return false;
|
||||
}
|
||||
// Remove máscara
|
||||
$cnpj = strtoupper(preg_replace('/[^A-Z0-9]/', '', $cnpj));
|
||||
// Tamanho fixo
|
||||
if (strlen($cnpj) !== 14) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bloqueia CNPJs com todos os dígitos iguais (11111111111111, 00000000000000, etc)
|
||||
if (preg_match('/^(\d)\1{13}$/', $cnpj)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Regex: 12 alfanum + 2 numéricos
|
||||
if (!preg_match('/^[A-Z0-9]{12}[0-9]{2}$/', $cnpj)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Converte para valores numéricos
|
||||
$valores = [];
|
||||
for ($i = 0; $i < 14; $i++) {
|
||||
$char = $cnpj[$i];
|
||||
if (ctype_digit($char)) {
|
||||
$valores[$i] = (int)$char;
|
||||
} else {
|
||||
$valores[$i] = ord($char) - 48;
|
||||
}
|
||||
}
|
||||
|
||||
// === Primeiro DV ===
|
||||
$pesos1 = [5,4,3,2,9,8,7,6,5,4,3,2];
|
||||
$soma = 0;
|
||||
for ($i = 0; $i < 12; $i++) {
|
||||
$soma += $valores[$i] * $pesos1[$i];
|
||||
}
|
||||
|
||||
$resto = $soma % 11;
|
||||
$dv1 = ($resto < 2) ? 0 : 11 - $resto;
|
||||
if ($dv1 !== $valores[12]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// === Segundo DV ===
|
||||
$pesos2 = [6,5,4,3,2,9,8,7,6,5,4,3,2];
|
||||
$soma = 0;
|
||||
for ($i = 0; $i < 13; $i++) {
|
||||
$soma += $valores[$i] * $pesos2[$i];
|
||||
}
|
||||
|
||||
$resto = $soma % 11;
|
||||
$dv2 = ($resto < 2) ? 0 : 11 - $resto;
|
||||
return $dv2 === $valores[13];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user