2016-10-06 15:16:20 -07:00
< ? php
namespace Drupal\user\Controller ;
use Drupal\Core\Access\CsrfTokenGenerator ;
use Drupal\Core\Controller\ControllerBase ;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface ;
use Drupal\Core\Flood\FloodInterface ;
use Drupal\Core\Routing\RouteProviderInterface ;
use Drupal\user\UserAuthInterface ;
use Drupal\user\UserInterface ;
use Drupal\user\UserStorageInterface ;
use Symfony\Component\DependencyInjection\ContainerInterface ;
use Symfony\Component\HttpFoundation\Request ;
use Symfony\Component\HttpFoundation\Response ;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException ;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException ;
use Symfony\Component\Serializer\Encoder\JsonEncoder ;
use Symfony\Component\Serializer\Serializer ;
/**
* Provides controllers for login , login status and logout via HTTP requests .
*/
class UserAuthenticationController extends ControllerBase implements ContainerInjectionInterface {
/**
* String sent in responses , to describe the user as being logged in .
*
* @ var string
*/
const LOGGED_IN = 1 ;
/**
* String sent in responses , to describe the user as being logged out .
*
* @ var string
*/
const LOGGED_OUT = 0 ;
/**
* The flood controller .
*
* @ var \Drupal\Core\Flood\FloodInterface
*/
protected $flood ;
/**
* The user storage .
*
* @ var \Drupal\user\UserStorageInterface
*/
protected $userStorage ;
/**
* The CSRF token generator .
*
* @ var \Drupal\Core\Access\CsrfTokenGenerator
*/
protected $csrfToken ;
/**
* The user authentication .
*
* @ var \Drupal\user\UserAuthInterface
*/
protected $userAuth ;
/**
* The route provider .
*
* @ var \Drupal\Core\Routing\RouteProviderInterface
*/
protected $routeProvider ;
/**
* The serializer .
*
* @ var \Symfony\Component\Serializer\Serializer
*/
protected $serializer ;
/**
* The available serialization formats .
*
* @ var array
*/
protected $serializerFormats = [];
/**
* Constructs a new UserAuthenticationController object .
*
* @ param \Drupal\Core\Flood\FloodInterface $flood
* The flood controller .
* @ param \Drupal\user\UserStorageInterface $user_storage
* The user storage .
* @ param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
* The CSRF token generator .
* @ param \Drupal\user\UserAuthInterface $user_auth
* The user authentication .
* @ param \Drupal\Core\Routing\RouteProviderInterface $route_provider
* The route provider .
* @ param \Symfony\Component\Serializer\Serializer $serializer
* The serializer .
* @ param array $serializer_formats
* The available serialization formats .
*/
public function __construct ( FloodInterface $flood , UserStorageInterface $user_storage , CsrfTokenGenerator $csrf_token , UserAuthInterface $user_auth , RouteProviderInterface $route_provider , Serializer $serializer , array $serializer_formats ) {
$this -> flood = $flood ;
$this -> userStorage = $user_storage ;
$this -> csrfToken = $csrf_token ;
$this -> userAuth = $user_auth ;
$this -> serializer = $serializer ;
$this -> serializerFormats = $serializer_formats ;
$this -> routeProvider = $route_provider ;
}
/**
* { @ inheritdoc }
*/
public static function create ( ContainerInterface $container ) {
if ( $container -> hasParameter ( 'serializer.formats' ) && $container -> has ( 'serializer' )) {
$serializer = $container -> get ( 'serializer' );
$formats = $container -> getParameter ( 'serializer.formats' );
}
else {
$formats = [ 'json' ];
$encoders = [ new JsonEncoder ()];
$serializer = new Serializer ([], $encoders );
}
return new static (
$container -> get ( 'flood' ),
$container -> get ( 'entity_type.manager' ) -> getStorage ( 'user' ),
$container -> get ( 'csrf_token' ),
$container -> get ( 'user.auth' ),
$container -> get ( 'router.route_provider' ),
$serializer ,
$formats
);
}
/**
* Logs in a user .
*
* @ param \Symfony\Component\HttpFoundation\Request $request
* The request .
*
* @ return \Symfony\Component\HttpFoundation\Response
* A response which contains the ID and CSRF token .
*/
public function login ( Request $request ) {
$format = $this -> getRequestFormat ( $request );
$content = $request -> getContent ();
$credentials = $this -> serializer -> decode ( $content , $format );
if ( ! isset ( $credentials [ 'name' ]) && ! isset ( $credentials [ 'pass' ])) {
throw new BadRequestHttpException ( 'Missing credentials.' );
}
if ( ! isset ( $credentials [ 'name' ])) {
throw new BadRequestHttpException ( 'Missing credentials.name.' );
}
if ( ! isset ( $credentials [ 'pass' ])) {
throw new BadRequestHttpException ( 'Missing credentials.pass.' );
}
$this -> floodControl ( $request , $credentials [ 'name' ]);
if ( $this -> userIsBlocked ( $credentials [ 'name' ])) {
throw new BadRequestHttpException ( 'The user has not been activated or is blocked.' );
}
if ( $uid = $this -> userAuth -> authenticate ( $credentials [ 'name' ], $credentials [ 'pass' ])) {
$this -> flood -> clear ( 'user.http_login' , $this -> getLoginFloodIdentifier ( $request , $credentials [ 'name' ]));
/** @var \Drupal\user\UserInterface $user */
$user = $this -> userStorage -> load ( $uid );
$this -> userLoginFinalize ( $user );
// Send basic metadata about the logged in user.
$response_data = [];
if ( $user -> get ( 'uid' ) -> access ( 'view' , $user )) {
$response_data [ 'current_user' ][ 'uid' ] = $user -> id ();
}
if ( $user -> get ( 'roles' ) -> access ( 'view' , $user )) {
$response_data [ 'current_user' ][ 'roles' ] = $user -> getRoles ();
}
if ( $user -> get ( 'name' ) -> access ( 'view' , $user )) {
$response_data [ 'current_user' ][ 'name' ] = $user -> getAccountName ();
}
$response_data [ 'csrf_token' ] = $this -> csrfToken -> get ( 'rest' );
$logout_route = $this -> routeProvider -> getRouteByName ( 'user.logout.http' );
// Trim '/' off path to match \Drupal\Core\Access\CsrfAccessCheck.
$logout_path = ltrim ( $logout_route -> getPath (), '/' );
$response_data [ 'logout_token' ] = $this -> csrfToken -> get ( $logout_path );
$encoded_response_data = $this -> serializer -> encode ( $response_data , $format );
return new Response ( $encoded_response_data );
}
$flood_config = $this -> config ( 'user.flood' );
if ( $identifier = $this -> getLoginFloodIdentifier ( $request , $credentials [ 'name' ])) {
$this -> flood -> register ( 'user.http_login' , $flood_config -> get ( 'user_window' ), $identifier );
}
// Always register an IP-based failed login event.
$this -> flood -> register ( 'user.failed_login_ip' , $flood_config -> get ( 'ip_window' ));
throw new BadRequestHttpException ( 'Sorry, unrecognized username or password.' );
}
/**
* Verifies if the user is blocked .
*
* @ param string $name
* The username .
*
* @ return bool
* TRUE if the user is blocked , otherwise FALSE .
*/
protected function userIsBlocked ( $name ) {
return user_is_blocked ( $name );
}
/**
* Finalizes the user login .
*
* @ param \Drupal\user\UserInterface $user
* The user .
*/
protected function userLoginFinalize ( UserInterface $user ) {
user_login_finalize ( $user );
}
/**
* Logs out a user .
*
2017-01-04 16:50:53 -08:00
* @ return \Symfony\Component\HttpFoundation\Response
2016-10-06 15:16:20 -07:00
* The response object .
*/
public function logout () {
$this -> userLogout ();
return new Response ( NULL , 204 );
}
/**
* Logs the user out .
*/
protected function userLogout () {
user_logout ();
}
/**
* Checks whether a user is logged in or not .
*
* @ return \Symfony\Component\HttpFoundation\Response
* The response .
*/
public function loginStatus () {
if ( $this -> currentUser () -> isAuthenticated ()) {
$response = new Response ( self :: LOGGED_IN );
}
else {
$response = new Response ( self :: LOGGED_OUT );
}
$response -> headers -> set ( 'Content-Type' , 'text/plain' );
return $response ;
}
/**
* Gets the format of the current request .
*
* @ param \Symfony\Component\HttpFoundation\Request $request
* The current request .
*
* @ return string
* The format of the request .
*/
protected function getRequestFormat ( Request $request ) {
$format = $request -> getRequestFormat ();
if ( ! in_array ( $format , $this -> serializerFormats )) {
throw new BadRequestHttpException ( " Unrecognized format: $format . " );
}
return $format ;
}
/**
* Enforces flood control for the current login request .
*
* @ param \Symfony\Component\HttpFoundation\Request $request
* The current request .
* @ param string $username
* The user name sent for login credentials .
*/
protected function floodControl ( Request $request , $username ) {
$flood_config = $this -> config ( 'user.flood' );
if ( ! $this -> flood -> isAllowed ( 'user.failed_login_ip' , $flood_config -> get ( 'ip_limit' ), $flood_config -> get ( 'ip_window' ))) {
throw new AccessDeniedHttpException ( 'Access is blocked because of IP based flood prevention.' , NULL , Response :: HTTP_TOO_MANY_REQUESTS );
}
if ( $identifier = $this -> getLoginFloodIdentifier ( $request , $username )) {
// Don't allow login if the limit for this user has been reached.
// Default is to allow 5 failed attempts every 6 hours.
if ( ! $this -> flood -> isAllowed ( 'user.http_login' , $flood_config -> get ( 'user_limit' ), $flood_config -> get ( 'user_window' ), $identifier )) {
if ( $flood_config -> get ( 'uid_only' )) {
$error_message = sprintf ( 'There have been more than %s failed login attempts for this account. It is temporarily blocked. Try again later or request a new password.' , $flood_config -> get ( 'user_limit' ));
}
else {
$error_message = 'Too many failed login attempts from your IP address. This IP address is temporarily blocked.' ;
}
throw new AccessDeniedHttpException ( $error_message , NULL , Response :: HTTP_TOO_MANY_REQUESTS );
}
}
}
/**
* Gets the login identifier for user login flood control .
*
* @ param \Symfony\Component\HttpFoundation\Request $request
* The current request .
* @ param string $username
* The username supplied in login credentials .
*
* @ return string
* The login identifier or if the user does not exist an empty string .
*/
protected function getLoginFloodIdentifier ( Request $request , $username ) {
$flood_config = $this -> config ( 'user.flood' );
$accounts = $this -> userStorage -> loadByProperties ([ 'name' => $username , 'status' => 1 ]);
if ( $account = reset ( $accounts )) {
if ( $flood_config -> get ( 'uid_only' )) {
// Register flood events based on the uid only, so they apply for any
// IP address. This is the most secure option.
$identifier = $account -> id ();
}
else {
// The default identifier is a combination of uid and IP address. This
// is less secure but more resistant to denial-of-service attacks that
// could lock out all users with public user names.
$identifier = $account -> id () . '-' . $request -> getClientIp ();
}
return $identifier ;
}
return '' ;
}
}