5

I generated flag link

  $flag_link = [
  '#lazy_builder' => ['flag.link_builder:build', [
    $product->getEntityTypeId(),
    $product->id(),
    'product_like',
  ]],
   '#create_placeholder' => TRUE,
];

Flag link is generated successfully. But while I click flag link , I got error message as response

{message: "'csrf_token' URL query argument is invalid."}
message: "'csrf_token' URL query argument is invalid."
Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Arif
  • 195
  • 12
  • 1
    Temporary I solved that issue by modifying modules/contrib/flag/src/Access/CsrfAccessCheck.php Just remove condition : return $this->account->isAnonymous() ? AccessResult::allowed() : $this->original->access($route, $request, $route_match); and add condition: return AccessResult::allowed(); – Arif Nov 18 '20 at 12:04
  • 2
    Above solution is not good. Please any one solve that with proper way.. – Arif Nov 18 '20 at 12:05
  • Drupal core issue: ["nojs"/"ajax" route parameter in use-ajax link breaks CSRF protection](https://www.drupal.org/project/drupal/issues/2670798) – mbomb007 Mar 23 '21 at 14:53

1 Answers1

2

I found a temporary solution. Not sure if this is a bug in Flag that needs to be addressed by the module maintainers, or if this is working as intended, since this is a REST response, and not a typical Drupal call for a view or display mode.

In the ModuleRestResource.php file (In my case, the ModuleRestResource.php file is located at: {{DRUPAL_ROOT}}/web/modules/custom/{{Module_Name}}/src/Plugin/rest/resource/{{Module_Name}}RestResource.php):

use Drupal\rest\ModifiedResourceResponse;
use Drupal\rest\Plugin\ResourceBase;
use Drupal\rest\ResourceResponse;
use Drupal\Core\Entity\EntityTypeManager;
use Drupal\Core\Entity\EntityInterface;
use Drupal\flag\FlagService;
use Drupal\Core\Render\RenderContext;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

class ModuleRestResource extends ResourceBase {

  /**
   * A current user instance.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  protected $currentUser;

  /**
   * @var $entityTypeManager \Drupal\Core\Entity\EntityTypeManager
   */
  protected $entityTypeManager;

  /**
   * @var \Drupal\flag\FlagService
   */
  protected $flagService;

  /**
   * @var Drupal\Core\Access\CsrfTokenGenerator
   */
  protected $csrfService;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
    $instance->logger = $container->get('logger.factory')->get('module');
    $instance->currentUser = $container->get('current_user');
    $instance->entityTypeManager = $container->get('entity_type.manager');
    $instance->flagService = $container->get('flag');
    $instance->csrfService = $container->get('csrf_token');
    return $instance;
  }

  /**
   * Responds to GET requests.
   *
   * @param string $payload
   *
   * @return \Drupal\rest\ResourceResponse
   *   The HTTP response object.
   *
   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
   *   Throws exception expected.
   */
  public function get($payload) {

    // You must to implement the logic of your REST Resource here.
    // Use current user after pass authentication to validate access.
    if (!$this->currentUser->hasPermission('access content')) {
      throw new AccessDeniedHttpException();
    }

    if (!is_numeric($payload)) {
      throw new BadRequestHttpException();
    }

    /*
     * This is the object that will be returned with the node details.
     */
    $obj = new \stdClass();

    // First load our node.
    /**
     * @var \Drupal\Core\Entity\EntityInterface
     */
    $node = $this->entityTypeManager->getStorage('node')->load($payload);

    /**
     * FIX STARTS HERE !!!!!
     */

    /**
     * Because we are rending code early in the process, we need to wrap in executeInRenderContext
     */

    $render_context = new RenderContext();
    $fl = \Drupal::service('renderer')->executeInRenderContext($render_context, function() use ($node, $payload) {

      /**
       * Get the flag we need and check if the selected node has been flagged by the current user
       *
       * Set the path to create a token. This is the value that is missing by default that creates an
       * invalid CSRF Token. Important to note that the leading slash should be left off for token generation
       * and then added to to the links href attribute
       *
       */
      $flag = $this->flagService->getFlagById('bookmark');
      $is_flagged = (bool) $this->flagService->getEntityFlaggings($flag, $node, \Drupal::currentUser() );
      $path = 'flag/'. ($is_flagged ? 'un' : '') .'flag/bookmark/' . $node->id();
      $token = $this->csrfService->get($path);
      $flag_link = $flag->getLinkTypePlugin()->getAsFlagLink($flag, $node);
      $flag_link['#attributes']['href'] = '/' . $path . '?destination&token=' . $token;

      /**
       * Render the link into HTML
       */
      return \Drupal::service('renderer')->render($flag_link);
    });

    /**
     * This is required to bubble metadata
     */
    if (!$render_context->isEmpty()) {
        $bubbleable_metadata = $render_context->pop();
        \Drupal\Core\Render\BubbleableMetadata::createFromObject($fl)
            ->merge($bubbleable_metadata);
    }
    /*
     * !!!!! FIX ENDS HERE !!!!!
     */

    $obj->flag_link = $fl;

    return new ResourceResponse((array)$obj, 200);
  }

}

Anyone who can get module maintainers to address this would be nice.