Introduce per-tenant override for helpdesk BC connection config. New table helpdesk_tenant_settings stores tenant-specific credentials with encrypted secrets (AES-256-GCM). EffectiveHelpdeskSettingsService resolves global vs tenant config per request. Settings UI extended with tenant override tab, toggle, and dual editing mode. All runtime consumers (OData, SOAP, OAuth) read through the new resolver. Token cache flushed on any config change. Default behavior unchanged — tenants use global config until override explicitly enabled. Also includes risk radar UI refinements: Stripe-style card redesign with accent borders and score pills, removal of redundant KPI tiles and search, and filtering of zero-score entries. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
204 lines
6.3 KiB
PHP
204 lines
6.3 KiB
PHP
<?php
|
|
|
|
namespace MintyPHP\Module\Helpdesk\Service;
|
|
|
|
use MintyPHP\Service\Settings\SettingsCryptoGatewayInterface;
|
|
|
|
/**
|
|
* Gateway for per-tenant helpdesk BC connection settings.
|
|
*
|
|
* Wraps the tenant settings repository and handles encryption/decryption
|
|
* of secrets (basic_password, oauth_client_secret) via SettingsCryptoGateway.
|
|
*/
|
|
class HelpdeskTenantSettingsGateway
|
|
{
|
|
public function __construct(
|
|
private readonly HelpdeskTenantSettingsRepository $repository,
|
|
private readonly SettingsCryptoGatewayInterface $settingsCryptoGateway
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Check whether an active override exists for the given tenant.
|
|
*/
|
|
public function isOverrideEnabled(int $tenantId): bool
|
|
{
|
|
$row = $this->repository->findByTenantId($tenantId);
|
|
if ($row === null) {
|
|
return false;
|
|
}
|
|
|
|
return (int) ($row['override_enabled'] ?? 0) === 1;
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>|null Raw row data (secrets still encrypted)
|
|
*/
|
|
public function findByTenantId(int $tenantId): ?array
|
|
{
|
|
return $this->repository->findByTenantId($tenantId);
|
|
}
|
|
|
|
public function getAuthMode(int $tenantId): string
|
|
{
|
|
$row = $this->repository->findByTenantId($tenantId);
|
|
$value = trim((string) ($row['auth_mode'] ?? ''));
|
|
|
|
return $value === HelpdeskSettingsGateway::AUTH_MODE_OAUTH2
|
|
? HelpdeskSettingsGateway::AUTH_MODE_OAUTH2
|
|
: HelpdeskSettingsGateway::AUTH_MODE_BASIC;
|
|
}
|
|
|
|
public function getODataBaseUrl(int $tenantId): string
|
|
{
|
|
$row = $this->repository->findByTenantId($tenantId);
|
|
$value = trim((string) ($row['odata_base_url'] ?? ''));
|
|
|
|
return $value !== '' ? rtrim($value, '/') : HelpdeskSettingsGateway::DEFAULT_ODATA_BASE_URL;
|
|
}
|
|
|
|
public function getCompanyName(int $tenantId): string
|
|
{
|
|
$row = $this->repository->findByTenantId($tenantId);
|
|
$value = trim((string) ($row['company_name'] ?? ''));
|
|
|
|
return $value !== '' ? $value : HelpdeskSettingsGateway::DEFAULT_COMPANY_NAME;
|
|
}
|
|
|
|
public function getBasicUser(int $tenantId): ?string
|
|
{
|
|
$row = $this->repository->findByTenantId($tenantId);
|
|
$value = trim((string) ($row['basic_user'] ?? ''));
|
|
|
|
return $value !== '' ? $value : null;
|
|
}
|
|
|
|
public function getBasicPassword(int $tenantId): ?string
|
|
{
|
|
$row = $this->repository->findByTenantId($tenantId);
|
|
$encrypted = trim((string) ($row['basic_password_enc'] ?? ''));
|
|
if ($encrypted === '') {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
$decrypted = $this->settingsCryptoGateway->decryptString($encrypted);
|
|
} catch (\Throwable) {
|
|
return null;
|
|
}
|
|
|
|
$decrypted = trim($decrypted);
|
|
|
|
return $decrypted !== '' ? $decrypted : null;
|
|
}
|
|
|
|
public function getOAuthTenantId(int $tenantId): ?string
|
|
{
|
|
$row = $this->repository->findByTenantId($tenantId);
|
|
$value = trim((string) ($row['oauth_tenant_id'] ?? ''));
|
|
|
|
return $value !== '' ? $value : null;
|
|
}
|
|
|
|
public function getOAuthClientId(int $tenantId): ?string
|
|
{
|
|
$row = $this->repository->findByTenantId($tenantId);
|
|
$value = trim((string) ($row['oauth_client_id'] ?? ''));
|
|
|
|
return $value !== '' ? $value : null;
|
|
}
|
|
|
|
public function getOAuthClientSecret(int $tenantId): ?string
|
|
{
|
|
$row = $this->repository->findByTenantId($tenantId);
|
|
$encrypted = trim((string) ($row['oauth_client_secret_enc'] ?? ''));
|
|
if ($encrypted === '') {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
$decrypted = $this->settingsCryptoGateway->decryptString($encrypted);
|
|
} catch (\Throwable) {
|
|
return null;
|
|
}
|
|
|
|
$decrypted = trim($decrypted);
|
|
|
|
return $decrypted !== '' ? $decrypted : null;
|
|
}
|
|
|
|
public function getOAuthTokenEndpoint(int $tenantId): ?string
|
|
{
|
|
$row = $this->repository->findByTenantId($tenantId);
|
|
$value = trim((string) ($row['oauth_token_endpoint'] ?? ''));
|
|
|
|
return $value !== '' ? $value : null;
|
|
}
|
|
|
|
public function getSystemRecommendationsConfig(int $tenantId): ?string
|
|
{
|
|
$row = $this->repository->findByTenantId($tenantId);
|
|
|
|
return ($row['system_recommendations_config'] ?? null) !== null
|
|
? (string) $row['system_recommendations_config']
|
|
: null;
|
|
}
|
|
|
|
public function getControllingRiskConfig(int $tenantId): ?string
|
|
{
|
|
$row = $this->repository->findByTenantId($tenantId);
|
|
|
|
return ($row['controlling_risk_config'] ?? null) !== null
|
|
? (string) $row['controlling_risk_config']
|
|
: null;
|
|
}
|
|
|
|
/**
|
|
* Persist tenant override settings. Secrets are encrypted before storage.
|
|
*
|
|
* @param array<string, mixed> $data Field values (plain-text passwords/secrets accepted)
|
|
*/
|
|
public function save(int $tenantId, array $data): bool
|
|
{
|
|
if ($tenantId <= 0) {
|
|
return false;
|
|
}
|
|
|
|
// Encrypt secrets before storage
|
|
if (isset($data['basic_password']) && is_string($data['basic_password'])) {
|
|
$password = trim($data['basic_password']);
|
|
unset($data['basic_password']);
|
|
if ($password !== '') {
|
|
try {
|
|
$data['basic_password_enc'] = $this->settingsCryptoGateway->encryptString($password);
|
|
} catch (\Throwable) {
|
|
return false;
|
|
}
|
|
} else {
|
|
$data['basic_password_enc'] = null;
|
|
}
|
|
}
|
|
|
|
if (isset($data['oauth_client_secret']) && is_string($data['oauth_client_secret'])) {
|
|
$secret = trim($data['oauth_client_secret']);
|
|
unset($data['oauth_client_secret']);
|
|
if ($secret !== '') {
|
|
try {
|
|
$data['oauth_client_secret_enc'] = $this->settingsCryptoGateway->encryptString($secret);
|
|
} catch (\Throwable) {
|
|
return false;
|
|
}
|
|
} else {
|
|
$data['oauth_client_secret_enc'] = null;
|
|
}
|
|
}
|
|
|
|
return $this->repository->upsert($tenantId, $data);
|
|
}
|
|
|
|
public function deleteByTenantId(int $tenantId): bool
|
|
{
|
|
return $this->repository->deleteByTenantId($tenantId);
|
|
}
|
|
}
|