altcha-1.0.0/altcha.module

altcha.module
<?php

/**
 * @file
 * This module enables ALTCHA functionality.
 */

use AltchaOrg\Altcha\Altcha;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Url;
use Drupal\altcha\Controller\ChallengeController;
use Drupal\altcha\Form\AltchaSettingsForm;

/**
 * Implements hook_theme().
 */
function altcha_theme(): array {
  return [
    'altcha_widget' => [
      'variables' => [
        'attributes' => [],
        'content' => [],
      ],
      'template' => 'altcha-widget',
    ],
  ];
}

/**
 * Implements hook_captcha().
 */
function altcha_captcha(string $op, string $captcha_type = '') {
  switch ($op) {
    case 'list':
      return ['ALTCHA'];

    case 'generate':
      if ($captcha_type === 'ALTCHA') {
        $config = \Drupal::configFactory()->get('altcha.settings');

        $labels = [];
        foreach (AltchaSettingsForm::getLabelMap() as $key => $altcha_info) {
          $labels[$altcha_info['altcha_key']] = $config->get($key) ?: NULL;
        }

        switch ($config->get('integration_type')) {
          case 'sentinel_api':
            $base_url = $config->get('sentinel_api_url');
            $challenge_url = Url::fromUri($base_url . AltchaSettingsForm::SENTINEL_API_CHALLENGE_PATH, [
              'query' => array_filter([
                'apiKey' => $config->get('sentinel_api_key') ?? NULL,
              ]),
            ])->toString();
            break;

          case 'saas_api':
            $region_map = AltchaSettingsForm::getRegionUrlMap();
            $base_url = $region_map[$config->get('saas_api_region')];
            $challenge_url = Url::fromUri($base_url . AltchaSettingsForm::SAAS_API_CHALLENGE_PATH, [
              'query' => array_filter([
                'apiKey' => $config->get('saas_api_key') ?? NULL,
                'maxnumber' => $config->get('max_number') ?? NULL,
              ]),
            ])->toString();
            break;

          case 'self_hosted':
          default:
            $challenge_url = Url::fromRoute('altcha.challenge')->toString();
            break;
        }

        $attributes = [
          'challengeurl' => $challenge_url,
          'maxnumber' => $config->get('max_number') ?? ChallengeController::DEFAULT_MAX_NUMBER,
          'hidelogo' => $config->get('hide_logo') ?? NULL,
          'hidefooter' => $config->get('hide_footer') ?? NULL,
          'expire' => $config->get('expire') ?? NULL,
          'delay' => $config->get('delay') ?? NULL,
          'auto' => $config->get('auto_verification') ?? NULL,
          'strings' => !empty($labels) ? Json::encode(array_filter($labels)) : NULL,
        ];

        if ($config->get('floating_enabled')) {
          $attributes += [
            'floating' => $config->get('floating_mode'),
            'floatingoffset' => $config->get('floating_offset') ?? NULL,
            // The default ALTCHA selector 'input[type="submit"]' will
            // not work on forms with a file upload field. Use the name="op"
            // selector by default instead - this is not an ideal solution,
            // since it won't always work correctly on multistep forms
            // where there are 'next' and 'previous' submit buttons.
            'floatinganchor' => $config->get('floating_anchor') ?: 'input[name="op"]',
          ];
        }

        return [
          'solution' => TRUE,
          // As the validate callback does not depend on sid or solution,
          // this captcha type can be displayed on cached pages.
          'cacheable' => TRUE,
          'form' => [
            'captcha_response' => [
              '#theme' => 'altcha_widget',
              '#attributes' => array_filter($attributes),
              '#attached' => altcha_get_attached_library('altcha/altcha', 'library_override'),
              '#cache' => [
                'tags' => [
                  'config:altcha.settings',
                ],
              ],
            ],
          ],
          'captcha_validate' => 'altcha_captcha_validation',
        ];
      }
  }
}

/**
 * Validate the ALTCHA solution on form submit.
 *
 * @see altcha_captcha
 */
function altcha_captcha_validation($solution, $response, $element, $form_state): bool {
  $hash = \Drupal::request()->get('altcha');
  if (!is_string($hash)) {
    return FALSE;
  }

  $json = base64_decode($hash);
  if (!$json) {
    return FALSE;
  }

  $payload = Json::decode($json);
  $config = \Drupal::configFactory()->get('altcha.settings');
  switch ($config->get('integration_type')) {
    case 'sentinel_api':
      $altcha = new Altcha($config->get('sentinel_api_secret'));
      $result = $altcha->verifyServerSignature($payload);
      return $result->verified;

    case 'saas_api':
    case 'self_hosted':
    default:
      /** @var \Drupal\altcha\SecretManager $secret_manager */
      $secret_manager = \Drupal::service('altcha.secret_manager');
      $altcha = new Altcha($secret_manager->getSecretKey());
      return $altcha->verifySolution($payload);
  }

}

/**
 * Implements template_preprocess_captcha().
 *
 * When floating UI (invisible captcha) is active, hide the CAPTCHA wrapper.
 */
function altcha_preprocess_captcha(&$variables, $hook, $info): void {
  $altcha_config = \Drupal::configFactory()->get('altcha.settings');
  if ($altcha_config->get('floating_enabled')) {
    $variables['is_visible'] = FALSE;
  }
}

/**
 * Retrieves an ALTCHA library to be attached.
 *
 * @param string $default_library
 *   The default library key.
 * @param string $override_config_key
 *   The library override config key within altcha.settings.
 *
 * @return array
 *   The attachments to be added.
 */
function altcha_get_attached_library(string $default_library, string $override_config_key): array {
  $library_override = \Drupal::configFactory()->get('altcha.settings')->get($override_config_key) ?? '';

  // Use the default library.
  if (empty($library_override)) {
    $attached['library'][] = $default_library;
    return $attached;
  }

  /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */
  $file_url_generator = \Drupal::service('file_url_generator');

  /** @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager */
  $stream_wrapper_manager = \Drupal::service('stream_wrapper_manager');

  $drupal_root = \Drupal::root();

  // Check if the path is a valid stream wrapper (e.g., 'public://').
  if ($stream_wrapper_manager->isValidUri($library_override)) {
    $src = $file_url_generator->generateAbsoluteString($library_override);
  }
  // If the path is an external URL, use it directly.
  elseif (filter_var($library_override, FILTER_VALIDATE_URL)) {
    $src = $library_override;
  }
  // If the path is an absolute server path, convert it relative to web root.
  elseif (str_starts_with($library_override, $drupal_root)) {
    $library_override = str_replace("$drupal_root/", '', $library_override);
    $src = $file_url_generator->generateAbsoluteString($library_override);
  }
  else {
    $src = $file_url_generator->generateAbsoluteString($library_override);
  }

  $attached['html_head'][] = [
    [
      '#tag' => 'script',
      '#attributes' => [
        'src' => $src,
        'type' => 'module',
      ],
    ],
    "altcha_$override_config_key",
  ];

  return $attached;
}

/**
 * Implements hook_local_tasks_alter().
 */
function altcha_local_tasks_alter(&$local_tasks): void {
  $local_task_key = 'config_translation.local_tasks:config_translation.item.overview.altcha.settings';
  if (isset($local_tasks[$local_task_key])) {
    // The config_translation module expects the base route to be
    // altcha.settings like it is for other configuration
    // entities. Altcha uses captcha_settings as the base route.
    $local_tasks[$local_task_key]['base_route'] = 'captcha_settings';
  }
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc