session info shown on view users

This commit is contained in:
Ivo Valchev
2019-11-06 13:39:12 +01:00
parent 1d35f06a52
commit fbff04432f
9 changed files with 265 additions and 2 deletions

View File

@@ -6,6 +6,7 @@ framework:
session:
# With this config, PHP's native session handling is used
handler_id: ~
cookie_lifetime: 20 #expires in 14 days
# When using the HTTP Cache, ESI allows to render page fragments separately
# and with different cache configurations for each fragment
# https://symfony.com/doc/current/book/http_cache.html#edge-side-includes

View File

@@ -28,6 +28,7 @@ security:
- Bolt\Security\LoginFormAuthenticator
logout:
handler: Bolt\Security\LogoutListener
path: bolt_logout
target: bolt_login

View File

@@ -96,6 +96,11 @@ class User implements UserInterface, \Serializable
*/
private $disabled = false;
/**
* @ORM\OneToOne(targetEntity="Bolt\Entity\UserAuthToken", mappedBy="user", cascade={"persist", "remove"})
*/
private $userAuthToken;
public function __construct()
{
}
@@ -290,4 +295,22 @@ class User implements UserInterface, \Serializable
return $this;
}
public function getUserAuthToken(): ?UserAuthToken
{
return $this->userAuthToken;
}
public function setUserAuthToken(?UserAuthToken $userAuthToken): self
{
$this->userAuthToken = $userAuthToken;
// set (or unset) the owning side of the relation if necessary
$newUser = null === $userAuthToken ? null : $this;
if ($userAuthToken->getUser() !== $newUser) {
$userAuthToken->setUser($newUser);
}
return $this;
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace Bolt\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="Bolt\Repository\UserAuthTokenRepository")
*/
class UserAuthToken
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\OneToOne(targetEntity="Bolt\Entity\User", inversedBy="userAuthToken", cascade={"persist"})
*/
private $user;
/**
* @ORM\Column(type="string", length=255)
*/
private $useragent;
/**
* @ORM\Column(type="datetime")
*/
private $validity;
public function getId(): ?int
{
return $this->id;
}
public function getUser(): ?User
{
return $this->user;
}
public function setUser(?User $user): self
{
$this->user = $user;
return $this;
}
public function getUseragent(): ?string
{
return $this->useragent;
}
public function setUseragent(string $useragent): self
{
$this->useragent = $useragent;
return $this;
}
public function getValidity(): ?\DateTimeInterface
{
return $this->validity;
}
public function setValidity(\DateTimeInterface $validity): self
{
$this->validity = $validity;
return $this;
}
public static function factory(User $user, string $useragent, \DateTime $validity): self
{
$userAuthToken = new self();
$userAuthToken->setUser($user);
$userAuthToken->setUseragent($useragent);
$userAuthToken->setValidity($validity);
return $userAuthToken;
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace Bolt\Repository;
use Bolt\Entity\UserAuthToken;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Persistence\ManagerRegistry;
/**
* @method UserAuthToken|null find($id, $lockMode = null, $lockVersion = null)
* @method UserAuthToken|null findOneBy(array $criteria, array $orderBy = null)
* @method UserAuthToken[] findAll()
* @method UserAuthToken[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class UserAuthTokenRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, UserAuthToken::class);
}
// /**
// * @return UserAuthToken[] Returns an array of UserAuthToken objects
// */
/*
public function findByExampleField($value)
{
return $this->createQueryBuilder('u')
->andWhere('u.exampleField = :val')
->setParameter('val', $value)
->orderBy('u.id', 'ASC')
->setMaxResults(10)
->getQuery()
->getResult()
;
}
*/
/*
public function findOneBySomeField($value): ?UserAuthToken
{
return $this->createQueryBuilder('u')
->andWhere('u.exampleField = :val')
->setParameter('val', $value)
->getQuery()
->getOneOrNullResult()
;
}
*/
}

View File

@@ -4,7 +4,11 @@ declare(strict_types=1);
namespace Bolt\Security;
use Bolt\Entity\UserAuthToken;
use Bolt\Repository\UserRepository;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RouterInterface;
@@ -32,12 +36,16 @@ class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
/** @var UserPasswordEncoderInterface */
private $passwordEncoder;
public function __construct(UserRepository $userRepository, RouterInterface $router, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder)
/** @var ObjectManager */
private $em;
public function __construct(UserRepository $userRepository, RouterInterface $router, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder, ObjectManager $em)
{
$this->userRepository = $userRepository;
$this->router = $router;
$this->csrfTokenManager = $csrfTokenManager;
$this->passwordEncoder = $passwordEncoder;
$this->em = $em;
}
protected function getLoginUrl()
@@ -84,6 +92,26 @@ class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
$user = $token->getUser();
if($user->getUserAuthToken())
{
$this->em->remove($user->getUserAuthToken());
$this->em->flush();
}
$user->setLastseenAt(new \DateTime());
$user->setLastIp($request->getClientIp());
$useragent = $request->headers->get('User-Agent');
$sessionLifetime = $request->getSession()->getMetadataBag()->getLifetime();
$expirationTime = (new \DateTime())->modify("+".$sessionLifetime." second");
$userAuthToken = UserAuthToken::factory($user, $useragent, $expirationTime);
$user->setUserAuthToken($userAuthToken);
$this->em->persist($user);
$this->em->flush();
return new RedirectResponse($request->getSession()->get(
'_security.'.$providerKey.'.target_path',
$this->router->generate('bolt_dashboard')

View File

@@ -0,0 +1,28 @@
<?php
namespace Bolt\Security;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface;
class LogoutListener implements LogoutHandlerInterface
{
private $em;
public function __construct(ObjectManager $em)
{
$this->em = $em;
}
public function logout(Request $request, Response $response, TokenInterface $token)
{
$user = $token->getUser();
$this->em->remove($user->getUserAuthToken());
$this->em->flush();
}
}

View File

@@ -33,7 +33,7 @@
<td>{{ iteratedUser.username }}</td>
<td>{{ iteratedUser.email }}</td>
<td><code>{{ iteratedUser.roles|join('</code>, <code>') }}</code></td>
<td>{{ iteratedUser.lastseenAt|default('-') }}</td>
<td>{{ iteratedUser.lastseenAt|date('Y-m-d H:i:s')|default('-') }}</td>
<td>{{ iteratedUser.lastIp|default('12.34.56.78') }}</td>
<td>
{{ macro.buttonlink('action.edit', path('bolt_user_edit', {'id': iteratedUser.id}), 'edit', 'secondary') }}
@@ -54,6 +54,35 @@
{{ macro.buttonlink('action.add_user', path('bolt_user_edit', {'id': 0}), 'user-plus', 'secondary') }}
</p>
<h3>Current sessions</h3>
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col" class="text-nowrap">{{ 'listing.title_username'|trans }}</th>
<th scope="col" class="text-nowrap">{{ 'listing.title_last_seen'|trans }}</th>
<th scope="col" class="text-nowrap">{{ 'listing.title_session_expires'|trans }}</th>
<th scope="col" class="text-nowrap">{{ 'listing.title_ip_address'|trans }}</th>
<th scope="col" class="text-nowrap">{{ 'listing.title_browser'|trans }}</th>
</tr>
</thead>
<tbody>
{% for iteratedUser in users %}
{% if iteratedUser.getUserAuthToken %}
{% set authtoken = iteratedUser.getUserAuthToken %}
<tr>
<td class="text-nowrap">{{ iteratedUser.id }}</td>
<td class="text-nowrap">{{ iteratedUser.username }}</td>
<td class="text-nowrap">{{ iteratedUser.lastseenAt|date('Y-m-d H:i:s')|default('-') }}</td>
<td class="text-nowrap">{{ authtoken.validity|date('Y-m-d H:i:s')|default('-') }}</td>
<td class="text-nowrap">{{ iteratedUser.lastIp|default('12.34.56.78') }}</td>
<td>{{ authtoken.useragent|default('-') }}</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
{% endblock %}

View File

@@ -1977,5 +1977,23 @@
<target>User has been disabled successfully!</target>
</segment>
</unit>
<unit id="Npkio79" name="listing.title_session_expires">
<segment>
<source>listing.title_session_expires</source>
<target>Session expires</target>
</segment>
</unit>
<unit id="mmUDcto" name="listing.title_ip_address">
<segment>
<source>listing.title_ip_address</source>
<target>IP address</target>
</segment>
</unit>
<unit id="x9qAwrz" name="listing.title_browser">
<segment>
<source>listing.title_browser</source>
<target>Browser / platform</target>
</segment>
</unit>
</file>
</xliff>