Skip to content
This repository has been archived by the owner on Jun 29, 2022. It is now read-only.

AutoLoginMiddleware #218 #235

Merged
merged 39 commits into from May 6, 2020
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
4092e59
AutoLoginMiddleware #218
mapeveri Apr 1, 2020
08ba4fd
Fix types #218
mapeveri Apr 3, 2020
00c872e
More tests and general adjustments #218
mapeveri Apr 4, 2020
d5f52bf
Adjustments in methods and tests #218
mapeveri Apr 4, 2020
6d2685d
Change name of method and fix description #218
mapeveri Apr 6, 2020
f1654e2
Logger interface for warning in error authentication #218
mapeveri Apr 7, 2020
61632e1
Cookie login #218
mapeveri Apr 8, 2020
a07ecbc
Fix style #218
mapeveri Apr 8, 2020
e7de628
Remove cookie remeber in logout #218
mapeveri Apr 11, 2020
80442d2
Change to DateTimeImmutable and fix tests #218
mapeveri Apr 12, 2020
a1841aa
Improvements tests #218
mapeveri Apr 12, 2020
02558b9
Fix typos in exception messages, remove unused import
samdark Apr 16, 2020
47969ef
Response cookie #218
mapeveri Apr 18, 2020
b6ccd4a
Fix type hinting #218
mapeveri Apr 18, 2020
0447c0b
Merge branch 'master' of https://github.com/yiisoft/yii-web
mapeveri Apr 18, 2020
6a33a34
Fix remove cookie #218
mapeveri Apr 18, 2020
ff1099d
Merge branch 'master' of https://github.com/yiisoft/yii-web
mapeveri Apr 25, 2020
24a3a6f
Remove IdentityInterface from User #218
mapeveri Apr 25, 2020
f46eeb8
Rename AuthenticationKeyInterface -> AutoLoginIdentityInterface
samdark May 5, 2020
be8f65b
Lay out feature structure
samdark May 5, 2020
5f39cab
Merge branch 'master' into master-mapeveri
samdark May 5, 2020
9e111ec
Adjust AutoLogin to use new cookie methods
samdark May 5, 2020
35d2427
Adjust cookie name
samdark May 5, 2020
7a1be2b
Rename variable
samdark May 5, 2020
bba7ab6
Throw exception if repository does not return an interface needed
samdark May 5, 2020
9a82efd
Cleanup, adjust tests
samdark May 5, 2020
9f1dc6a
Use better names
samdark May 6, 2020
d3b128f
Fixes
samdark May 6, 2020
9386ec5
Make duration required argument of AutoLogin
samdark May 6, 2020
ecb4587
Automatically deal with Cookie in AutoLoginMiddleware
samdark May 6, 2020
6dfc980
Fix tests
samdark May 6, 2020
af47ab7
Add AutoLogin tests, fix code
samdark May 6, 2020
53c0d03
Fix code style
samdark May 6, 2020
d70bde1
Add strict types
samdark May 6, 2020
a93c8d6
Finalize AutoLogin, add test stubs
samdark May 6, 2020
ada9831
Remove tests stubs I have no idea on how to implement :(
samdark May 6, 2020
4db5e3a
Merge branch 'master' into master-mapeveri
samdark May 6, 2020
72d71f1
Do not encode cookie manually
samdark May 6, 2020
e89d374
Add strict types
samdark May 6, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
38 changes: 0 additions & 38 deletions src/User/AuthenticationKeyInterface.php

This file was deleted.

66 changes: 66 additions & 0 deletions src/User/AutoLogin.php
@@ -0,0 +1,66 @@
<?php
samdark marked this conversation as resolved.
Show resolved Hide resolved


namespace Yiisoft\Yii\Web\User;

use DateInterval;
use Psr\Http\Message\ResponseInterface;
use Yiisoft\Yii\Web\Cookie;

/**
* The service is used to send or remove auto-login cookie.
*
* @see AutoLoginIdentityInterface
* @see AutoLoginMiddleware
*/
class AutoLogin
{
private string $cookieName = 'autoLogin';
private DateInterval $duration;

public function __construct(DateInterval $duration)
{
$this->duration = $duration;
}

public function withCookieName(string $name): self
{
$new = clone $this;
$new->cookieName = $name;
return $new;
}

/**
* Add auto-login cookie to response so the user is logged in automatically based on cookie even if session
* is expired.
*
* @param AutoLoginIdentityInterface $identity
* @param ResponseInterface $response Response to handle
*/
public function addCookie(AutoLoginIdentityInterface $identity, ResponseInterface $response): void
{
$data = json_encode([
$identity->getId(),
$identity->getAutoLoginKey()
], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

(new Cookie($this->cookieName, $data))
->withMaxAge($this->duration)
->addToResponse($response);
}

/**
* Expire auto-login cookie so user is not logged in automatically anymore.
*/
public function expireCookie(ResponseInterface $response): void
{
(new Cookie($this->cookieName))
->expire()
->addToResponse($response);
}

public function getCookieName(): string
{
return $this->cookieName;
}
}
42 changes: 42 additions & 0 deletions src/User/AutoLoginIdentityInterface.php
@@ -0,0 +1,42 @@
<?php

namespace Yiisoft\Yii\Web\User;

use Yiisoft\Auth\IdentityInterface;

/**
* AutoLoginIdentityInterface should be implemented in order to automatically log user in based on a cookie.
*
* @see AutoLogin
* @see AutoLoginMiddleware
*/
interface AutoLoginIdentityInterface extends IdentityInterface
{
/**
* Returns a key that can be used to check the validity of a given identity ID.
*
* The key should be unique for each individual user, and should be persistent
* so that it can be used to check the validity of the user identity.
*
* The space of such keys should be big enough to defeat potential identity attacks.
*
* The returned key will be stored on the client side as part of a cookie and will be used to authenticate user even
* if PHP session has been expired.
*
* Make sure to invalidate earlier issued keys when you implement force user logout, password change and
* other scenarios, that require forceful access revocation for old sessions.
*
* @return string a key that is used to check the validity of a given identity ID.
* @see validateAutoLoginKey()
*/
public function getAutoLoginKey(): string;

/**
* Validates the given key.
*
* @param string $key the given key
* @return bool whether the given key is valid.
* @see getAutoLoginKey()
*/
public function validateAutoLoginKey(string $key): bool;
}
91 changes: 87 additions & 4 deletions src/User/AutoLoginMiddleware.php
@@ -1,16 +1,99 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Yii\Web\User;

use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Log\LoggerInterface;
use Yiisoft\Auth\IdentityRepositoryInterface;

/**
* AutoLoginMiddleware automatically logs user in based on "remember me" cookie
* AutoLoginMiddleware automatically logs user in based on cookie.
*/
class AutoLoginMiddleware
final class AutoLoginMiddleware implements MiddlewareInterface
{
private User $user;
private IdentityRepositoryInterface $identityRepository;
private LoggerInterface $logger;
private AutoLogin $autoLogin;

public function __construct(User $user)
{
public function __construct(
User $user,
IdentityRepositoryInterface $identityRepository,
LoggerInterface $logger,
AutoLogin $autoLogin
) {
$this->user = $user;
$this->identityRepository = $identityRepository;
$this->logger = $logger;
$this->autoLogin = $autoLogin;
}

public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
samdark marked this conversation as resolved.
Show resolved Hide resolved
{
$this->authenticateUserByCookieFromRequest($request);
$guestBeforeHandle = $this->user->isGuest();
$response = $handler->handle($request);
samdark marked this conversation as resolved.
Show resolved Hide resolved
$guestAfterHandle = $this->user->isGuest();

if ($guestBeforeHandle && !$guestAfterHandle) {
$this->autoLogin->addCookie($this->user->getIdentity(false), $response);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not enough tests

изображение

}

if (!$guestBeforeHandle && $guestAfterHandle) {
$this->autoLogin->expireCookie($response);
}

return $response;
}

/**
* Authenticate user by auto login cookie from request.
*
* @param ServerRequestInterface $request Request instance containing auto login cookie.
*/
private function authenticateUserByCookieFromRequest(ServerRequestInterface $request): void
roxblnfk marked this conversation as resolved.
Show resolved Hide resolved
{
$cookieName = $this->autoLogin->getCookieName();
$cookies = $request->getCookieParams();

if (!array_key_exists($cookieName, $cookies)) {
return;
}

try {
$data = json_decode($cookies[$cookieName], true, 512, JSON_THROW_ON_ERROR);
} catch (\Exception $e) {
samdark marked this conversation as resolved.
Show resolved Hide resolved
$this->logger->warning('Unable to authenticate user by cookie. Invalid cookie.');
return;
}

if (!is_array($data) || count($data) !== 2) {
$this->logger->warning('Unable to authenticate user by cookie. Invalid cookie.');
return;
}

[$id, $key] = $data;
$identity = $this->identityRepository->findIdentity($id);

if ($identity === null) {
$this->logger->warning("Unable to authenticate user by cookie. Identity \"$id\" not found.");
return;
}

if (!$identity instanceof AutoLoginIdentityInterface) {
throw new \RuntimeException('Identity repository must return an instance of \Yiisoft\Yii\Web\User\AutoLoginIdentityInterface in order for auto-login to function.');
}

if (!$identity->validateAutoLoginKey($key)) {
$this->logger->warning('Unable to authenticate user by cookie. Invalid key.');
return;
}

$this->user->login($identity);
}
}
9 changes: 1 addition & 8 deletions src/User/Event/AfterLogin.php
Expand Up @@ -7,21 +7,14 @@
class AfterLogin
samdark marked this conversation as resolved.
Show resolved Hide resolved
{
private IdentityInterface $identity;
private int $duration;

public function __construct(IdentityInterface $identity, int $duration)
public function __construct(IdentityInterface $identity)
{
$this->identity = $identity;
$this->duration = $duration;
}

public function getIdentity(): IdentityInterface
{
return $this->identity;
}

public function getDuration(): int
{
return $this->duration;
}
}
9 changes: 1 addition & 8 deletions src/User/Event/BeforeLogin.php
Expand Up @@ -7,13 +7,11 @@
class BeforeLogin
samdark marked this conversation as resolved.
Show resolved Hide resolved
{
private IdentityInterface $identity;
private int $duration;
private bool $isValid = true;

public function __construct(IdentityInterface $identity, int $duration)
public function __construct(IdentityInterface $identity)
{
$this->identity = $identity;
$this->duration = $duration;
}

public function invalidate(): void
Expand All @@ -30,9 +28,4 @@ public function getIdentity(): IdentityInterface
{
return $this->identity;
}

public function getDuration(): int
{
return $this->duration;
}
}