-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #27 from xp-forge/feature/userinfo
Add web.auth.UserInfo to map the returned user from a flow
- Loading branch information
Showing
6 changed files
with
252 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<?php namespace web\auth; | ||
|
||
use lang\XPException; | ||
|
||
/** Indicates an authentication error occurred */ | ||
class AuthenticationError extends XPException { | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
<?php namespace web\auth; | ||
|
||
use Iterator, Throwable; | ||
use web\auth\AuthenticationError; | ||
|
||
/** | ||
* Retrieves details about the authenticated user from a given endpoint. | ||
* | ||
* @test web.auth.unittest.UserInfoTest | ||
*/ | ||
class UserInfo { | ||
private $supplier; | ||
private $map= []; | ||
|
||
/** @param function(var): var $supplier */ | ||
public function __construct(callable $supplier) { $this->supplier= $supplier; } | ||
|
||
/** | ||
* Maps the user info using the given the function. | ||
* | ||
* @param (function(var): var)|(function(var, var): var) $function | ||
* @return self | ||
*/ | ||
public function map(callable $function) { | ||
$this->map[]= $function; | ||
return $this; | ||
} | ||
|
||
/** | ||
* Peeks into the given results. Useful for debugging. | ||
* | ||
* @param (function(var): void)|(function(var, var): void) $function | ||
* @return self | ||
*/ | ||
public function peek(callable $function) { | ||
$this->map[]= function($value, $result) use($function) { | ||
$function($value, $result); | ||
return $value; | ||
}; | ||
return $this; | ||
} | ||
|
||
/** | ||
* Fetches the user info and maps the returned value. | ||
* | ||
* @param var $result Authentication flow result | ||
* @return var The user object | ||
* @throws web.auth.AuthenticationError | ||
*/ | ||
public function __invoke($result) { | ||
try { | ||
$value= ($this->supplier)($result); | ||
foreach ($this->map as $function) { | ||
$result= $function($value, $result); | ||
$value= $result instanceof Iterator ? iterator_to_array($result) : $result; | ||
} | ||
return $value; | ||
} catch (AuthenticationError $e) { | ||
throw $e; | ||
} catch (Throwable $t) { | ||
throw new AuthenticationError('Invoking mappers: '.$t->getMessage(), $t); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
<?php namespace web\auth\unittest; | ||
|
||
use lang\IllegalStateException; | ||
use test\{Assert, Before, Expect, Test, Values}; | ||
use web\auth\{UserInfo, AuthenticationError}; | ||
|
||
class UserInfoTest { | ||
const USER= ['id' => 6100]; | ||
|
||
private $returned; | ||
|
||
|
||
/** @return iterable */ | ||
private function mappers() { | ||
$instance= new class() { | ||
public function first($user) { return ['first' => $user]; } | ||
public function second($user) { return ['second' => $user, 'aggregated' => true]; } | ||
}; | ||
yield [ | ||
[$instance, 'first'], | ||
[$instance, 'second'] | ||
]; | ||
yield [ | ||
function($user) { return ['first' => $user]; }, | ||
function($user) { return ['second' => $user, 'aggregated' => true]; }, | ||
]; | ||
yield [ | ||
function($user) { yield 'first' => $user; }, | ||
function($user) { yield 'second' => $user; yield 'aggregated' => true; }, | ||
]; | ||
yield [ | ||
new class() { public function __invoke($user) { return ['first' => $user]; }}, | ||
new class() { public function __invoke($user) { return ['second' => $user, 'aggregated' => true]; }}, | ||
]; | ||
} | ||
|
||
#[Before] | ||
public function returned() { | ||
$this->returned= function($source) { return $source; }; | ||
} | ||
|
||
#[Test] | ||
public function can_create_with_supplier() { | ||
new UserInfo($this->returned); | ||
} | ||
|
||
#[Test] | ||
public function fetch() { | ||
$fixture= new UserInfo($this->returned); | ||
Assert::equals(['id' => 'root'], $fixture(['id' => 'root'])); | ||
} | ||
|
||
#[Test, Expect(AuthenticationError::class)] | ||
public function fetch_raises_exception_when_endpoint_fails() { | ||
$fixture= new UserInfo(function($source) { | ||
throw new AuthenticationError('Internal Server Error'); | ||
}); | ||
$fixture(self::USER); | ||
} | ||
|
||
#[Test, Values(from: 'mappers')] | ||
public function map_functions_executed($first, $second) { | ||
$fixture= (new UserInfo($this->returned))->map($first)->map($second); | ||
Assert::equals( | ||
['second' => ['first' => self::USER], 'aggregated' => true], | ||
$fixture(self::USER) | ||
); | ||
} | ||
|
||
#[Test] | ||
public function map_functions_have_access_to_result() { | ||
$fixture= (new UserInfo($this->returned))->map(function($user, $result) { | ||
return ['user' => $result->fetch(), 'token' => $result->token()]; | ||
}); | ||
Assert::equals( | ||
['user' => self::USER, 'token' => 'TOKEN'], | ||
$fixture(new class(self::USER) { | ||
private $user; | ||
public function __construct($user) { $this->user= $user; } | ||
public function fetch() { return $this->user; } | ||
public function token() { return 'TOKEN'; } | ||
}) | ||
); | ||
} | ||
|
||
#[Test, Expect(AuthenticationError::class)] | ||
public function map_wraps_invocation_exceptions() { | ||
$fixture= (new UserInfo($this->returned))->map(function($user, $result) { | ||
throw new IllegalStateException('Test'); | ||
}); | ||
$fixture(self::USER); | ||
} | ||
|
||
#[Test, Expect(AuthenticationError::class)] | ||
public function map_wraps_supplier_exceptions() { | ||
$fixture= new UserInfo(function($result) { | ||
throw new IllegalStateException('Test'); | ||
}); | ||
$fixture(self::USER); | ||
} | ||
|
||
#[Test] | ||
public function peek_function_executed() { | ||
$invoked= []; | ||
$fixture= (new UserInfo($this->returned))->peek(function($user, $result) use(&$invoked) { | ||
$invoked[]= [$user, $result]; | ||
}); | ||
$user= $fixture(self::USER); | ||
|
||
Assert::equals(self::USER, $user); | ||
Assert::equals([[self::USER, self::USER]], $invoked); | ||
} | ||
} |