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
7 changes: 7 additions & 0 deletions src/main/php/web/auth/Context.class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php namespace web\auth;

abstract class Context {

/** Logs out the user. Always returns true */
public abstract function logout(): bool;
}
10 changes: 7 additions & 3 deletions src/main/php/web/auth/SessionBased.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ private function authorize($session, $result) {
* Executes authentication flow. On success, the user is looked up and
* registered in the session under a key "user".
*
* @param web.Request $request
* @param web.Response $response
* @param web.Request $req
* @param web.Response $res
* @param web.filters.Invocation
* @return var
*/
Expand Down Expand Up @@ -96,6 +96,10 @@ public function filter($req, $res, $invocation) {
$session->transmit($res);
}

return $invocation->proceed($req->pass('user', $user)->pass('token', $token), $res);
$context= new SessionContext($session, $req, $res);
return $invocation->proceed(
$req->pass('context', $context)->pass('user', $user)->pass('token', $token),
$res
);
}
}
45 changes: 45 additions & 0 deletions src/main/php/web/auth/SessionContext.class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php namespace web\auth;

use web\session\ISession;
use web\{Request, Response};

class SessionContext extends Context {
private $session, $request, $response;

/** Creates a new session context */
public function __construct(ISession $session, Request $request, Response $response) {
$this->session= $session;
$this->request= $request;
$this->response= $response;
}

public function user() {
return ($this->session->value('auth') ?? [null, $this->session->value('user')])[1];
}

public function claims() {
return ($this->session->value('auth') ?? [null, $this->session->value('user')])[0];
}

/** Modifies context with given changes */
public function modify(iterable $changes): self {
[$claims, $user]= $this->session->value('auth') ?? [null, $this->session->value('user')];
foreach ($changes as $key => $value) {
$user[$key]= $value;
}

$this->session->register('auth', [$claims, $user]);
$this->session->transmit($this->response);
$this->request->pass('user', $user);
return $this;
}

/** Destroys underlying session, effectively logging out the user */
public function logout(): bool {
if ($this->session->valid()) {
$this->session->destroy();
$this->session->transmit($this->response);
}
return true;
}
}
55 changes: 54 additions & 1 deletion src/test/php/web/auth/unittest/SessionBasedTest.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use lang\IllegalStateException;
use test\{Assert, Test};
use web\auth\{Flow, SessionBased};
use web\auth\{Flow, SessionBased, Context};
use web\io\{TestInput, TestOutput};
use web\session\{ForTesting, ISession};
use web\{Request, Response};
Expand Down Expand Up @@ -120,6 +120,18 @@ public function passes_token() {
Assert::equals(SessionBased::TOKEN_LENGTH, strlen(base64_decode($token)));
}

#[Test]
public function passes_context() {
$user= ['username' => 'test'];

$auth= new SessionBased($this->authenticate($user), new ForTesting());
$this->handle([], $auth->required(function($req, $res) use(&$passed) {
$passed= $req->value('context');
}));

Assert::instance(Context::class, $passed);
}

#[Test]
public function session_is_attached_when_redirecting() {
$auth= new SessionBased($this->authenticate(null), newinstance(ForTesting::class, [], [
Expand Down Expand Up @@ -155,4 +167,45 @@ public function session_is_attached_after_authentication() {
Assert::equals(1, $attached->value('times'));
Assert::equals($user, $attached->value('auth')[1]);
}

#[Test]
public function user_accessible_from_context() {
$user= ['username' => 'test'];
$passed= null;

$auth= new SessionBased($this->authenticate($user), new ForTesting());
$this->handle([], $auth->required(function($req, $res) use(&$passed) {
$passed= $req->value('context')->user();
}));

Assert::equals($user, $passed);
}

#[Test]
public function session_and_request_values_modified() {
$user= ['username' => 'test'];
$modified= null;

$sessions= new ForTesting();
$auth= new SessionBased($this->authenticate($user), $sessions);
$this->handle([], $auth->required(function($req, $res) use(&$modified) {
$req->value('context')->modify(['theme' => 'dark']);
$modified= $req->value('user')['theme'];
}));

$session= current($sessions->all());
Assert::equals('dark', $modified);
Assert::equals($user + ['theme' => 'dark'], $session->value('auth')[1]);
}

#[Test]
public function session_is_destroyed_on_logout() {
$sessions= new ForTesting();
$auth= new SessionBased($this->authenticate(['username' => 'test']), $sessions);
$this->handle([], $auth->required(function($req, $res) {
$req->value('context')->logout();
}));

Assert::equals([], $sessions->all());
}
}
Loading