Rename the top-level lib/ directory to core/ so the project root immediately communicates which code is core platform and which lives in modules/. PHP namespaces (MintyPHP\*) are unchanged — only the Composer PSR-4 path mapping moves from lib/ to core/. Module-internal lib/ directories (modules/*/lib/) are untouched. Updated across the full stack: - composer.json PSR-4 mapping - bootstrap entry points (web/index.php, bin/*, tests/bootstrap.php) - tooling configs (phpstan.neon, phpunit.xml, php-cs-fixer) - 26 architecture contract tests - enforcement-policy, guard-catalog, quality-gates - all documentation (CLAUDE.md, README, 25 docs/, .agents/skills/) - bin/qa-extended.sh search paths - .claude/settings.local.json permission paths Workflow: .agents/runs/CORE-LIB-RENAME-001/ (Analyst → Planner → Executor → Code Review (4 findings fixed) → Security Review → Acceptance Test → Finalizer) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
138 lines
6.1 KiB
PHP
138 lines
6.1 KiB
PHP
<?php
|
|
|
|
namespace MintyPHP\Repository\Tenant;
|
|
|
|
use MintyPHP\DB;
|
|
|
|
/** Persists LDAP authentication settings per tenant, including encrypted bind credentials. */
|
|
class TenantLdapAuthRepository implements TenantLdapAuthRepositoryInterface
|
|
{
|
|
private const COLUMNS = 'id, tenant_id, enabled, enforce_ldap_login, host, port, encryption_mode, verify_tls_certificate, base_dn, bind_dn_enc, bind_password_enc, user_search_filter, user_search_scope, bind_method, bind_username_format, unique_id_attribute, attribute_map, sync_profile_on_login, sync_profile_fields, allowed_domains, auto_remember_mode, network_timeout, created, modified';
|
|
|
|
private function unwrap($row): ?array
|
|
{
|
|
if (!$row || !is_array($row)) {
|
|
return null;
|
|
}
|
|
if (isset($row['tenant_auth_ldap']) && is_array($row['tenant_auth_ldap'])) {
|
|
return $row['tenant_auth_ldap'];
|
|
}
|
|
return $row;
|
|
}
|
|
|
|
public function findByTenantId(int $tenantId): ?array
|
|
{
|
|
$row = DB::selectOne(
|
|
'select ' . self::COLUMNS . ' from tenant_auth_ldap where tenant_id = ? limit 1',
|
|
(string) $tenantId
|
|
);
|
|
return $this->unwrap($row);
|
|
}
|
|
|
|
public function listByTenantIds(array $tenantIds): array
|
|
{
|
|
$tenantIds = array_values(array_unique(array_map('intval', $tenantIds)));
|
|
$tenantIds = array_values(array_filter($tenantIds, static fn ($id) => $id > 0));
|
|
if (!$tenantIds) {
|
|
return [];
|
|
}
|
|
|
|
$placeholders = implode(',', array_fill(0, count($tenantIds), '?'));
|
|
$rows = DB::select(
|
|
'select ' . self::COLUMNS . ' from tenant_auth_ldap where tenant_id in (' . $placeholders . ')',
|
|
...array_map('strval', $tenantIds)
|
|
);
|
|
|
|
if (!is_array($rows)) {
|
|
return [];
|
|
}
|
|
|
|
$result = [];
|
|
foreach ($rows as $row) {
|
|
$item = $this->unwrap($row);
|
|
if (!is_array($item)) {
|
|
continue;
|
|
}
|
|
$tenantId = (int) ($item['tenant_id'] ?? 0);
|
|
if ($tenantId <= 0) {
|
|
continue;
|
|
}
|
|
$result[$tenantId] = $item;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
public function upsertByTenantId(int $tenantId, array $data): bool
|
|
{
|
|
$autoRememberMode = $data['auto_remember_mode'] ?? null;
|
|
$autoRememberModeParam = $autoRememberMode !== null ? (string) (int) $autoRememberMode : null;
|
|
$attributeMap = $data['attribute_map'] ?? '{"first_name":"givenName","last_name":"sn","email":"mail","phone":"telephoneNumber","mobile":"mobile"}';
|
|
if (is_array($attributeMap)) {
|
|
$attributeMap = json_encode($attributeMap, JSON_THROW_ON_ERROR);
|
|
}
|
|
|
|
$result = DB::update(
|
|
'insert into tenant_auth_ldap (tenant_id, enabled, enforce_ldap_login, host, port, encryption_mode, verify_tls_certificate, base_dn, bind_dn_enc, bind_password_enc, user_search_filter, user_search_scope, bind_method, bind_username_format, unique_id_attribute, attribute_map, sync_profile_on_login, sync_profile_fields, allowed_domains, auto_remember_mode, network_timeout, created) values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,NOW()) ' .
|
|
'on duplicate key update enabled = values(enabled), enforce_ldap_login = values(enforce_ldap_login), host = values(host), port = values(port), encryption_mode = values(encryption_mode), verify_tls_certificate = values(verify_tls_certificate), base_dn = values(base_dn), bind_dn_enc = values(bind_dn_enc), bind_password_enc = values(bind_password_enc), ' .
|
|
'user_search_filter = values(user_search_filter), user_search_scope = values(user_search_scope), bind_method = values(bind_method), bind_username_format = values(bind_username_format), unique_id_attribute = values(unique_id_attribute), attribute_map = values(attribute_map), ' .
|
|
'sync_profile_on_login = values(sync_profile_on_login), sync_profile_fields = values(sync_profile_fields), allowed_domains = values(allowed_domains), auto_remember_mode = values(auto_remember_mode), network_timeout = values(network_timeout), modified = NOW()',
|
|
(string) $tenantId,
|
|
!empty($data['enabled']) ? '1' : '0',
|
|
!empty($data['enforce_ldap_login']) ? '1' : '0',
|
|
$data['host'] ?? '',
|
|
(string) (int) ($data['port'] ?? 389),
|
|
$data['encryption_mode'] ?? 'starttls',
|
|
!empty($data['verify_tls_certificate']) ? '1' : '0',
|
|
$data['base_dn'] ?? '',
|
|
$data['bind_dn_enc'] ?? null,
|
|
$data['bind_password_enc'] ?? null,
|
|
$data['user_search_filter'] ?? '(&(objectClass=user)(sAMAccountName=%s))',
|
|
$data['user_search_scope'] ?? 'sub',
|
|
$data['bind_method'] ?? 'search_then_bind',
|
|
$data['bind_username_format'] ?? null,
|
|
$data['unique_id_attribute'] ?? 'objectGUID',
|
|
$attributeMap,
|
|
!empty($data['sync_profile_on_login']) ? '1' : '0',
|
|
$data['sync_profile_fields'] ?? null,
|
|
$data['allowed_domains'] ?? null,
|
|
$autoRememberModeParam,
|
|
(string) (int) ($data['network_timeout'] ?? 5)
|
|
);
|
|
|
|
return $result !== false;
|
|
}
|
|
|
|
public function findEnabledWithExplicitDomain(string $domain): array
|
|
{
|
|
$domain = strtolower(trim($domain));
|
|
if ($domain === '') {
|
|
return [];
|
|
}
|
|
|
|
$rows = DB::select(
|
|
"select tenant_id, allowed_domains from tenant_auth_ldap where enabled = 1 and allowed_domains is not null and allowed_domains != '' and FIND_IN_SET(?, allowed_domains) > 0",
|
|
$domain
|
|
);
|
|
|
|
if (!is_array($rows)) {
|
|
return [];
|
|
}
|
|
|
|
$result = [];
|
|
foreach ($rows as $row) {
|
|
$item = $this->unwrap($row);
|
|
if (!is_array($item)) {
|
|
continue;
|
|
}
|
|
$tenantId = (int) ($item['tenant_id'] ?? 0);
|
|
if ($tenantId <= 0) {
|
|
continue;
|
|
}
|
|
$result[] = ['tenant_id' => $tenantId, 'allowed_domains' => (string) ($item['allowed_domains'] ?? '')];
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
}
|