Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions app/V1Module/presenters/LoginPresenter.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,17 @@ class LoginPresenter extends BasePresenter
/**
* Sends response with an access token, if the user exists.
* @param User $user
* @param int|null $expiration in seconds, null for default expiration
* @throws AuthenticationException
* @throws ForbiddenRequestException
* @throws InvalidAccessTokenException
*/
private function sendAccessTokenResponse(User $user)
private function sendAccessTokenResponse(User $user, ?int $expiration = null)
{
$token = $this->accessManager->issueToken($user, null, [TokenScope::MASTER, TokenScope::REFRESH]);
if ($expiration !== null && ($expiration > $this->accessManager->getExpiration() || $expiration <= 0)) {
$expiration = null; // invalid values are ignored
}
$token = $this->accessManager->issueToken($user, null, [TokenScope::MASTER, TokenScope::REFRESH], $expiration);
$this->getUser()->login(new Identity($user, $this->accessManager->decodeToken($token)));

$this->sendSuccessResponse(
Expand All @@ -121,11 +125,13 @@ private function sendAccessTokenResponse(User $user)
*/
#[Post("username", new VEmail(), "User's E-mail")]
#[Post("password", new VString(1), "Password")]
#[Post("expiration", new VInt(), "Token expiration in seconds (not greater than the default)", required: false)]
public function actionDefault()
{
$req = $this->getRequest();
$username = $req->getPost("username");
$password = $req->getPost("password");
$expiration = $req->getPost("expiration");

$user = $this->credentialsAuthenticator->authenticate($username, $password);
$this->verifyUserIpLock($user);
Expand All @@ -135,7 +141,7 @@ public function actionDefault()
$event = SecurityEvent::createLoginEvent($this->getHttpRequest()->getRemoteAddress(), $user);
$this->securityEvents->persist($event);

$this->sendAccessTokenResponse($user);
$this->sendAccessTokenResponse($user, $expiration);
}

/**
Expand All @@ -148,6 +154,7 @@ public function actionDefault()
* @throws BadRequestException
*/
#[Post("token", new VString(1), "JWT external authentication token")]
#[Post("expiration", new VInt(), "Token expiration in seconds (not greater than the default)", required: false)]
#[Path("authenticatorName", new VString(), "Identifier of the external authenticator", required: true)]
public function actionExternal($authenticatorName)
{
Expand All @@ -160,7 +167,7 @@ public function actionExternal($authenticatorName)
$event = SecurityEvent::createExternalLoginEvent($this->getHttpRequest()->getRemoteAddress(), $user);
$this->securityEvents->persist($event);

$this->sendAccessTokenResponse($user);
$this->sendAccessTokenResponse($user, $req->getPost("expiration"));
}

public function checkTakeOver($userId)
Expand Down
1 change: 1 addition & 0 deletions app/V1Module/presenters/UsersPresenter.php
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,7 @@ public function actionInvalidateTokens(string $id)

$this->sendSuccessResponse(
[
"user" => $this->userViewFactory->getUser($user),
"accessToken" => $user === $this->getCurrentUser() ? $this->accessManager->issueRefreshedToken(
$token
) : null
Expand Down
104 changes: 96 additions & 8 deletions tests/Presenters/LoginPresenter.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ class TestLoginPresenter extends Tester\TestCase
$events = $this->presenter->securityEvents->findAll();
Assert::count(0, $events);

$request = new Request(
$payload = PresenterTestHelper::performPresenterRequest(
$this->presenter,
"V1:Login",
"POST",
["action" => "default"],
Expand All @@ -95,16 +96,47 @@ class TestLoginPresenter extends Tester\TestCase
]
);

/** @var JsonResponse $response */
$response = $this->presenter->run($request);
Assert::type(JsonResponse::class, $response);
$result = $response->getPayload();
$user = $this->presenter->users->getByEmail($this->userLogin);
Assert::same($user->getId(), $payload["user"]["id"]);
Assert::true($this->presenter->user->isLoggedIn());

Assert::same(200, $result["code"]);
Assert::true(array_key_exists("accessToken", $result["payload"]));
Assert::same($this->presenter->users->getByEmail($this->userLogin)->getId(), $result["payload"]["user"]["id"]);
Assert::true(array_key_exists("accessToken", $payload));
$token = $this->presenter->accessManager->decodeToken($payload["accessToken"]);
Assert::same($user->getId(), $token->getUserId());
Assert::same($this->presenter->accessManager->getExpiration(), $token->getExpirationTime());

$events = $this->presenter->securityEvents->findAll();
Assert::count(1, $events);
Assert::equal(SecurityEvent::TYPE_LOGIN, $events[0]->getType());
Assert::equal($this->presenter->user->getId(), $events[0]->getUser()->getId());
}

public function testLoginRestrictedExpiration()
{
$events = $this->presenter->securityEvents->findAll();
Assert::count(0, $events);

$payload = PresenterTestHelper::performPresenterRequest(
$this->presenter,
"V1:Login",
"POST",
["action" => "default"],
[
"username" => $this->userLogin,
"password" => $this->userPassword,
"expiration" => 10,
]
);

$user = $this->presenter->users->getByEmail($this->userLogin);
Assert::same($user->getId(), $payload["user"]["id"]);
Assert::true($this->presenter->user->isLoggedIn());

Assert::true(array_key_exists("accessToken", $payload));
$token = $this->presenter->accessManager->decodeToken($payload["accessToken"]);
Assert::same($user->getId(), $token->getUserId());
Assert::same(10, $token->getExpirationTime());

$events = $this->presenter->securityEvents->findAll();
Assert::count(1, $events);
Assert::equal(SecurityEvent::TYPE_LOGIN, $events[0]->getType());
Expand Down Expand Up @@ -171,10 +203,66 @@ class TestLoginPresenter extends Tester\TestCase
$result = $response->getPayload();

Assert::same(200, $result["code"]);
Assert::equal($user->getId(), $result["payload"]["user"]["id"]);
Assert::true($this->presenter->user->isLoggedIn());

Assert::true(array_key_exists("accessToken", $result["payload"]));
$token = $this->presenter->accessManager->decodeToken($result["payload"]["accessToken"]);
Assert::same($user->getId(), $token->getUserId());
Assert::same($this->presenter->accessManager->getExpiration(), $token->getExpirationTime());

$events = $this->presenter->securityEvents->findAll();
Assert::count(1, $events);
Assert::equal(SecurityEvent::TYPE_LOGIN_EXTERNAL, $events[0]->getType());
Assert::equal($user->getId(), $events[0]->getUser()->getId());
}

public function testLoginExternalRestrictedExpiration()
{
$events = $this->presenter->securityEvents->findAll();
Assert::count(0, $events);

$authenticator = new ExternalServiceAuthenticator(
[[
'name' => 'test-cas',
'jwtSecret' => 'tajnyRetezec',
]],
$this->externalLogins,
$this->users,
$this->logins,
$this->instances,
$this->emailVerificationHelper,
$this->failureHelper
);

$user = $this->presenter->users->getByEmail($this->userLogin);

$payload = [
'iat' => time(),
'id' => 'external-id-1',
'mail' => $this->userLogin,
'firstName' => $user->getFirstName(),
'lastName' => $user->getLastName(),
];
$token = JWT::encode($payload, 'tajnyRetezec', "HS256");

$this->presenter->externalServiceAuthenticator = $authenticator;

$request = new Request("V1:Login", "POST", ["action" => "external", "authenticatorName" => "test-cas"], ['token' => $token, 'expiration' => 15]);

$response = $this->presenter->run($request);
Assert::type(JsonResponse::class, $response);
$result = $response->getPayload();

Assert::same(200, $result["code"]);
Assert::equal($user->getId(), $result["payload"]["user"]["id"]);
Assert::true($this->presenter->user->isLoggedIn());

Assert::true(array_key_exists("accessToken", $result["payload"]));
$token = $this->presenter->accessManager->decodeToken($result["payload"]["accessToken"]);
Assert::same($user->getId(), $token->getUserId());
Assert::same(15, $token->getExpirationTime());

$events = $this->presenter->securityEvents->findAll();
Assert::count(1, $events);
Assert::equal(SecurityEvent::TYPE_LOGIN_EXTERNAL, $events[0]->getType());
Expand Down