Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- New #113: Split `AuthenticationMethodInterface` into focused authentication and challenge interfaces (@samdark, @vjik)
- Bug #116: Fix authentication scheme in `HttpBearer` challenge according to RFC 6750 (@samdark)
- Bug #123: Fix `HttpHeader` authentication to correctly handle header value `"0"` (@vjik)
- Chg #104: Bump minimal PHP version to 8.1 (@vjik)
- Enh #104: Explicitly mark readonly properties (@vjik)
- Enh #105: Explicitly import classes and functions in "use" section (@mspirkov)
Expand Down
9 changes: 6 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"rector/rector": "^2.3.0",
"roave/infection-static-analysis-plugin": "^1.35",
"spatie/phpunit-watcher": "^1.24.4",
"vimeo/psalm": "^5.26.1 || ^6.10",
"vimeo/psalm": "^5.26.1 || ^6.16.1",
"yiisoft/yii-debug": "dev-master"
},
"autoload": {
Expand All @@ -66,8 +66,11 @@
}
},
"scripts": {
"test": "phpunit --testdox --no-interaction",
"test-watch": "phpunit-watcher watch"
"test": "phpunit",
"test-watch": "phpunit-watcher watch",
"cs-fix": "php-cs-fixer fix",
"rector": "rector",
"psalm": "psalm --no-cache"
},
"config": {
"sort-packages": true,
Expand Down
2 changes: 1 addition & 1 deletion psalm.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<psalm
errorLevel="3"
errorLevel="1"
resolveFromConfigFile="true"
findUnusedBaselineEntry="true"
findUnusedCode="false"
Expand Down
3 changes: 3 additions & 0 deletions src/Debug/AuthenticationMethodInterfaceProxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
use Yiisoft\Auth\AuthenticationMethodInterface;
use Yiisoft\Auth\IdentityInterface;

/**
* @psalm-suppress DeprecatedInterface
*/
final class AuthenticationMethodInterfaceProxy implements AuthenticationMethodInterface
{
public function __construct(private readonly AuthenticationMethodInterface $decorated, private readonly IdentityCollector $collector) {}
Expand Down
2 changes: 2 additions & 0 deletions src/Method/Composite.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

/**
* Composite allows multiple authentication methods at the same time.
*
* @psalm-suppress DeprecatedInterface
*/
final class Composite implements AuthenticationMethodInterface, AuthenticatorWithChallengeInterface
{
Expand All @@ -28,7 +30,7 @@
{
foreach ($this->methods as $method) {
if (!$method instanceof AuthenticatorInterface) {
throw new RuntimeException('Authentication method must be an instance of ' . AuthenticatorInterface::class . '.');

Check warning on line 33 in src/Method/Composite.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "ConcatOperandRemoval": @@ @@ { foreach ($this->methods as $method) { if (!$method instanceof AuthenticatorInterface) { - throw new RuntimeException('Authentication method must be an instance of ' . AuthenticatorInterface::class . '.'); + throw new RuntimeException('Authentication method must be an instance of ' . AuthenticatorInterface::class); } $identity = $method->authenticate($request);

Check warning on line 33 in src/Method/Composite.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "Concat": @@ @@ { foreach ($this->methods as $method) { if (!$method instanceof AuthenticatorInterface) { - throw new RuntimeException('Authentication method must be an instance of ' . AuthenticatorInterface::class . '.'); + throw new RuntimeException('Authentication method must be an instance of ' . '.' . AuthenticatorInterface::class); } $identity = $method->authenticate($request);

Check warning on line 33 in src/Method/Composite.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "ConcatOperandRemoval": @@ @@ { foreach ($this->methods as $method) { if (!$method instanceof AuthenticatorInterface) { - throw new RuntimeException('Authentication method must be an instance of ' . AuthenticatorInterface::class . '.'); + throw new RuntimeException('Authentication method must be an instance of ' . '.'); } $identity = $method->authenticate($request);

Check warning on line 33 in src/Method/Composite.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "ConcatOperandRemoval": @@ @@ { foreach ($this->methods as $method) { if (!$method instanceof AuthenticatorInterface) { - throw new RuntimeException('Authentication method must be an instance of ' . AuthenticatorInterface::class . '.'); + throw new RuntimeException(AuthenticatorInterface::class . '.'); } $identity = $method->authenticate($request);

Check warning on line 33 in src/Method/Composite.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "Concat": @@ @@ { foreach ($this->methods as $method) { if (!$method instanceof AuthenticatorInterface) { - throw new RuntimeException('Authentication method must be an instance of ' . AuthenticatorInterface::class . '.'); + throw new RuntimeException(AuthenticatorInterface::class . 'Authentication method must be an instance of ' . '.'); } $identity = $method->authenticate($request);
}

$identity = $method->authenticate($request);
Expand Down
29 changes: 25 additions & 4 deletions src/Method/HttpBasic.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
* ```
* RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
* ```
*
* @psalm-suppress DeprecatedInterface
*
* @psalm-type TAuthenticationCallback = callable(?string, ?string, IdentityWithTokenRepositoryInterface): (?IdentityInterface)
*/
final class HttpBasic implements AuthenticationMethodInterface, AuthenticatorWithChallengeInterface
{
Expand All @@ -36,6 +40,7 @@

/**
* @var callable|null
* @psalm-var TAuthenticationCallback|null
*/
private $authenticationCallback;

Expand Down Expand Up @@ -69,7 +74,7 @@
* static function (
* ?string $username,
* #[\SensitiveParameter] ?string $password,
* \Yiisoft\Auth\IdentityRepositoryInterface $identityRepository
* \Yiisoft\Auth\IdentityWithTokenRepositoryInterface $identityRepository
* ): ?\Yiisoft\Auth\IdentityInterface
* ```
*
Expand All @@ -81,6 +86,8 @@
* while the password information will be ignored.
* The {@see IdentityWithTokenRepositoryInterface::findIdentityByToken()}
* method will be called to authenticate an identity.
*
* @psalm-param TAuthenticationCallback $authenticationCallback
*/
public function withAuthenticationCallback(callable $authenticationCallback): self
{
Expand Down Expand Up @@ -121,11 +128,17 @@
* Obtains authentication credentials from request.
*
* @return array ['username', 'password'] array.
*
* @psalm-return array{0: ?string, 1: ?string}
*/
private function getAuthenticationCredentials(ServerRequestInterface $request): array
{
$username = $request->getServerParams()['PHP_AUTH_USER'] ?? null;
$password = $request->getServerParams()['PHP_AUTH_PW'] ?? null;
/**
* @var string[] $serverParams
*/
$serverParams = $request->getServerParams();
$username = $serverParams['PHP_AUTH_USER'] ?? null;
$password = $serverParams['PHP_AUTH_PW'] ?? null;
if ($username !== null || $password !== null) {
return [$username, $password];
}
Expand Down Expand Up @@ -156,14 +169,22 @@
return $header;
}

return $request->getServerParams()['REDIRECT_HTTP_AUTHORIZATION'] ?? null;
/**
* @var string[] $serverParams
*/
$serverParams = $request->getServerParams();

return $serverParams['REDIRECT_HTTP_AUTHORIZATION'] ?? null;
}

/**
* @psalm-return array{0: ?string, 1?: ?string}
*/
private function extractCredentialsFromHeader(#[SensitiveParameter] string $authToken): array
{
return array_map(
static fn($value) => $value === '' ? null : $value,
explode(':', base64_decode(substr($authToken, 6)), 2),

Check warning on line 187 in src/Method/HttpBasic.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "DecrementInteger": @@ @@ { return array_map( static fn($value) => $value === '' ? null : $value, - explode(':', base64_decode(substr($authToken, 6)), 2), + explode(':', base64_decode(substr($authToken, 5)), 2), ); }
);
}

Expand Down
2 changes: 2 additions & 0 deletions src/Method/HttpBearer.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
* Authentication method based on HTTP Bearer token.
*
* @see https://tools.ietf.org/html/rfc6750
*
* @psalm-suppress DeprecatedInterface
*/
final class HttpBearer extends HttpHeader implements AuthenticatorWithChallengeInterface
{
Expand Down
5 changes: 5 additions & 0 deletions src/Method/HttpCookie.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
* HTTP cookie authentication method.
*
* @see https://tools.ietf.org/html/rfc6265
*
* @psalm-suppress DeprecatedInterface
*/
final class HttpCookie implements AuthenticationMethodInterface, AuthenticatorInterface
{
Expand Down Expand Up @@ -67,6 +69,9 @@ public function withTokenType(?string $type): self

private function getAuthenticationToken(ServerRequestInterface $request): ?string
{
/**
* @var string[] $cookies
*/
$cookies = $request->getCookieParams();

return $cookies[$this->cookieName] ?? null;
Expand Down
4 changes: 3 additions & 1 deletion src/Method/HttpHeader.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
* The default implementation of HttpHeader uses the
* {@see IdentityWithTokenRepositoryInterface::findIdentityByToken()}
* and passes the value of the `X-Api-Key` header. This implementation is used mainly for authenticating API clients.
*
* @psalm-suppress DeprecatedInterface
*/
class HttpHeader implements AuthenticationMethodInterface, AuthenticatorInterface
{
Expand Down Expand Up @@ -96,11 +98,11 @@
return $new;
}

protected function getAuthenticationToken(ServerRequestInterface $request): ?string

Check warning on line 101 in src/Method/HttpHeader.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "ProtectedVisibility": @@ @@ return $new; } - protected function getAuthenticationToken(ServerRequestInterface $request): ?string + private function getAuthenticationToken(ServerRequestInterface $request): ?string { $authHeaders = $request->getHeader($this->headerName); $authHeader = reset($authHeaders);
{
$authHeaders = $request->getHeader($this->headerName);
$authHeader = reset($authHeaders);
if (!empty($authHeader)) {
if ($authHeader !== false && $authHeader !== '') {
if (preg_match($this->pattern, $authHeader, $matches)) {
$authHeader = $matches[1];
} else {
Expand Down
2 changes: 2 additions & 0 deletions src/Method/QueryParameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

/**
* QueryParameter supports the authentication based on the access token passed through a query parameter.
*
* @psalm-suppress DeprecatedInterface
*/
final class QueryParameter implements AuthenticationMethodInterface, AuthenticatorInterface
{
Expand Down
4 changes: 2 additions & 2 deletions src/Middleware/Authentication.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ final class Authentication implements MiddlewareInterface
private RequestHandlerInterface $failureHandler;

/**
* @var array Patterns to match to consider the given request URI path optional.
* @var string[] Patterns to match to consider the given request URI path optional.
*/
private array $optionalPatterns = [];
/**
Expand Down Expand Up @@ -62,7 +62,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
}

/**
* @param array $optional Patterns to match to consider the given request URI path optional.
* @param string[] $optional Patterns to match to consider the given request URI path optional.
*
* @see WildcardPattern
*/
Expand Down
21 changes: 21 additions & 0 deletions tests/Method/HttpHeaderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,27 @@ public function testWithTokenType(): void
);
}

public function testZeroValueHeader(): void
{
$identityRepository = new FakeIdentityRepository($this->createIdentity());
$result = (new HttpHeader($identityRepository))->authenticate(
$this->createRequest(['X-Api-Key' => '0']),
);

$this->assertNotNull($result);
$this->assertEquals('test-id', $result->getId());
$this->assertEquals(
[
'findIdentityByToken'
=> [
'token' => '0',
'type' => null,
],
],
$identityRepository->getCallParams(),
);
}

private function createIdentity(): IdentityInterface
{
return new FakeIdentity('test-id');
Expand Down
Loading