mirror of
https://github.com/jbcr/core.git
synced 2026-03-30 12:52:16 +02:00
session info shown on view users
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -28,6 +28,7 @@ security:
|
||||
- Bolt\Security\LoginFormAuthenticator
|
||||
|
||||
logout:
|
||||
handler: Bolt\Security\LogoutListener
|
||||
path: bolt_logout
|
||||
target: bolt_login
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
85
src/Entity/UserAuthToken.php
Normal file
85
src/Entity/UserAuthToken.php
Normal 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;
|
||||
}
|
||||
}
|
||||
50
src/Repository/UserAuthTokenRepository.php
Normal file
50
src/Repository/UserAuthTokenRepository.php
Normal 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()
|
||||
;
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -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')
|
||||
|
||||
28
src/Security/LogoutListener.php
Normal file
28
src/Security/LogoutListener.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 %}
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user