mirror of
https://gitlab.unicamp.br/infimecc_drupal11_modules/ldap_groups_sync.git
synced 2026-03-09 09:57:41 -03:00
ldap_departments_sync/ e ldap_research_groups_sync/ movidos para modules/, padrão adotado por módulos contrib como Drupal Commerce. Nenhum arquivo PHP ou YAML alterado — o Drupal descobre módulos recursivamente pelo .info.yml independente do caminho. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
292 lines
9.9 KiB
Plaintext
292 lines
9.9 KiB
Plaintext
<?php
|
|
|
|
/**
|
|
* @file
|
|
* Module to synchronize departments from LDAP to groups.
|
|
*/
|
|
|
|
use Drupal\Core\Access\AccessResult;
|
|
use Drupal\Core\Entity\EntityInterface;
|
|
use Drupal\Core\Field\FieldDefinitionInterface;
|
|
use Drupal\Core\Field\FieldItemListInterface;
|
|
use Drupal\Core\Form\FormStateInterface;
|
|
use Drupal\Core\Routing\RouteMatchInterface;
|
|
use Drupal\Core\Session\AccountInterface;
|
|
use Drupal\field\FieldConfigInterface;
|
|
use Drupal\ldap_departments_sync\LdapDepartmentsSync;
|
|
|
|
/**
|
|
* Implements hook_help().
|
|
*/
|
|
function ldap_departments_sync_help($route_name, RouteMatchInterface $route_match) {
|
|
switch ($route_name) {
|
|
case 'help.page.ldap_departments_sync':
|
|
return '<p>' . t('This module synchronizes departments from an LDAP server to groups through cron.') . '</p>';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_cron().
|
|
*/
|
|
function ldap_departments_sync_cron() {
|
|
// Check if module is properly configured
|
|
$config = \Drupal::config('ldap_departments_sync.settings');
|
|
$ldap_server_id = $config->get('ldap_server_id');
|
|
$ldap_query_id = $config->get('ldap_query_id');
|
|
|
|
// Validate LDAP server
|
|
try {
|
|
$entity_type_manager = \Drupal::entityTypeManager();
|
|
$server_storage = $entity_type_manager->getStorage('ldap_server');
|
|
$server = $server_storage->load($ldap_server_id);
|
|
|
|
if (!$server || !$server->get('status')) {
|
|
\Drupal::logger('ldap_departments_sync')->warning('Synchronization cancelled: LDAP server "@server_id" not found or inactive. Configure at /admin/config/local-modules/ldap-departments-sync', [
|
|
'@server_id' => $ldap_server_id,
|
|
]);
|
|
return;
|
|
}
|
|
}
|
|
catch (\Exception $e) {
|
|
\Drupal::logger('ldap_departments_sync')->warning('Synchronization cancelled: error checking LDAP server: @message', [
|
|
'@message' => $e->getMessage(),
|
|
]);
|
|
return;
|
|
}
|
|
|
|
// Validate LDAP query
|
|
try {
|
|
if ($entity_type_manager->hasDefinition('ldap_query_entity')) {
|
|
$query_storage = $entity_type_manager->getStorage('ldap_query_entity');
|
|
$query = $query_storage->load($ldap_query_id);
|
|
|
|
if (!$query || !$query->get('status')) {
|
|
\Drupal::logger('ldap_departments_sync')->warning('Synchronization cancelled: LDAP query "@query_id" not found or inactive. Configure at /admin/config/local-modules/ldap-departments-sync', [
|
|
'@query_id' => $ldap_query_id,
|
|
]);
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
\Drupal::logger('ldap_departments_sync')->warning('Synchronization cancelled: ldap_query module not available.');
|
|
return;
|
|
}
|
|
}
|
|
catch (\Exception $e) {
|
|
\Drupal::logger('ldap_departments_sync')->warning('Synchronization cancelled: error checking LDAP query: @message', [
|
|
'@message' => $e->getMessage(),
|
|
]);
|
|
return;
|
|
}
|
|
|
|
// Validate group type
|
|
try {
|
|
$group_type_id = $config->get('group_type_id');
|
|
$group_type_storage = $entity_type_manager->getStorage('group_type');
|
|
$group_type = $group_type_storage->load($group_type_id);
|
|
|
|
if (!$group_type) {
|
|
\Drupal::logger('ldap_departments_sync')->warning('Synchronization cancelled: group type "@type_id" not found. Configure at /admin/config/local-modules/ldap-departments-sync', [
|
|
'@type_id' => $group_type_id,
|
|
]);
|
|
return;
|
|
}
|
|
}
|
|
catch (\Exception $e) {
|
|
\Drupal::logger('ldap_departments_sync')->warning('Synchronization cancelled: error checking group type: @message', [
|
|
'@message' => $e->getMessage(),
|
|
]);
|
|
return;
|
|
}
|
|
|
|
// Get LDAP synchronization service
|
|
$ldap_sync = \Drupal::service('ldap_departments_sync.sync');
|
|
|
|
// Execute departments synchronization
|
|
try {
|
|
$ldap_sync->syncDepartments();
|
|
\Drupal::logger('ldap_departments_sync')->info('Departments synchronization executed successfully.');
|
|
}
|
|
catch (\Exception $e) {
|
|
\Drupal::logger('ldap_departments_sync')->error('Error in departments synchronization: @message', [
|
|
'@message' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_entity_access().
|
|
*
|
|
* Evaluates access rules configured in the module settings for view, update
|
|
* and delete operations on existing entities.
|
|
*/
|
|
function ldap_departments_sync_entity_access(EntityInterface $entity, $operation, AccountInterface $account) {
|
|
// Skip group-related entities to avoid conflicts with the group module.
|
|
$skip_types = ['group', 'group_content', 'group_relationship', 'group_role', 'group_type'];
|
|
if (in_array($entity->getEntityTypeId(), $skip_types, TRUE)) {
|
|
return AccessResult::neutral();
|
|
}
|
|
|
|
return \Drupal::service('ldap_departments_sync.access_rules')
|
|
->checkAccess($entity, $operation, $account);
|
|
}
|
|
|
|
/**
|
|
* Implements hook_entity_create_access().
|
|
*
|
|
* Evaluates access rules configured in the module settings for create
|
|
* operations on new entities.
|
|
*/
|
|
function ldap_departments_sync_entity_create_access(AccountInterface $account, array $context, $entity_bundle) {
|
|
$entity_type_id = $context['entity_type_id'] ?? '';
|
|
|
|
// Skip group-related entities.
|
|
$skip_types = ['group', 'group_content', 'group_relationship', 'group_role', 'group_type'];
|
|
if (in_array($entity_type_id, $skip_types, TRUE)) {
|
|
return AccessResult::neutral();
|
|
}
|
|
|
|
return \Drupal::service('ldap_departments_sync.access_rules')
|
|
->checkCreateAccess($account, $entity_type_id, $entity_bundle ?? '');
|
|
}
|
|
|
|
/**
|
|
* Implements hook_entity_field_access().
|
|
*
|
|
* Nega acesso de edição aos campos gerenciados pelo LDAP sync em formulários
|
|
* e APIs (REST, JSON:API). Saves programáticos não são afetados.
|
|
*
|
|
* Campos protegidos no usuário:
|
|
* - field_user_category: categoria do usuário, populado pelo ldap_user.
|
|
* - field_user_dept_code: código do departamento, populado pelo ldap_user.
|
|
* - field_user_department: referência ao grupo, populado pelo nosso sync.
|
|
* Proteger este campo evita o erro de validação "Esta entidade não pode
|
|
* ser referenciada" que ocorre quando o widget tenta validar o grupo
|
|
* referenciado contra o acesso do usuário atual.
|
|
* - field_user_work_phone: telefone de trabalho, populado pelo ldap_user.
|
|
*/
|
|
function ldap_departments_sync_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, ?FieldItemListInterface $items = NULL) {
|
|
$protected_user_fields = [
|
|
'field_user_category',
|
|
'field_user_dept_code',
|
|
'field_user_department',
|
|
'field_user_work_phone',
|
|
];
|
|
|
|
if (
|
|
$field_definition->getTargetEntityTypeId() === 'user' &&
|
|
in_array($field_definition->getName(), $protected_user_fields, TRUE) &&
|
|
$operation === 'edit'
|
|
) {
|
|
// Sempre nega edição via UI/API, inclusive para o uid 1.
|
|
// Saves programáticos (ldap_user, drush) não passam por este hook.
|
|
return AccessResult::forbidden('This field is managed exclusively by LDAP synchronization.');
|
|
}
|
|
return AccessResult::neutral();
|
|
}
|
|
|
|
/**
|
|
* Implements hook_user_presave().
|
|
*
|
|
* Protege os campos gerenciados pelo LDAP sync de alterações programáticas
|
|
* não autorizadas em usuários existentes. Permite:
|
|
* - Saves durante sync LDAP autorizado (LdapDepartmentsSync::$syncing TRUE)
|
|
* - Criação de novos usuários (provisionamento inicial via ldap_user)
|
|
* - Salvas por usuários com a permissão 'edit ldap managed user fields'
|
|
*/
|
|
function ldap_departments_sync_user_presave(\Drupal\user\UserInterface $user) {
|
|
// Permite durante qualquer sync LDAP autorizado.
|
|
if (LdapDepartmentsSync::isSyncing()) {
|
|
return;
|
|
}
|
|
|
|
// Permite na criação inicial do usuário (primeiro login LDAP).
|
|
if ($user->isNew()) {
|
|
return;
|
|
}
|
|
|
|
// Permite se o usuário atual tem a permissão de bypass.
|
|
if (\Drupal::currentUser()->hasPermission('edit ldap managed user fields')) {
|
|
return;
|
|
}
|
|
|
|
$original = $user->original;
|
|
if ($original === NULL) {
|
|
return;
|
|
}
|
|
|
|
// Campos gerenciados pelo LDAP que não podem ser alterados externamente.
|
|
$protected_fields = [
|
|
'field_user_category',
|
|
'field_user_dept_code',
|
|
'field_user_department',
|
|
'field_user_work_phone',
|
|
];
|
|
|
|
foreach ($protected_fields as $field_name) {
|
|
if (!$user->hasField($field_name)) {
|
|
continue;
|
|
}
|
|
|
|
$original_value = $original->get($field_name)->getValue();
|
|
$new_value = $user->get($field_name)->getValue();
|
|
|
|
if ($original_value !== $new_value) {
|
|
$user->set($field_name, $original_value);
|
|
\Drupal::logger('ldap_departments_sync')->warning(
|
|
'Tentativa não autorizada de alterar @field do usuário @username foi bloqueada. Use a sincronização LDAP para atualizar este campo.',
|
|
[
|
|
'@field' => $field_name,
|
|
'@username' => $user->getAccountName(),
|
|
]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_ENTITY_TYPE_predelete() for user entities.
|
|
*
|
|
* Cleans up user references in department groups when a user is deleted.
|
|
*/
|
|
function ldap_departments_sync_user_predelete(\Drupal\user\UserInterface $user) {
|
|
$config = \Drupal::config('ldap_departments_sync.settings');
|
|
$group_type_id = $config->get('group_type_id') ?: 'departments';
|
|
|
|
// Fields that may contain user references
|
|
$user_reference_fields = [
|
|
'field_dept_coord',
|
|
'field_dept_coord_assoc',
|
|
];
|
|
|
|
try {
|
|
$group_storage = \Drupal::entityTypeManager()->getStorage('group');
|
|
$user_id = $user->id();
|
|
|
|
// Find groups that reference this user
|
|
foreach ($user_reference_fields as $field_name) {
|
|
$groups = $group_storage->loadByProperties([
|
|
'type' => $group_type_id,
|
|
$field_name => $user_id,
|
|
]);
|
|
|
|
foreach ($groups as $group) {
|
|
if ($group->hasField($field_name)) {
|
|
$group->set($field_name, NULL);
|
|
$group->save();
|
|
\Drupal::logger('ldap_departments_sync')->info('Removed user @username reference from @field in group @group', [
|
|
'@username' => $user->getAccountName(),
|
|
'@field' => $field_name,
|
|
'@group' => $group->label(),
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (\Exception $e) {
|
|
\Drupal::logger('ldap_departments_sync')->error('Error cleaning up user references: @message', [
|
|
'@message' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|