From aa3fa74020d4cd653f2b6b076e1ae4dce86bd3ae Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 15 Feb 2025 10:50:19 +0100 Subject: [PATCH] Add access to authentication state via context --- src/main/php/web/auth/Context.class.php | 7 +++ src/main/php/web/auth/SessionBased.class.php | 10 +++- .../php/web/auth/SessionContext.class.php | 45 +++++++++++++++ .../auth/unittest/SessionBasedTest.class.php | 55 ++++++++++++++++++- 4 files changed, 113 insertions(+), 4 deletions(-) create mode 100755 src/main/php/web/auth/Context.class.php create mode 100755 src/main/php/web/auth/SessionContext.class.php diff --git a/src/main/php/web/auth/Context.class.php b/src/main/php/web/auth/Context.class.php new file mode 100755 index 0000000..62e326d --- /dev/null +++ b/src/main/php/web/auth/Context.class.php @@ -0,0 +1,7 @@ +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 + ); } } \ No newline at end of file diff --git a/src/main/php/web/auth/SessionContext.class.php b/src/main/php/web/auth/SessionContext.class.php new file mode 100755 index 0000000..bf20f28 --- /dev/null +++ b/src/main/php/web/auth/SessionContext.class.php @@ -0,0 +1,45 @@ +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; + } +} \ No newline at end of file diff --git a/src/test/php/web/auth/unittest/SessionBasedTest.class.php b/src/test/php/web/auth/unittest/SessionBasedTest.class.php index e0eebdb..cd425dc 100755 --- a/src/test/php/web/auth/unittest/SessionBasedTest.class.php +++ b/src/test/php/web/auth/unittest/SessionBasedTest.class.php @@ -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}; @@ -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, [], [ @@ -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()); + } } \ No newline at end of file