Как принудительно сменить пароль с помощью FOSUserBundle?

Я пытаюсь реализовать функцию безопасности в проекте Symfony 2.1, где администратор может создать пользователя с начальным паролем, а затем, когда пользователь регистрируется в первый раз, обработчик смены пароля автоматически запускается.

У меня проблемы с переопределением классов FOSUserBundle, и я думаю, что это, безусловно, уже построено каким-то образом, по крайней мере частично, хотя я не вижу его в документах где угодно.

Я хотел бы использовать флаг credentials_expired в сущности. Когда администратор создает пользователя, это будет установлено в 1. Когда пользователь впервые войдет в систему, проверяется учетная запись credentials_expired и вместо того, чтобы бросать исключение, сменяется пароль смены пароля. Я сделал это так далеко.

ChangePasswordController затем будет убедиться, что пароль был фактически изменен (это не похоже на поведение по умолчанию в FOS), а credentials_expired - на 0. Это то место, где я застрял. Есть так много слоев услуг, которые я, похоже, не могу правильно настроить.

Ответ 1

Вот подробный ответ. Спасибо Ману за трамплин!

Во-первых, убедитесь, что в файле composer.json правильно установлен FOSUserBundle ( "dev-master", NOT "*" ):

"friendsofsymfony/user-bundle":"dev-master"

В моем собственном пакете пользователей содержится все, что расширяет FOSUserBundle, как указано в установочном документе.

PortalFlare/Bundle/UserBundle/Resources/config/services.xml:

 
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services  http://symfony.com/schema/dic/services/services-1.0.xsd">

<parameters>
    <parameter key="portal_flare_user.forcepasswordchange.class">PortalFlare\Bundle\UserBundle\EventListener\ForcePasswordChange</parameter>
    <parameter key="portal_flare_user.passwordchangelistener.class">PortalFlare\Bundle\UserBundle\EventListener\PasswordChangeListener</parameter>
</parameters>

<services>
    <service id="portal_flare_user.forcepasswordchange" class="%portal_flare_user.forcepasswordchange.class%">
        <argument type="service" id="router" />
        <argument type="service" id="security.context" />
        <argument type="service" id="session" />
        <tag name="kernel.event_listener" event="kernel.request" method="onCheckStatus" priority="1" />
    </service>
    <service id="portal_flare_user.passwordchange" class="%portal_flare_user.passwordchangelistener.class%">
        <argument type="service" id="router" />
        <argument type="service" id="security.context" />
        <argument type="service" id="fos_user.user_manager" />
        <tag name="kernel.event_subscriber" />
    </service>
</services>

</container>

PortalFlare/Пачка/UserBundle/EventListener/ForcePasswordChange.php:

    <?php

namespace PortalFlare\Bundle\UserBundle\EventListener;

use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;

use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\HttpFoundation\Session\Session;

/**
 * @Service("request.set_messages_count_listener")
 *
 */
class ForcePasswordChange {

  private $security_context;
  private $router;
  private $session;

  public function __construct(Router $router, SecurityContext $security_context, Session $session) {
    $this->security_context = $security_context;
    $this->router           = $router;
    $this->session          = $session;

  }

  public function onCheckStatus(GetResponseEvent $event) {

    if (($this->security_context->getToken()) && ($this->security_context->isGranted('IS_AUTHENTICATED_FULLY'))) {

      $route_name = $event->getRequest()->get('_route');

      if ($route_name != 'fos_user_change_password') {

        if ($this->security_context->getToken()->getUser()->hasRole('ROLE_FORCEPASSWORDCHANGE')) {

          $response = new RedirectResponse($this->router->generate('fos_user_change_password'));
          $this->session->setFlash('notice', "Your password has expired. Please change it.");
          $event->setResponse($response);

        }

      }

    }

  }

} 

PortalFlare/Пачка/UserBundle/EventListener/PasswordChangeListener.php:

<?php
namespace PortalFlare\Bundle\UserBundle\EventListener;

use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use FOS\UserBundle\Doctrine\UserManager;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\SecurityContext;

/**
 * Listener responsible to change the redirection at the end of the password change
 */
class PasswordChangeListener implements EventSubscriberInterface {
  private $security_context;
  private $router;
  private $usermanager;

  public function __construct(UrlGeneratorInterface $router, SecurityContext $security_context, UserManager $usermanager) {
    $this->security_context = $security_context;
    $this->router           = $router;
    $this->usermanager      = $usermanager;
  }

  /**
   * {@inheritDoc}
   */
  public static function getSubscribedEvents() {
    return array(
      FOSUserEvents::CHANGE_PASSWORD_SUCCESS => 'onChangePasswordSuccess',
    );
  }

  public function onChangePasswordSuccess(FormEvent $event) {

    $user = $this->security_context->getToken()->getUser();
    $user->removeRole('ROLE_FORCEPASSWORDCHANGE');
    $this->usermanager->updateUser($user);

    $url = $this->router->generate('_welcome');
    $event->setResponse(new RedirectResponse($url));
  }
}

Проблема с FOSUserBundle на самом деле не гарантирует, что пользователь изменяет пароль, является проблемой еще на один день.

Я надеюсь, что это поможет кому-то.

Ответ 2

Хорошим подходом может быть запуск ROLE_USER, который будет иметь роль, доступную для всего приложения. Когда пользователь регистрируется, он автоматически добавляет ему ROLE_CREDENTIAL_EXPIRED или что-то в этом роде. Используя JMSSecurityExtraBundle, вы можете использовать аннотации в своем контроллере, решая, смогут ли пользователи с данной ролью иметь доступ. Проверьте также документы о том, как Symfony обрабатывает HTTP-аутентификацию.