diff --git a/.gitattributes b/.gitattributes index f9bbf33..fb3db50 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,20 +1,23 @@ -/.build export-ignore -/.config export-ignore -/.github export-ignore -/.idea export-ignore -/.phan export-ignore -/.phpdoc export-ignore -/docs export-ignore -/examples export-ignore -/public export-ignore -/tests export-ignore -/.editorconfig export-ignore -/.gitattributes export-ignore -/.gitignore export-ignore -/.readthedocs.yml export-ignore -/phpcs.xml.dist export-ignore -/phpdoc.xml.dist export-ignore -/phpmd.xml.dist export-ignore -/phpunit.xml.dist export-ignore +/.build export-ignore +/.config export-ignore +/.github export-ignore +/.idea export-ignore +/.phan export-ignore +/.phpdoc export-ignore +/docs export-ignore +/examples export-ignore +/public export-ignore +/tests export-ignore +/.editorconfig export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.readthedocs.yml export-ignore +/composer.lock export-ignore +/phpcs.xml.dist export-ignore +/phpdoc.xml.dist export-ignore +/phpmd.xml.dist export-ignore +/phpunit.xml.dist export-ignore +/phpstan.dist.neon export-ignore +/phpstan-baseline.neon export-ignore *.php diff=php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 414f97b..fcb5a89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,10 +40,6 @@ jobs: - "8.3" # - "8.4" - env: - PHAN_ALLOW_XDEBUG: 0 - PHAN_DISABLE_XDEBUG_WARN: 1 - steps: - name: "Checkout" uses: actions/checkout@v4 @@ -52,15 +48,15 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-version }} - extensions: ast, ${{ env.PHP_EXTENSIONS }} + extensions: ${{ env.PHP_EXTENSIONS }} ini-values: ${{ env.PHP_INI_VALUES }} coverage: none - name: "Install dependencies with composer" uses: ramsey/composer-install@v3 - - name: "Run phan" - run: php vendor/bin/phan --target-php-version=${{ matrix.php-version }} + - name: "Run PHPStan" + run: php vendor/bin/phpstan - name: "Run PHP_CodeSniffer" run: php vendor/bin/phpcs diff --git a/.gitignore b/.gitignore index a8328a1..b4d3c3f 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ phpcs.xml phpdoc.xml phpmd.xml phpunit.xml +phpstan.neon diff --git a/.phan/config.php b/.phan/config.php deleted file mode 100644 index c387692..0000000 --- a/.phan/config.php +++ /dev/null @@ -1,57 +0,0 @@ - null, - 'minimum_target_php_version' => '8.1', - - // A list of directories that should be parsed for class and - // method information. After excluding the directories - // defined in exclude_analysis_directory_list, the remaining - // files will be statically analyzed for errors. - // - // Thus, both first-party and third-party code being used by - // your application should be included in this list. - 'directory_list' => [ - 'examples', - 'src', - 'tests', - 'vendor', - ], - - // A regex used to match every file name that you want to - // exclude from parsing. Actual value will exclude every - // "test", "tests", "Test" and "Tests" folders found in - // "vendor/" directory. - 'exclude_file_regex' => '@^vendor/.*/(tests?|Tests?)/@', - - // A directory list that defines files that will be excluded - // from static analysis, but whose class and method - // information should be included. - // - // Generally, you'll want to include the directories for - // third-party code (such as "vendor/") in this list. - // - // n.b.: If you'd like to parse but not analyze 3rd - // party code, directories containing that code - // should be added to both the `directory_list` - // and `exclude_analysis_directory_list` arrays. - 'exclude_analysis_directory_list' => [ - 'vendor', - ], - 'suppress_issue_types' => [ - 'PhanUndeclaredGlobalVariable', // happens in get-token examples - ], -]; diff --git a/composer.json b/composer.json index 8fc7eb9..cafd7f9 100644 --- a/composer.json +++ b/composer.json @@ -34,8 +34,8 @@ "php": "^8.1", "ext-json": "*", "ext-sodium": "*", - "chillerlan/php-http-message-utils": "^2.2.1", - "chillerlan/php-settings-container": "^3.2", + "chillerlan/php-http-message-utils": "^2.2.2", + "chillerlan/php-settings-container": "^3.2.1", "psr/http-client": "^1.0", "psr/http-message": "^1.1 || ^2.0", "psr/log": "^1.1 || ^2.0 || ^3.0" @@ -43,10 +43,11 @@ "require-dev": { "chillerlan/php-dotenv": "^3.0", "chillerlan/phpunit-http": "^1.0", - "guzzlehttp/guzzle": "^7.8", - "monolog/monolog": "^3.6", - "phan/phan": "^5.4", + "guzzlehttp/guzzle": "^7.9", + "monolog/monolog": "^3.7", "phpmd/phpmd": "^2.15", + "phpstan/phpstan": "^1.11", + "phpstan/phpstan-deprecation-rules": "^1.2", "phpunit/phpunit": "^10.5", "slevomat/coding-standard": "^8.15", "squizlabs/php_codesniffer": "^3.10" @@ -56,17 +57,18 @@ }, "autoload": { "psr-4": { - "chillerlan\\OAuth\\": "src/" + "chillerlan\\OAuth\\": "src" } }, "autoload-dev": { "psr-4": { - "chillerlan\\OAuthTest\\": "tests/" + "chillerlan\\OAuthTest\\": "tests" } }, "scripts": { - "phan": "@php vendor/bin/phan", "phpcs": "@php vendor/bin/phpcs", + "phpstan": "@php vendor/bin/phpstan", + "phpstan-baseline": "@php vendor/bin/phpstan --generate-baseline", "phpunit": "@php vendor/bin/phpunit" }, "config": { diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..c569eae --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,137 @@ +parameters: + ignoreErrors: + - + message: "#^Parameter \\#2 \\$array of function implode expects array\\|null, array\\\\|string given\\.$#" + count: 2 + path: src/Core/OAuth1Provider.php + + - + message: "#^Parameter \\#1 \\$separator of function explode expects non\\-empty\\-string, string given\\.$#" + count: 1 + path: src/Core/OAuth2Provider.php + + - + message: "#^Method chillerlan\\\\OAuth\\\\Core\\\\OAuthProvider\\:\\:cleanBodyParams\\(\\) should return array\\ but returns array\\\\.$#" + count: 1 + path: src/Core/OAuthProvider.php + + - + message: "#^Method chillerlan\\\\OAuth\\\\Core\\\\OAuthProvider\\:\\:cleanQueryParams\\(\\) should return array\\ but returns array\\\\.$#" + count: 1 + path: src/Core/OAuthProvider.php + + - + message: "#^Parameter \\#1 \\$content of method Psr\\\\Http\\\\Message\\\\StreamFactoryInterface\\:\\:createStream\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: src/Core/OAuthProvider.php + + - + message: "#^Parameter \\#1 \\$length of function random_bytes expects int\\<1, max\\>, int given\\.$#" + count: 1 + path: src/Core/OAuthProvider.php + + - + message: "#^Parameter \\#1 \\$objectOrClass of class ReflectionClass constructor expects class\\-string\\\\|T of object, string given\\.$#" + count: 1 + path: src/Core/Utilities.php + + - + message: "#^Method chillerlan\\\\OAuth\\\\OAuthProviderFactory\\:\\:getProvider\\(\\) should return chillerlan\\\\OAuth\\\\Core\\\\OAuthInterface but returns object\\.$#" + count: 1 + path: src/OAuthProviderFactory.php + + - + message: "#^Access to an undefined property object\\:\\:\\$name\\.$#" + count: 1 + path: src/Providers/GuildWars2.php + + - + message: "#^Access to an undefined property object\\:\\:\\$permissions\\.$#" + count: 1 + path: src/Providers/GuildWars2.php + + - + message: "#^Method chillerlan\\\\OAuth\\\\Providers\\\\LastFM\\:\\:parseTrack\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: src/Providers/LastFM.php + + - + message: "#^Method chillerlan\\\\OAuth\\\\Providers\\\\LastFM\\:\\:scrobble\\(\\) has parameter \\$tracks with no value type specified in iterable type array\\.$#" + count: 1 + path: src/Providers/LastFM.php + + - + message: "#^Method chillerlan\\\\OAuth\\\\Providers\\\\LastFM\\:\\:scrobble\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: src/Providers/LastFM.php + + - + message: "#^Method chillerlan\\\\OAuth\\\\Providers\\\\LastFM\\:\\:sendScrobbles\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: src/Providers/LastFM.php + + - + message: "#^Parameter \\#1 \\$body of method chillerlan\\\\OAuth\\\\Providers\\\\LastFM\\:\\:sendScrobbles\\(\\) expects array\\, array\\ given\\.$#" + count: 1 + path: src/Providers/LastFM.php + + - + message: "#^Property chillerlan\\\\OAuth\\\\Core\\\\AccessToken\\:\\:\\$expires \\(int\\) does not accept DateInterval\\|DateTime\\|int\\|null\\.$#" + count: 1 + path: tests/Core/AccessTokenTest.php + + - + message: "#^Call to method chillerlan\\\\OAuth\\\\Core\\\\AuthenticatedUser\\:\\:fromIterable\\(\\) on a separate line has no effect\\.$#" + count: 1 + path: tests/Core/AuthenticatedUserTest.php + + - + message: "#^Parameter \\#1 \\$objectOrClass of class ReflectionClass constructor expects class\\-string\\\\|T of object, string given\\.$#" + count: 2 + path: tests/Core/UtilitiesTest.php + + - + message: "#^Parameter \\#2 \\$string of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertMatchesRegularExpression\\(\\) expects string, int\\|string given\\.$#" + count: 1 + path: tests/Providers/Live/AmazonAPITest.php + + - + message: "#^Access to an undefined property chillerlan\\\\DotEnv\\\\DotEnv\\:\\:\\$GW2_TOKEN\\.$#" + count: 1 + path: tests/Providers/Live/GuildWars2APITest.php + + - + message: "#^Access to an undefined property chillerlan\\\\DotEnv\\\\DotEnv\\:\\:\\$GW2_TOKEN_NAME\\.$#" + count: 1 + path: tests/Providers/Live/GuildWars2APITest.php + + - + message: "#^Dead catch \\- chillerlan\\\\OAuth\\\\Providers\\\\ProviderException is never thrown in the try block\\.$#" + count: 1 + path: tests/Providers/Live/MusicBrainzAPITest.php + + - + message: """ + #^Fetching class constant class of deprecated class chillerlan\\\\OAuth\\\\Providers\\\\OpenStreetmap\\: + https\\://github\\.com/openstreetmap/operations/issues/867$# + """ + count: 1 + path: tests/Providers/Live/OpenStreetmapAPITest.php + + - + message: "#^Parameter \\#1 \\$content of method Psr\\\\Http\\\\Message\\\\StreamFactoryInterface\\:\\:createStream\\(\\) expects string, string\\|false given\\.$#" + count: 2 + path: tests/Providers/Unit/OAuth2ProviderUnitTestAbstract.php + + - + message: "#^Parameter \\#1 \\$expected of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) expects class\\-string\\, string given\\.$#" + count: 1 + path: tests/Providers/Unit/OAuthProviderUnitTestAbstract.php + + - + message: """ + #^Fetching class constant class of deprecated class chillerlan\\\\OAuth\\\\Providers\\\\OpenStreetmap\\: + https\\://github\\.com/openstreetmap/operations/issues/867$# + """ + count: 1 + path: tests/Providers/Unit/OpenStreetmapTest.php diff --git a/phpstan.dist.neon b/phpstan.dist.neon new file mode 100644 index 0000000..78a143a --- /dev/null +++ b/phpstan.dist.neon @@ -0,0 +1,18 @@ +# https://phpstan.org/config-reference + +parameters: + level: 8 + tmpDir: .build/phpstan-cache + paths: +# - examples + - src + - tests + + treatPhpDocTypesAsCertain: false + + +includes: + - phpstan-baseline.neon + - vendor/phpstan/phpstan/conf/bleedingEdge.neon + - vendor/phpstan/phpstan-deprecation-rules/rules.neon + - vendor/chillerlan/php-settings-container/rules-magic-access.neon diff --git a/src/Core/AccessToken.php b/src/Core/AccessToken.php index b8b8a54..7b09444 100644 --- a/src/Core/AccessToken.php +++ b/src/Core/AccessToken.php @@ -22,6 +22,14 @@ * * @link https://datatracker.ietf.org/doc/html/rfc5849#section-2.3 * @link https://datatracker.ietf.org/doc/html/rfc6749#section-1.4 + * + * @property string|null $accessToken + * @property string|null $accessTokenSecret + * @property string|null $refreshToken + * @property int $expires + * @property string[] $scopes + * @property array $extraParams + * @property string|null $provider */ final class AccessToken extends SettingsContainerAbstract{ diff --git a/src/Core/AuthenticatedUser.php b/src/Core/AuthenticatedUser.php index 673fc10..155de21 100644 --- a/src/Core/AuthenticatedUser.php +++ b/src/Core/AuthenticatedUser.php @@ -20,6 +20,14 @@ * A simple read-only container for user data responses * * @see \chillerlan\OAuth\Core\UserInfo::me() + * + * @property string|null $handle + * @property string|null $displayName + * @property string|null $email + * @property string|int|null $id + * @property string|null $avatar + * @property string|null $url + * @property array $data */ final class AuthenticatedUser extends SettingsContainerAbstract{ diff --git a/src/Core/OAuth1Provider.php b/src/Core/OAuth1Provider.php index a61d6e3..01f5e99 100644 --- a/src/Core/OAuth1Provider.php +++ b/src/Core/OAuth1Provider.php @@ -72,6 +72,8 @@ protected function sendRequestTokenRequest(string $url):ResponseInterface{ * * @see \chillerlan\OAuth\Core\OAuth1Provider::sendRequestTokenRequest() * @link https://datatracker.ietf.org/doc/html/rfc5849#section-2.1 + * + * @return array */ protected function getRequestTokenRequestParams():array{ @@ -102,6 +104,7 @@ protected function getRequestTokenRequestParams():array{ * @throws \chillerlan\OAuth\Providers\ProviderException */ protected function parseTokenResponse(ResponseInterface $response, bool|null $checkCallbackConfirmed = null):AccessToken{ + /** @var array $data */ $data = QueryUtil::parse(MessageUtil::decompress($response)); if($data === []){ @@ -205,9 +208,11 @@ public function getAccessToken(string $requestToken, string $verifier):AccessTok /** * Prepares the header params for the access token request + * + * @return array */ protected function getAccessTokenRequestHeaderParams(AccessToken $requestToken, string $verifier):array{ - + /** @var array $params */ $params = [ 'oauth_consumer_key' => $this->options->key, 'oauth_nonce' => $this->nonce(), @@ -268,6 +273,7 @@ public function getRequestAuthorization(RequestInterface $request, AccessToken|n throw new InvalidAccessTokenException; } + /** @var array $params */ $params = [ 'oauth_consumer_key' => $this->options->key, 'oauth_nonce' => $this->nonce(), diff --git a/src/Core/OAuth2Provider.php b/src/Core/OAuth2Provider.php index 042060f..20a53c3 100644 --- a/src/Core/OAuth2Provider.php +++ b/src/Core/OAuth2Provider.php @@ -76,6 +76,7 @@ public function getAuthorizationURL(array|null $params = null, array|null $scope * * @param array $params * @param string[] $scopes + * @return array */ protected function getAuthorizationURLRequestParams(array $params, array $scopes):array{ @@ -176,6 +177,7 @@ protected function parseTokenResponse(ResponseInterface $response):AccessToken{ * * @see \chillerlan\OAuth\Core\OAuth2Provider::parseTokenResponse() * + * @return array * @throws \JsonException */ protected function getTokenResponseData(ResponseInterface $response):array{ @@ -208,6 +210,8 @@ public function getAccessToken(string $code, string|null $state = null):AccessTo * prepares the request body parameters for the access token request * * @see \chillerlan\OAuth\Core\OAuth2Provider::getAccessToken() + * + * @return array */ protected function getAccessTokenRequestBodyParams(string $code):array{ @@ -236,7 +240,7 @@ protected function getAccessTokenRequestBodyParams(string $code):array{ * @see \chillerlan\OAuth\Core\OAuth2Provider::refreshAccessToken() * @see \chillerlan\OAuth\Core\OAuth2Provider::getParRequestUri() * - * @param array $body + * @param array $body */ protected function sendAccessTokenRequest(string $url, array $body):ResponseInterface{ @@ -324,6 +328,7 @@ public function getClientCredentialsToken(array|null $scopes = null):AccessToken * @see \chillerlan\OAuth\Core\OAuth2Provider::getClientCredentialsToken() * * @param string[]|null $scopes + * @return array */ protected function getClientCredentialsTokenRequestBodyParams(array|null $scopes):array{ $body = ['grant_type' => 'client_credentials']; @@ -404,6 +409,8 @@ public function refreshAccessToken(AccessToken|null $token = null):AccessToken{ * prepares the request body parameters for the token refresh * * @see \chillerlan\OAuth\Core\OAuth2Provider::refreshAccessToken() + * + * @return array */ protected function getRefreshAccessTokenRequestBodyParams(string $refreshToken):array{ return [ @@ -467,7 +474,7 @@ public function invalidateAccessToken(AccessToken|null $token = null, string|nul * * @see \chillerlan\OAuth\Core\OAuth2Provider::invalidateAccessToken() * - * @param array $body + * @param array $body */ protected function sendTokenInvalidateRequest(string $url, array $body):ResponseInterface{ @@ -486,6 +493,8 @@ protected function sendTokenInvalidateRequest(string $url, array $body):Response * Prepares the body for a token revocation request * * @see \chillerlan\OAuth\Core\OAuth2Provider::invalidateAccessToken() + * + * @return array */ protected function getInvalidateAccessTokenBodyParams(AccessToken $token, string $type):array{ return [ @@ -505,7 +514,8 @@ protected function getInvalidateAccessTokenBodyParams(AccessToken $token, string * @see \chillerlan\OAuth\Core\CSRFToken::setState() * @see \chillerlan\OAuth\Core\OAuth2Provider::getAuthorizationURLRequestParams() * - * @param array $params + * @param array $params + * @return array * @throws \chillerlan\OAuth\Providers\ProviderException */ final public function setState(array $params):array{ @@ -562,7 +572,8 @@ final public function checkState(string|null $state = null):void{ * @see \chillerlan\OAuth\Core\PKCE::setCodeChallenge() * @see \chillerlan\OAuth\Core\OAuth2Provider::getAuthorizationURLRequestParams() * - * @param array $params + * @param array $params + * @return array */ final public function setCodeChallenge(array $params, string $challengeMethod):array{ @@ -590,7 +601,8 @@ final public function setCodeChallenge(array $params, string $challengeMethod):a * @see \chillerlan\OAuth\Core\PKCE::setCodeVerifier() * @see \chillerlan\OAuth\Core\OAuth2Provider::getAccessTokenRequestBodyParams() * - * @param array $params + * @param array $params + * @return array */ final public function setCodeVerifier(array $params):array{ @@ -653,6 +665,7 @@ final public function generateChallenge(string $verifier, string $challengeMetho $verifier = match($challengeMethod){ PKCE::CHALLENGE_METHOD_S256 => hash('sha256', $verifier, true), // no other hash methods yet + default => throw new ProviderException('invalid PKCE challenge method'), }; return sodium_bin2base64($verifier, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING); @@ -669,7 +682,7 @@ final public function generateChallenge(string $verifier, string $challengeMetho * @see \chillerlan\OAuth\Core\PAR::getParRequestUri() * @see \chillerlan\OAuth\Core\OAuth2Provider::getAuthorizationURL() * - * @param array $body + * @param array $body */ public function getParRequestUri(array $body):UriInterface{ @@ -704,7 +717,8 @@ public function getParRequestUri(array $body):UriInterface{ * * @see \chillerlan\OAuth\Core\OAuth2Provider::getParRequestUri() * - * @param array $response + * @param array $response + * @return array * * @codeCoverageIgnore */ diff --git a/src/Core/OAuthInterface.php b/src/Core/OAuthInterface.php index aac6810..470c79b 100644 --- a/src/Core/OAuthInterface.php +++ b/src/Core/OAuthInterface.php @@ -43,7 +43,7 @@ interface OAuthInterface extends ClientInterface{ * * Note: must not contain: Accept-Encoding, Authorization, Content-Length, Content-Type * - * @var array + * @var array */ public const HEADERS_AUTH = []; @@ -52,7 +52,7 @@ interface OAuthInterface extends ClientInterface{ * * Note: must not contain: Authorization * - * @var array + * @var array */ public const HEADERS_API = []; @@ -102,7 +102,7 @@ public function getUserRevokeURL():string|null; * @link https://datatracker.ietf.org/doc/html/rfc9126 * @see \chillerlan\OAuth\Core\PAR * - * @param string[]|null $params + * @param array|null $params * @param string[]|null $scopes */ public function getAuthorizationURL(array|null $params = null, array|null $scopes = null):UriInterface; @@ -118,6 +118,10 @@ public function getRequestAuthorization(RequestInterface $request, AccessToken|n /** * Prepares an API request to $path with the given parameters, gets authorization, fires the request * and returns a PSR-7 ResponseInterface with the corresponding API response + * + * @param array|null $params + * @param StreamInterface|array|string|null $body + * @param array|null $headers */ public function request( string $path, diff --git a/src/Core/OAuthProvider.php b/src/Core/OAuthProvider.php index 1cabfb8..eb48dfa 100644 --- a/src/Core/OAuthProvider.php +++ b/src/Core/OAuthProvider.php @@ -146,110 +146,74 @@ protected function construct():void{ // noop } - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ final public function getName():string{ return $this->name; } - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ final public function getApiDocURL():string|null{ return $this->apiDocs; } - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ final public function getApplicationURL():string|null{ return $this->applicationURL; } - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ final public function getUserRevokeURL():string|null{ return $this->userRevokeURL; } - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ final public function setStorage(OAuthStorageInterface $storage):static{ $this->storage = $storage; return $this; } - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ final public function getStorage():OAuthStorageInterface{ return $this->storage; } - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ final public function setLogger(LoggerInterface $logger):static{ $this->logger = $logger; return $this; } - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ final public function setRequestFactory(RequestFactoryInterface $requestFactory):static{ $this->requestFactory = $requestFactory; return $this; } - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ final public function setStreamFactory(StreamFactoryInterface $streamFactory):static{ $this->streamFactory = $streamFactory; return $this; } - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ final public function setUriFactory(UriFactoryInterface $uriFactory):static{ $this->uriFactory = $uriFactory; return $this; } - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ final public function storeAccessToken(AccessToken $token):static{ $this->storage->storeAccessToken($token, $this->name); return $this; } - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ final public function getAccessTokenFromStorage():AccessToken{ return $this->storage->getAccessToken($this->name); } @@ -265,6 +229,9 @@ final protected function createAccessToken():AccessToken{ /** * Prepare request headers + * + * @param array|null $headers + * @return array */ final protected function getRequestHeaders(array|null $headers = null):array{ return array_merge($this::HEADERS_API, ($headers ?? [])); @@ -272,6 +239,8 @@ final protected function getRequestHeaders(array|null $headers = null):array{ /** * Prepares the request URL + * + * @param array|null $params */ final protected function getRequestURL(string $path, array|null $params = null):string{ return QueryUtil::merge($this->getRequestTarget($path), $this->cleanQueryParams(($params ?? []))); @@ -280,7 +249,8 @@ final protected function getRequestURL(string $path, array|null $params = null): /** * Cleans an array of query parameters * - * @param array $params + * @param array $params + * @return array */ protected function cleanQueryParams(iterable $params):array{ return QueryUtil::cleanParams($params, QueryUtil::BOOLEANS_AS_INT_STRING, true); @@ -289,7 +259,8 @@ protected function cleanQueryParams(iterable $params):array{ /** * Cleans an array of body parameters * - * @param array $params + * @param array $params + * @return array */ protected function cleanBodyParams(iterable $params):array{ return QueryUtil::cleanParams($params, QueryUtil::BOOLEANS_AS_BOOL, true); @@ -315,7 +286,9 @@ protected function nonce(int $bytes = 32):string{ } /** - * @implements \chillerlan\OAuth\Core\TokenInvalidate + * implements TokenInvalidate + * + * @see \chillerlan\OAuth\Core\TokenInvalidate * @codeCoverageIgnore * @throws \chillerlan\OAuth\Providers\ProviderException */ @@ -324,7 +297,6 @@ public function InvalidateAccessToken(AccessToken|null $token = null):bool{ } /** - * @inheritDoc * @throws \chillerlan\OAuth\Core\InvalidAccessTokenException */ final public function sendRequest(RequestInterface $request):ResponseInterface{ @@ -341,7 +313,6 @@ final public function sendRequest(RequestInterface $request):ResponseInterface{ } /** - * @inheritDoc * @throws \chillerlan\OAuth\Core\UnauthorizedAccessException */ public function request( @@ -379,6 +350,7 @@ public function request( /** * Prepares the request body and sets it in the given RequestInterface, along with a Content-Length header * + * @param StreamInterface|array|string $body * @throws \chillerlan\OAuth\Providers\ProviderException */ final protected function setRequestBody(StreamInterface|array|string $body, RequestInterface $request):RequestInterface{ @@ -388,15 +360,13 @@ final protected function setRequestBody(StreamInterface|array|string $body, Requ $body = $this->cleanBodyParams($body); $contentType = strtolower($request->getHeaderLine('content-type')); - try{ - $body = match($contentType){ - 'application/x-www-form-urlencoded' => QueryUtil::build($body, PHP_QUERY_RFC1738), - 'application/json', 'application/vnd.api+json' => json_encode($body), - }; - } - catch(UnhandledMatchError){ - throw new ProviderException(sprintf('invalid content-type "%s" for the given array body', $contentType)); - } + $body = match($contentType){ + 'application/x-www-form-urlencoded' => QueryUtil::build($body, PHP_QUERY_RFC1738), + 'application/json', 'application/vnd.api+json' => json_encode($body), + default => throw new ProviderException( + sprintf('invalid content-type "%s" for the given array body', $contentType), + ), + }; } @@ -467,6 +437,8 @@ protected function getRequestTarget(string $uri):string{ /** * prepares and sends the request to the provider's "me" endpoint and returns a ResponseInterface + * + * @param array|null $params */ protected function sendMeRequest(string $endpoint, array|null $params = null):ResponseInterface{ // we'll bypass the API check here as not all "me" endpoints align with the provider APIs @@ -488,6 +460,9 @@ protected function sendMeRequest(string $endpoint, array|null $params = null):Re * @see \chillerlan\OAuth\Core\UserInfo::me() * @see \chillerlan\OAuth\Core\OAuthProvider::sendMeRequest() * @see \chillerlan\OAuth\Core\OAuthProvider::handleMeResponseError() + * + * @param array|null $params + * @return array * @throws \chillerlan\OAuth\Providers\ProviderException */ final protected function getMeResponseData(string $endpoint, array|null $params = null):array{ @@ -506,6 +481,9 @@ final protected function getMeResponseData(string $endpoint, array|null $params // handle and throw the error $this->handleMeResponseError($response); + + /** @noinspection PhpUnreachableStatementInspection this is here because phpstan silly */ + return []; } /** diff --git a/src/Core/PKCE.php b/src/Core/PKCE.php index 54f0c17..8f52cf6 100644 --- a/src/Core/PKCE.php +++ b/src/Core/PKCE.php @@ -48,6 +48,7 @@ public function generateChallenge(string $verifier, string $challengeMethod):str * @link https://datatracker.ietf.org/doc/html/rfc7636#section-4.3 * * @param array $params + * @return array * @throws \chillerlan\OAuth\Providers\ProviderException */ public function setCodeChallenge(array $params, string $challengeMethod):array; @@ -59,6 +60,7 @@ public function setCodeChallenge(array $params, string $challengeMethod):array; * @link https://datatracker.ietf.org/doc/html/rfc7636#section-4.5 * * @param array $params + * @return array * @throws \chillerlan\OAuth\Providers\ProviderException */ public function setCodeVerifier(array $params):array; diff --git a/src/Core/Utilities.php b/src/Core/Utilities.php index bc1a910..7a8edd2 100644 --- a/src/Core/Utilities.php +++ b/src/Core/Utilities.php @@ -44,6 +44,8 @@ class Utilities{ /** * Fetches a list of provider classes in the given directory + * + * @return array> */ public static function getProviders(string|null $providerDir = null, string|null $namespace = null):array{ $providerDir = realpath(($providerDir ?? __DIR__.'/../Providers')); @@ -54,7 +56,6 @@ public static function getProviders(string|null $providerDir = null, string|null throw new InvalidArgumentException('invalid $providerDir'); } - /** @var \SplFileInfo $e */ foreach(new DirectoryIterator($providerDir) as $e){ if($e->getExtension() !== 'php'){ @@ -79,7 +80,7 @@ public static function getProviders(string|null $providerDir = null, string|null } /** - * Creates a new cryptographically secure random encryption key (in hexadecimal or format) + * Creates a new cryptographically secure random encryption key (in hexadecimal format) */ public static function createEncryptionKey():string{ return sodium_bin2hex(sodium_crypto_secretbox_keygen()); @@ -100,6 +101,7 @@ public static function encrypt(string $data, string $keyHex, int $format = self: self::ENCRYPT_FORMAT_BINARY => $nonce.$box, self::ENCRYPT_FORMAT_BASE64 => sodium_bin2base64($nonce.$box, SODIUM_BASE64_VARIANT_ORIGINAL), self::ENCRYPT_FORMAT_HEX => sodium_bin2hex($nonce.$box), + default => throw new InvalidArgumentException('invalid format'), }; sodium_memzero($data); @@ -123,6 +125,7 @@ public static function decrypt(string $encrypted, string $keyHex, int $format = self::ENCRYPT_FORMAT_BINARY => $encrypted, self::ENCRYPT_FORMAT_BASE64 => sodium_base642bin($encrypted, SODIUM_BASE64_VARIANT_ORIGINAL), self::ENCRYPT_FORMAT_HEX => sodium_hex2bin($encrypted), + default => throw new InvalidArgumentException('invalid format'), }; $nonce = substr($bin, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); diff --git a/src/OAuthOptionsTrait.php b/src/OAuthOptionsTrait.php index 957bd3d..8107745 100644 --- a/src/OAuthOptionsTrait.php +++ b/src/OAuthOptionsTrait.php @@ -16,6 +16,18 @@ /** * The settings for the OAuth provider + * + * @property string $key + * @property string $secret + * @property string $callbackURL + * @property bool $useStorageEncryption + * @property string $storageEncryptionKey + * @property bool $tokenAutoRefresh + * @property bool $sessionStart + * @property bool $sessionStop + * @property string $sessionStorageVar + * @property string $fileStoragePath + * @property int $pkceVerifierLength */ trait OAuthOptionsTrait{ diff --git a/src/Providers/Amazon.php b/src/Providers/Amazon.php index 637c73d..8c52ea2 100644 --- a/src/Providers/Amazon.php +++ b/src/Providers/Amazon.php @@ -40,10 +40,7 @@ class Amazon extends OAuth2Provider implements CSRFToken, TokenRefresh, UserInfo protected string|null $applicationURL = 'https://developer.amazon.com/loginwithamazon/console/site/lwa/overview.html'; protected string|null $apiDocs = 'https://developer.amazon.com/docs/login-with-amazon/web-docs.html'; - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ public function me():AuthenticatedUser{ $json = $this->getMeResponseData('/user/profile'); diff --git a/src/Providers/BattleNet.php b/src/Providers/BattleNet.php index 8f42014..ac4d098 100644 --- a/src/Providers/BattleNet.php +++ b/src/Providers/BattleNet.php @@ -113,10 +113,7 @@ public function setRegion(string $region):static{ return $this; } - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ public function me():AuthenticatedUser{ $json = $this->getMeResponseData($this->battleNetOauth.'/oauth/userinfo'); diff --git a/src/Providers/Bitbucket.php b/src/Providers/Bitbucket.php index 7fdde2f..18634c5 100644 --- a/src/Providers/Bitbucket.php +++ b/src/Providers/Bitbucket.php @@ -28,10 +28,7 @@ class Bitbucket extends OAuth2Provider implements ClientCredentials, CSRFToken, protected string|null $apiDocs = 'https://developer.atlassian.com/bitbucket/api/2/reference/'; protected string|null $applicationURL = 'https://developer.atlassian.com/apps/'; - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ public function me():AuthenticatedUser{ $json = $this->getMeResponseData('/user'); diff --git a/src/Providers/Codeberg.php b/src/Providers/Codeberg.php index c20662d..f73d567 100644 --- a/src/Providers/Codeberg.php +++ b/src/Providers/Codeberg.php @@ -67,10 +67,7 @@ class Codeberg extends OAuth2Provider implements CSRFToken, PKCE, TokenRefresh, protected string|null $applicationURL = 'https://codeberg.org/user/settings/applications'; protected string|null $userRevokeURL = 'https://codeberg.org/user/settings/applications'; - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ public function me():AuthenticatedUser{ $json = $this->getMeResponseData('/v1/user'); diff --git a/src/Providers/Discogs.php b/src/Providers/Discogs.php index 3d53596..a0a64c1 100644 --- a/src/Providers/Discogs.php +++ b/src/Providers/Discogs.php @@ -36,10 +36,7 @@ class Discogs extends OAuth1Provider implements UserInfo{ protected string|null $apiDocs = 'https://www.discogs.com/developers/'; protected string|null $applicationURL = 'https://www.discogs.com/settings/developers'; - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ public function me():AuthenticatedUser{ $json = $this->getMeResponseData('/oauth/identity'); diff --git a/src/Providers/Discord.php b/src/Providers/Discord.php index c539724..61e0a54 100644 --- a/src/Providers/Discord.php +++ b/src/Providers/Discord.php @@ -65,7 +65,6 @@ class Discord extends OAuth2Provider implements ClientCredentials, CSRFToken, To protected string|null $applicationURL = 'https://discordapp.com/developers/applications/'; /** - * @inheritDoc * @link https://github.com/discord/discord-api-docs/issues/2259#issuecomment-927180184 */ protected function getInvalidateAccessTokenBodyParams(AccessToken $token, string $type):array{ @@ -77,10 +76,7 @@ protected function getInvalidateAccessTokenBodyParams(AccessToken $token, string ]; } - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ public function me():AuthenticatedUser{ $json = $this->getMeResponseData('/users/@me'); diff --git a/src/Providers/GitHub.php b/src/Providers/GitHub.php index b53633b..64f3037 100644 --- a/src/Providers/GitHub.php +++ b/src/Providers/GitHub.php @@ -86,10 +86,7 @@ class GitHub extends OAuth2Provider implements CSRFToken, TokenRefresh, UserInfo protected string|null $apiDocs = 'https://docs.github.com/rest'; protected string|null $applicationURL = 'https://github.com/settings/developers'; - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ public function me():AuthenticatedUser{ $json = $this->getMeResponseData('/user'); diff --git a/src/Providers/GitLab.php b/src/Providers/GitLab.php index 011957c..d36622b 100644 --- a/src/Providers/GitLab.php +++ b/src/Providers/GitLab.php @@ -28,10 +28,7 @@ class GitLab extends OAuth2Provider implements ClientCredentials, CSRFToken, Tok protected string|null $applicationURL = 'https://gitlab.com/profile/applications'; protected string|null $apiDocs = 'https://docs.gitlab.com/ee/api/rest/'; - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ public function me():AuthenticatedUser{ $json = $this->getMeResponseData('/v4/user'); diff --git a/src/Providers/Gitea.php b/src/Providers/Gitea.php index dfd85ed..34af0a0 100644 --- a/src/Providers/Gitea.php +++ b/src/Providers/Gitea.php @@ -65,10 +65,7 @@ class Gitea extends OAuth2Provider implements CSRFToken, PKCE, TokenRefresh, Use protected string|null $applicationURL = 'https://gitea.com/user/settings/applications'; protected string|null $userRevokeURL = 'https://gitea.com/user/settings/applications'; - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ public function me():AuthenticatedUser{ $json = $this->getMeResponseData('/v1/user'); diff --git a/src/Providers/Google.php b/src/Providers/Google.php index ac5300c..a4ded3f 100644 --- a/src/Providers/Google.php +++ b/src/Providers/Google.php @@ -43,10 +43,7 @@ class Google extends OAuth2Provider implements CSRFToken, UserInfo{ protected string|null $apiDocs = 'https://developers.google.com/oauthplayground/'; protected string|null $applicationURL = 'https://console.developers.google.com/apis/credentials'; - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ public function me():AuthenticatedUser{ $json = $this->getMeResponseData('/userinfo/v2/me'); diff --git a/src/Providers/GuildWars2.php b/src/Providers/GuildWars2.php index 71b9533..f1a2ad0 100644 --- a/src/Providers/GuildWars2.php +++ b/src/Providers/GuildWars2.php @@ -84,7 +84,6 @@ public function storeGW2Token(string $access_token):AccessToken{ } /** - * @inheritdoc * @throws \chillerlan\OAuth\Providers\ProviderException */ public function getAuthorizationURL(array|null $params = null, array|null $scopes = null):UriInterface{ @@ -92,7 +91,6 @@ public function getAuthorizationURL(array|null $params = null, array|null $scope } /** - * @inheritdoc * @throws \chillerlan\OAuth\Providers\ProviderException */ public function getAccessToken(string $code, string|null $state = null):AccessToken{ diff --git a/src/Providers/LastFM.php b/src/Providers/LastFM.php index 94c7719..9386773 100644 --- a/src/Providers/LastFM.php +++ b/src/Providers/LastFM.php @@ -79,6 +79,8 @@ public function getAccessToken(string $session_token):AccessToken{ /** * prepares the request body parameters for the access token request + * + * @return array */ protected function getAccessTokenRequestBodyParams(string $session_token):array{ @@ -95,7 +97,7 @@ protected function getAccessTokenRequestBodyParams(string $session_token):array{ /** * sends a request to the access token endpoint $url with the given $params as URL query * - * @param array $params + * @param array $params */ protected function sendAccessTokenRequest(string $url, array $params):ResponseInterface{ @@ -184,7 +186,8 @@ public function request( /** * adds the authorization parameters to the request parameters * - * @param array $params + * @param array $params + * @return array */ protected function getAuthorization(array $params, AccessToken|null $token = null):array{ $token ??= $this->storage->getAccessToken($this->name); @@ -275,10 +278,8 @@ public function me():AuthenticatedUser{ * - duration : [optional] The length of the track in seconds. * * @link https://www.last.fm/api/show/track.scrobble - * - * @param array|array> $tracks */ - public function scrobble(array $tracks):array{ + public function scrobble(array $tracks):array{ // phpcs:ignore // a single track was given if(isset($tracks['artist'], $tracks['track'], $tracks['timestamp'])){ @@ -328,9 +329,7 @@ public function addScrobble(array $track):static{ return $this; } - /** - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ public function clearScrobbles():static{ $this->scrobbles = []; diff --git a/src/Providers/MailChimp.php b/src/Providers/MailChimp.php index f936cf7..f8ae2a9 100644 --- a/src/Providers/MailChimp.php +++ b/src/Providers/MailChimp.php @@ -55,13 +55,8 @@ public function getAccessToken(string $code, string|null $state = null):AccessTo * @throws \chillerlan\OAuth\OAuthException */ public function getTokenMetadata(AccessToken|null $token = null):AccessToken{ - $token ??= $this->storage->getAccessToken($this->name); - if(!$token instanceof AccessToken){ - throw new OAuthException('invalid token'); // @codeCoverageIgnore - } - $request = $this->requestFactory ->createRequest('GET', $this::METADATA_ENDPOINT) ->withHeader('Authorization', 'OAuth '.$token->accessToken) diff --git a/src/Providers/MicrosoftGraph.php b/src/Providers/MicrosoftGraph.php index 49406c5..adf8169 100644 --- a/src/Providers/MicrosoftGraph.php +++ b/src/Providers/MicrosoftGraph.php @@ -37,10 +37,7 @@ class MicrosoftGraph extends AzureActiveDirectory implements UserInfo{ protected string $apiURL = 'https://graph.microsoft.com'; protected string|null $apiDocs = 'https://learn.microsoft.com/graph/overview'; - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ public function me():AuthenticatedUser{ $json = $this->getMeResponseData('/v1.0/me'); diff --git a/src/Providers/Mixcloud.php b/src/Providers/Mixcloud.php index 6502292..a79be5e 100644 --- a/src/Providers/Mixcloud.php +++ b/src/Providers/Mixcloud.php @@ -33,10 +33,7 @@ class Mixcloud extends OAuth2Provider implements UserInfo{ protected string|null $apiDocs = 'https://www.mixcloud.com/developers/'; protected string|null $applicationURL = 'https://www.mixcloud.com/developers/create/'; - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ public function me():AuthenticatedUser{ // mixcloud sends "Content-Type: text/javascript" for JSON content (????) $json = $this->getMeResponseData('/me/'); diff --git a/src/Providers/OpenCaching.php b/src/Providers/OpenCaching.php index 87a89f4..7de1f70 100644 --- a/src/Providers/OpenCaching.php +++ b/src/Providers/OpenCaching.php @@ -37,10 +37,7 @@ class OpenCaching extends OAuth1Provider implements UserInfo{ protected string|null $apiDocs = 'https://www.opencaching.de/okapi/'; protected string|null $applicationURL = 'https://www.opencaching.de/okapi/signup.html'; - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ public function me():AuthenticatedUser{ $json = $this->getMeResponseData('/users/user', ['fields' => implode('|', $this::USER_FIELDS)]); diff --git a/src/Providers/OpenStreetmap.php b/src/Providers/OpenStreetmap.php index 9559c04..cda134f 100644 --- a/src/Providers/OpenStreetmap.php +++ b/src/Providers/OpenStreetmap.php @@ -32,10 +32,7 @@ class OpenStreetmap extends OAuth1Provider implements UserInfo{ protected string|null $apiDocs = 'https://wiki.openstreetmap.org/wiki/API'; protected string|null $applicationURL = 'https://www.openstreetmap.org/user/{USERNAME}/oauth_clients'; - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ public function me():AuthenticatedUser{ $json = $this->getMeResponseData('/api/0.6/user/details.json'); diff --git a/src/Providers/OpenStreetmap2.php b/src/Providers/OpenStreetmap2.php index 9c50411..7c825b0 100644 --- a/src/Providers/OpenStreetmap2.php +++ b/src/Providers/OpenStreetmap2.php @@ -50,10 +50,7 @@ class OpenStreetmap2 extends OAuth2Provider implements CSRFToken, UserInfo{ protected string|null $apiDocs = 'https://wiki.openstreetmap.org/wiki/API'; protected string|null $applicationURL = 'https://www.openstreetmap.org/oauth2/applications'; - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ public function me():AuthenticatedUser{ $json = $this->getMeResponseData('/api/0.6/user/details.json'); diff --git a/src/Providers/PayPal.php b/src/Providers/PayPal.php index 052e93b..bb51eb6 100644 --- a/src/Providers/PayPal.php +++ b/src/Providers/PayPal.php @@ -43,10 +43,7 @@ class PayPal extends OAuth2Provider implements ClientCredentials, CSRFToken, Tok protected string|null $applicationURL = 'https://developer.paypal.com/developer/applications/'; protected string|null $apiDocs = 'https://developer.paypal.com/docs/connect-with-paypal/reference/'; - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ public function me():AuthenticatedUser{ $json = $this->getMeResponseData('/v1/identity/oauth2/userinfo', ['schema' => 'paypalv1.1']); diff --git a/src/Providers/Pinterest.php b/src/Providers/Pinterest.php index b9e1179..57963fa 100644 --- a/src/Providers/Pinterest.php +++ b/src/Providers/Pinterest.php @@ -54,10 +54,7 @@ class Pinterest extends OAuth2Provider implements CSRFToken, TokenRefresh, UserI protected string|null $applicationURL = 'https://developers.pinterest.com/apps/'; protected string|null $userRevokeURL = 'https://www.pinterest.com/settings/security'; - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ public function me():AuthenticatedUser{ $json = $this->getMeResponseData('/v5/user_account'); diff --git a/src/Providers/Reddit.php b/src/Providers/Reddit.php index 83858d5..4a054d3 100644 --- a/src/Providers/Reddit.php +++ b/src/Providers/Reddit.php @@ -89,10 +89,7 @@ class Reddit extends OAuth2Provider implements ClientCredentials, CSRFToken, Tok protected string|null $applicationURL = 'https://www.reddit.com/prefs/apps/'; protected string|null $userRevokeURL = 'https://www.reddit.com/settings/privacy'; - /** - * @inheritDoc - */ - protected function sendTokenInvalidateRequest(string $url, array $body):ResponseInterface{ + protected function sendTokenInvalidateRequest(string $url, array $body):ResponseInterface{ // phpcs:ignore $request = $this->requestFactory ->createRequest('POST', $url) @@ -105,10 +102,7 @@ protected function sendTokenInvalidateRequest(string $url, array $body):Response return $this->http->sendRequest($request); } - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ public function me():AuthenticatedUser{ $json = $this->getMeResponseData('/v1/me'); diff --git a/src/Providers/SoundCloud.php b/src/Providers/SoundCloud.php index a3d4ff3..1cd7fae 100644 --- a/src/Providers/SoundCloud.php +++ b/src/Providers/SoundCloud.php @@ -40,10 +40,7 @@ class SoundCloud extends OAuth2Provider implements ClientCredentials, TokenRefre protected string|null $apiDocs = 'https://developers.soundcloud.com/'; protected string|null $applicationURL = 'https://soundcloud.com/you/apps'; - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ public function me():AuthenticatedUser{ $json = $this->getMeResponseData('/me'); diff --git a/src/Providers/Spotify.php b/src/Providers/Spotify.php index 1bd62fb..b4ead22 100644 --- a/src/Providers/Spotify.php +++ b/src/Providers/Spotify.php @@ -86,10 +86,7 @@ class Spotify extends OAuth2Provider implements ClientCredentials, CSRFToken, To protected string|null $apiDocs = 'https://developer.spotify.com/documentation/web-api/'; protected string|null $applicationURL = 'https://developer.spotify.com/dashboard'; - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ public function me():AuthenticatedUser{ $json = $this->getMeResponseData('/v1/me'); diff --git a/src/Providers/Steam.php b/src/Providers/Steam.php index f23701f..bf0707f 100644 --- a/src/Providers/Steam.php +++ b/src/Providers/Steam.php @@ -75,6 +75,7 @@ public function getAccessToken(array $urlQuery):AccessToken{ * prepares the request body parameters for the access token request * * @param array $received + * @return array */ protected function getAccessTokenRequestBodyParams(array $received):array{ diff --git a/src/Providers/Stripe.php b/src/Providers/Stripe.php index 738d939..f84ea55 100644 --- a/src/Providers/Stripe.php +++ b/src/Providers/Stripe.php @@ -43,10 +43,7 @@ class Stripe extends OAuth2Provider implements CSRFToken, TokenRefresh, TokenInv protected string|null $apiDocs = 'https://stripe.com/docs/api'; protected string|null $applicationURL = 'https://dashboard.stripe.com/apikeys'; - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ public function me():AuthenticatedUser{ $json = $this->getMeResponseData('/accounts'); @@ -58,9 +55,6 @@ public function me():AuthenticatedUser{ return new AuthenticatedUser($userdata); } - /** - * @inheritDoc - */ protected function getInvalidateAccessTokenBodyParams(AccessToken $token, string $type):array{ $params = $token->extraParams; diff --git a/src/Providers/Tumblr.php b/src/Providers/Tumblr.php index 2844eda..775ea75 100644 --- a/src/Providers/Tumblr.php +++ b/src/Providers/Tumblr.php @@ -34,10 +34,7 @@ class Tumblr extends OAuth1Provider implements UserInfo{ protected string|null $apiDocs = 'https://www.tumblr.com/docs/en/api/v2'; protected string|null $applicationURL = 'https://www.tumblr.com/oauth/apps'; - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ public function me():AuthenticatedUser{ $json = $this->getMeResponseData('/v2/user/info'); diff --git a/src/Providers/Tumblr2.php b/src/Providers/Tumblr2.php index 2fea8ec..a0fdef0 100644 --- a/src/Providers/Tumblr2.php +++ b/src/Providers/Tumblr2.php @@ -40,10 +40,7 @@ class Tumblr2 extends OAuth2Provider implements CSRFToken, TokenRefresh, ClientC protected string|null $apiDocs = 'https://www.tumblr.com/docs/en/api/v2'; protected string|null $applicationURL = 'https://www.tumblr.com/oauth/apps'; - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ public function me():AuthenticatedUser{ $json = $this->getMeResponseData('/v2/user/info'); diff --git a/src/Providers/Twitter.php b/src/Providers/Twitter.php index 30223a9..739499e 100644 --- a/src/Providers/Twitter.php +++ b/src/Providers/Twitter.php @@ -38,10 +38,7 @@ class Twitter extends OAuth1Provider implements UserInfo{ protected string|null $apiDocs = 'https://developer.twitter.com/docs'; protected string|null $applicationURL = 'https://developer.twitter.com/apps'; - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ public function me():AuthenticatedUser{ $json = $this->getMeResponseData('/1.1/account/verify_credentials.json'); diff --git a/src/Providers/TwitterCC.php b/src/Providers/TwitterCC.php index d37315d..41eb5c5 100644 --- a/src/Providers/TwitterCC.php +++ b/src/Providers/TwitterCC.php @@ -39,7 +39,6 @@ class TwitterCC extends OAuth2Provider implements ClientCredentials{ protected string|null $applicationURL = 'https://developer.twitter.com/apps'; /** - * @inheritdoc * @throws \chillerlan\OAuth\Providers\ProviderException */ public function getAuthorizationURL(array|null $params = null, array|null $scopes = null):UriInterface{ @@ -47,7 +46,6 @@ public function getAuthorizationURL(array|null $params = null, array|null $scope } /** - * @inheritdoc * @throws \chillerlan\OAuth\Providers\ProviderException */ public function getAccessToken(string $code, string|null $state = null):AccessToken{ diff --git a/src/Providers/WordPress.php b/src/Providers/WordPress.php index 08ab432..2244157 100644 --- a/src/Providers/WordPress.php +++ b/src/Providers/WordPress.php @@ -38,10 +38,7 @@ class WordPress extends OAuth2Provider implements CSRFToken, UserInfo{ protected string|null $apiDocs = 'https://developer.wordpress.com/docs/api/'; protected string|null $applicationURL = 'https://developer.wordpress.com/apps/'; - /** - * @inheritDoc - * @codeCoverageIgnore - */ + /** @codeCoverageIgnore */ public function me():AuthenticatedUser{ $json = $this->getMeResponseData('/v1/me'); diff --git a/src/Storage/FileStorage.php b/src/Storage/FileStorage.php index fa96961..9d7a9ad 100644 --- a/src/Storage/FileStorage.php +++ b/src/Storage/FileStorage.php @@ -200,7 +200,13 @@ protected function loadFile(string $key, string $provider):string|null{ $path = $this->getFilepath($key, $provider); if(is_file($path)){ - return file_get_contents($path); + $contents = file_get_contents($path); + + if($contents === false){ + throw new OAuthStorageException('file_get_contents() error'); + } + + return $contents; } return null; @@ -239,7 +245,6 @@ protected function deleteFile(string $key, string $provider):void{ * deletes all matching files */ protected function deleteAll(string $key):void{ - /** @var \SplFileInfo $finfo */ foreach(new DirectoryIterator($this->options->fileStoragePath) as $finfo){ $name = $finfo->getFilename(); diff --git a/src/Storage/MemoryStorage.php b/src/Storage/MemoryStorage.php index 087659a..0a24981 100644 --- a/src/Storage/MemoryStorage.php +++ b/src/Storage/MemoryStorage.php @@ -23,7 +23,7 @@ class MemoryStorage extends OAuthStorageAbstract{ /** * the storage array * - * @var array> + * @var array> (the int keys are to keep phpstan silent) */ protected array $storage = [ self::KEY_TOKEN => [], diff --git a/tests/Core/AccessTokenTest.php b/tests/Core/AccessTokenTest.php index 085e48f..0596be5 100644 --- a/tests/Core/AccessTokenTest.php +++ b/tests/Core/AccessTokenTest.php @@ -28,6 +28,9 @@ protected function setUp():void{ $this->token = new AccessToken; } + /** + * @return array + */ public static function tokenDataProvider():array{ return [ 'accessTokenSecret' => ['accessTokenSecret', null, 'ACCESS_TOKEN'], @@ -48,6 +51,9 @@ public function testDefaultsGetSet(string $property, mixed $value, mixed $data): $this::assertSame($data, $this->token->{$property}); } + /** + * @return array + */ public static function expiryDataProvider():array{ $now = time(); @@ -81,6 +87,9 @@ public function testSetExpiry(DateTime|DateInterval|int|null $expires, int $expe } + /** + * @return array + */ public static function isExpiredDataProvider():array{ return [ '0 (f)' => [0, false], diff --git a/tests/Core/AuthenticatedUserTest.php b/tests/Core/AuthenticatedUserTest.php index e237e55..614beb9 100644 --- a/tests/Core/AuthenticatedUserTest.php +++ b/tests/Core/AuthenticatedUserTest.php @@ -55,6 +55,9 @@ public function testClassIsReadOnly():void{ $this::assertSame('test@example.com', $user->email); } + /** + * @return array> + */ public static function idProvider():array{ return [ 'null' => [null, null], @@ -71,6 +74,9 @@ public function testSetID(string|int|null $id, string|int|null $expexted):void{ $this::assertSame($expexted, $user->id); } + /** + * @return array> + */ public static function displayNameProvider():array{ return [ 'null' => [null, null], diff --git a/tests/Core/OAuthOptionsTest.php b/tests/Core/OAuthOptionsTest.php index 44b7673..37c87be 100644 --- a/tests/Core/OAuthOptionsTest.php +++ b/tests/Core/OAuthOptionsTest.php @@ -48,7 +48,7 @@ public function testSetFileStoragePathInvalidException():void{ new OAuthOptions(['fileStoragePath' => '/foo']); } - public function testClampPKCEVerifierLength(){ + public function testClampPKCEVerifierLength():void{ $options = new OAuthOptions; // lower limit = 43 diff --git a/tests/Core/OAuthProviderFactoryTest.php b/tests/Core/OAuthProviderFactoryTest.php index c1cde6e..d28e09f 100644 --- a/tests/Core/OAuthProviderFactoryTest.php +++ b/tests/Core/OAuthProviderFactoryTest.php @@ -20,7 +20,6 @@ use chillerlan\OAuthTest\Providers\ProviderUnitTestHttpClientFactory; use chillerlan\PHPUnitHttp\HttpFactoryTrait; use PHPUnit\Framework\TestCase; -use function realpath; /** * Tests the OAuthProviderFactory class @@ -32,8 +31,10 @@ class OAuthProviderFactoryTest extends TestCase{ protected string $HTTP_CLIENT_FACTORY = ProviderUnitTestHttpClientFactory::class; + protected OAuthProviderFactory $providerFactory; + protected function setUp():void{ - $this->initFactories(realpath($this::CACERT)); + $this->initFactories($this::CACERT); $this->providerFactory = new OAuthProviderFactory( $this->httpClient, diff --git a/tests/Core/UtilitiesTest.php b/tests/Core/UtilitiesTest.php index 446e143..03847f7 100644 --- a/tests/Core/UtilitiesTest.php +++ b/tests/Core/UtilitiesTest.php @@ -44,6 +44,9 @@ public function testGetProvidersInvalidPathException():void{ Utilities::getProviders('/foo'); } + /** + * @return array> + */ public static function encryptionFormatProvider():array{ return [ 'binary' => [Utilities::ENCRYPT_FORMAT_BINARY], diff --git a/tests/Providers/Live/AmazonAPITest.php b/tests/Providers/Live/AmazonAPITest.php index 38d2258..b1eca38 100644 --- a/tests/Providers/Live/AmazonAPITest.php +++ b/tests/Providers/Live/AmazonAPITest.php @@ -25,7 +25,7 @@ final class AmazonAPITest extends OAuth2ProviderLiveTestAbstract{ protected function assertMeResponse(AuthenticatedUser $user):void{ - $this::assertMatchesRegularExpression('/[a-z\d.]+/i', $user->id); + $this::assertMatchesRegularExpression('/[a-z\d.]+/i', ($user->id ?? '')); } public function testMeUnauthorizedAccessException():void{ diff --git a/tests/Providers/Live/BattleNetAPITest.php b/tests/Providers/Live/BattleNetAPITest.php index e4a94a8..694a8e1 100644 --- a/tests/Providers/Live/BattleNetAPITest.php +++ b/tests/Providers/Live/BattleNetAPITest.php @@ -26,7 +26,7 @@ final class BattleNetAPITest extends OAuth2ProviderLiveTestAbstract{ protected function assertMeResponse(AuthenticatedUser $user):void{ - $this::assertSame($this->TEST_USER, explode('#', $user->handle)[0]); + $this::assertSame($this->TEST_USER, explode('#', ($user->handle ?? ''))[0]); } } diff --git a/tests/Providers/Live/OAuthProviderLiveTestAbstract.php b/tests/Providers/Live/OAuthProviderLiveTestAbstract.php index b7abfb0..67ac098 100644 --- a/tests/Providers/Live/OAuthProviderLiveTestAbstract.php +++ b/tests/Providers/Live/OAuthProviderLiveTestAbstract.php @@ -22,7 +22,7 @@ /** * abstract OAuth live API test * - * @property \chillerlan\OAuth\Core\OAuthInterface $provider + * @property \chillerlan\OAuth\Core\OAuthInterface & UserInfo $provider */ abstract class OAuthProviderLiveTestAbstract extends ProviderLiveTestAbstract{ diff --git a/tests/Providers/ProviderLiveTestAbstract.php b/tests/Providers/ProviderLiveTestAbstract.php index 56c5ba7..590574f 100644 --- a/tests/Providers/ProviderLiveTestAbstract.php +++ b/tests/Providers/ProviderLiveTestAbstract.php @@ -50,7 +50,6 @@ protected function setUp():void{ protected function initConfig():void{ parent::initConfig(); - /** @var \chillerlan\OAuth\Core\OAuthInterface $providerFQCN */ $providerFQCN = $this->getProviderFQCN(); $this->dotEnv = (new DotEnv($this::CFGDIR, constant('TEST_ENVFILE'), false))->load(); diff --git a/tests/Providers/ProviderUnitTestAbstract.php b/tests/Providers/ProviderUnitTestAbstract.php index 1429099..744354e 100644 --- a/tests/Providers/ProviderUnitTestAbstract.php +++ b/tests/Providers/ProviderUnitTestAbstract.php @@ -33,7 +33,6 @@ use function constant; use function defined; use function ini_set; -use function realpath; use function sprintf; /** @@ -68,7 +67,7 @@ protected function setUp():void{ try{ $this->initConfig(); - $this->initFactories(realpath($this::CACERT)); + $this->initFactories($this::CACERT); $this->logger = (new ProviderTestLoggerFactory)->getLogger($this->ENV_IS_CI); // PSR-3 logger $this->options = $this->initOptions(); @@ -213,6 +212,8 @@ protected function setMockResponse(ResponseInterface|StreamInterface $response): /** * Creates a test access token with the given parameters or a set of defaults + * + * @param array|null $params */ protected function getTestToken(array|null $params = null):AccessToken{ diff --git a/tests/Providers/Unit/OAuth2ProviderUnitTestAbstract.php b/tests/Providers/Unit/OAuth2ProviderUnitTestAbstract.php index 057dee7..2bf6185 100644 --- a/tests/Providers/Unit/OAuth2ProviderUnitTestAbstract.php +++ b/tests/Providers/Unit/OAuth2ProviderUnitTestAbstract.php @@ -27,7 +27,7 @@ /** * OAuth2 unit test * - * @property \chillerlan\OAuth\Core\OAuth2Interface $provider + * @property OAuth2Interface & ClientCredentials & CSRFToken & PAR & PKCE & TokenRefresh $provider */ abstract class OAuth2ProviderUnitTestAbstract extends OAuthProviderUnitTestAbstract{ @@ -453,7 +453,7 @@ public function testCheckCSRFStateMismatchException():void{ $this->provider->checkState('unknown_state'); } - public function testSetCSRFStateNotSupportedException(){ + public function testSetCSRFStateNotSupportedException():void{ if($this->provider instanceof CSRFToken){ $this->markTestSkipped('CSRFToken supported'); @@ -465,7 +465,7 @@ public function testSetCSRFStateNotSupportedException(){ $this->provider->setState([]); } - public function testCheckCSRFStateNotSupportedException(){ + public function testCheckCSRFStateNotSupportedException():void{ if($this->provider instanceof CSRFToken){ $this->markTestSkipped('CSRFToken supported'); @@ -553,6 +553,8 @@ public function testRefreshAccessTokenNotSupportedException():void{ * test values from RFC-7636, Appendix B * * @link https://datatracker.ietf.org/doc/html/rfc7636#appendix-B + * + * @return array */ public static function challengeProvider():array{ $verifier = 'dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk'; diff --git a/tests/Providers/Unit/OAuthProviderUnitTestAbstract.php b/tests/Providers/Unit/OAuthProviderUnitTestAbstract.php index 9e01ee2..3c7fc9f 100644 --- a/tests/Providers/Unit/OAuthProviderUnitTestAbstract.php +++ b/tests/Providers/Unit/OAuthProviderUnitTestAbstract.php @@ -29,7 +29,7 @@ /** * abstract OAuth unit test * - * @property \chillerlan\OAuth\Core\OAuthInterface $provider + * @property OAuthInterface & TokenInvalidate & UserInfo $provider */ abstract class OAuthProviderUnitTestAbstract extends ProviderUnitTestAbstract{ @@ -70,6 +70,9 @@ public function testGetRequestBodyWithString():void{ $this::assertSame($body, $request->getBody()->getContents()); } + /** + * @return array, 1: string, 2: string}> + */ public static function arrayBodyProvider():array{ $body = ['test' => 'nope']; @@ -113,6 +116,9 @@ public function testGetRequestBodyInvalidContentTypeForArrayException():void{ * request target */ + /** + * @return array + */ public static function requestTargetProvider():array{ return [ 'empty' => ['', 'https://example.com/api'], @@ -328,6 +334,9 @@ public function testHandleMeResponseErrorNoJSONContentTypeException():void{ $this->invokeReflectionMethod('handleMeResponseError', [$response]); } + /** + * @return array + */ public static function jsonErrorProvider():array{ $message = 'oh noes';