diff --git a/.phpstorm.meta.php b/.phpstorm.meta.php index 4d852b0..6f65aa7 100644 --- a/.phpstorm.meta.php +++ b/.phpstorm.meta.php @@ -9,8 +9,6 @@ exitPoint(\Baraja\StructuredApi\BaseEndpoint::sendSuccess()); exitPoint(\Baraja\StructuredApi\BaseEndpoint::sendError()); exitPoint(\Baraja\StructuredApi\BaseEndpoint::sendItems()); -exitPoint(\Baraja\StructuredApi\BaseEndpoint::redirect()); -exitPoint(\Baraja\StructuredApi\BaseEndpoint::redirectUrl()); exitPoint(\Baraja\StructuredApi\ThrowStatusResponse::invoke()); exitPoint(\Baraja\StructuredApi\Response\Status\StatusResponse::invoke()); exitPoint(\Baraja\StructuredApi\Response\Status\ErrorResponse::invoke()); diff --git a/composer.json b/composer.json index 1c8baf7..69568e8 100644 --- a/composer.json +++ b/composer.json @@ -20,8 +20,7 @@ "nette/http": "^3.1", "nette/application": "^3.1", "nette/robot-loader": "^3.3", - "nette/caching": "^3.1", - "nette/security": "^3.1" + "nette/caching": "^3.1" }, "require-dev": { "phpstan/phpstan": "^1.0", @@ -31,6 +30,7 @@ "phpstan/phpstan-strict-rules": "^1.0", "spaze/phpstan-disallowed-calls": "^2.0", "baraja-core/localization": "^2.0", + "baraja-core/cas": "^1.0", "tracy/tracy": "^2.8", "roave/security-advisories": "dev-master" }, diff --git a/src/ApiManager.php b/src/ApiManager.php index 588936c..f0986c1 100644 --- a/src/ApiManager.php +++ b/src/ApiManager.php @@ -5,6 +5,7 @@ namespace Baraja\StructuredApi; +use Baraja\Localization\Localization; use Baraja\RuntimeInvokeException; use Baraja\Serializer\Serializer; use Baraja\ServiceMethodInvoker; @@ -61,26 +62,26 @@ public function __construct( public function run(?string $path = null, ?array $params = [], ?string $method = null, bool $throw = false): void { $path ??= Url::get()->getRelativeUrl(); - $this->checkFirewall(); $method = $method === null || $method === '' ? Helpers::httpMethod() : $method; $params = array_merge($this->safeGetParams($path), $this->getBodyParams($method), $params ?? []); $panel = new Panel($path, $params, $method); - Debugger::getBar()->addPanel($panel); + $isDebugger = class_exists(Debugger::class); + if ($isDebugger) { + Debugger::getBar()->addPanel($panel); + } if (preg_match('/^api\/v(?\d{1,3}(?:\.\d{1,3})?)\/(?.*?)$/', $path, $pathParser) === 1) { try { $route = $this->route((string) preg_replace('/^(.*?)(\?.*|)$/', '$1', $pathParser['path']), $pathParser['v'], $params); - $response = null; try { - $endpoint = $this->getEndpointService($route['class'], $params); + $endpoint = $this->getEndpointService($route['class']); $panel->setEndpoint($endpoint); $response = $this->process($endpoint, $params, $route['action'], $method, $panel); $panel->setResponse($response); } catch (StructuredApiException $e) { throw $e; } catch (\Throwable $e) { - $isDebugger = class_exists(Debugger::class); - if ($isDebugger === true) { + if ($isDebugger) { Debugger::log($e, ILogger::EXCEPTION); } @@ -149,39 +150,14 @@ public function getEndpoints(): array * Create new API endpoint instance with all injected dependencies. * * @param class-string $className - * @param array $params * @internal */ - public function getEndpointService(string $className, array $params): Endpoint + public function getEndpointService(string $className): Endpoint { $endpoint = $this->container->getEndpoint($className); - $endpoint->setConvention($this->convention); - - $createReflection = static function (object $class, string $propertyName): ?\ReflectionProperty { - try { - $ref = new \ReflectionProperty($class, $propertyName); - $ref->setAccessible(true); - - return $ref; - } catch (\ReflectionException) { - $refClass = new \ReflectionClass($class); - $parentClass = $refClass->getParentClass(); - while ($parentClass !== false) { - try { - $ref = $parentClass->getProperty($propertyName); - $ref->setAccessible(true); - - return $ref; - } catch (\ReflectionException) { - $parentClass = $refClass->getParentClass(); - } - } - } - - return null; - }; - - $createReflection($endpoint, 'data')?->setValue($endpoint, $params); + if ($endpoint instanceof BaseEndpoint) { + $endpoint->convention = $this->convention; + } return $endpoint; } @@ -336,8 +312,17 @@ private function invokeActionMethod( array $params, Panel $panel, ): ?Response { - $endpoint->startup(); - $endpoint->startupCheck(); + if (PHP_SAPI !== 'cli') { + $httpRequest = class_exists(Request::class) + ? $this->container->getByType(Request::class) + : null; + $localization = class_exists(Localization::class) + ? $this->container->getByType(Localization::class) + : null; + if ($httpRequest !== null && $localization !== null) { + $localization->processHttpRequest($httpRequest); + } + } try { $invoker = new ServiceMethodInvoker($this->projectEntityRepository); @@ -381,8 +366,9 @@ private function invokeActionMethod( if ($method !== 'GET' && $response === null) { $response = new JsonResponse($this->convention, ['state' => 'ok']); } - - $endpoint->saveState(); + if ($endpoint instanceof BaseEndpoint) { + $endpoint->saveState(); + } return $response; } @@ -432,19 +418,6 @@ private function getBodyParams(string $method): array } - private function checkFirewall(): void - { - if (str_contains($_SERVER['HTTP_USER_AGENT'] ?? '', 'CloudFlare-AlwaysOnline') === true) { - header('HTTP/1.0 403 Forbidden'); - echo 'Access denied | API endpoint'; - echo '

Access denied

'; - echo '

API endpoint crawling is disabled for robots.

'; - echo '

Information for developers: Endpoint API indexing is disabled for privacy reasons. At the same time, robots can crawl a disproportionate amount of data, copying your valuable data.'; - die; - } - } - - private function rewriteInvalidArgumentException(\InvalidArgumentException $e): ?Response { $message = null; diff --git a/src/Bridge/LinkGeneratorBridge.php b/src/Bridge/LinkGeneratorBridge.php new file mode 100644 index 0000000..7d4b3d8 --- /dev/null +++ b/src/Bridge/LinkGeneratorBridge.php @@ -0,0 +1,37 @@ + $params + * @throws \InvalidArgumentException + */ + public function link(string $dest, array $params = []): string + { + if ($this->linkGenerator === null) { + throw new \RuntimeException('Service LinkGenerator is not available. Did you install nette/application?'); + } + + try { + return $this->linkGenerator->link(ltrim($dest, ':'), $params); + } catch (\Throwable $e) { + throw new \InvalidArgumentException($e->getMessage(), $e->getCode()); + } + } +} diff --git a/src/Endpoint/BaseEndpoint.php b/src/Endpoint/BaseEndpoint.php index 319815c..84fc77c 100644 --- a/src/Endpoint/BaseEndpoint.php +++ b/src/Endpoint/BaseEndpoint.php @@ -5,30 +5,17 @@ namespace Baraja\StructuredApi; -use Baraja\Localization\Localization; +use Baraja\CAS\User; +use Baraja\CAS\UserIdentity; +use Baraja\StructuredApi\Bridge\LinkGeneratorBridge; use Baraja\StructuredApi\Entity\Convention; -use Baraja\StructuredApi\Middleware\Container; use Baraja\StructuredApi\Response\Status\ErrorResponse; use Baraja\StructuredApi\Response\Status\OkResponse; use Baraja\StructuredApi\Response\Status\StatusResponse; use Baraja\StructuredApi\Response\Status\SuccessResponse; -use Nette\Caching\Cache; -use Nette\Caching\Storage; -use Nette\Http\Request; -use Nette\Localization\Translator; -use Nette\Security\Authorizator; -use Nette\Security\IIdentity; -use Nette\Security\User; abstract class BaseEndpoint implements Endpoint { - // TODO: @deprecated since 2022-04-18, use Perl case instead - public const - FLASH_MESSAGE_SUCCESS = 'success', - FLASH_MESSAGE_INFO = 'info', - FLASH_MESSAGE_WARNING = 'warning', - FLASH_MESSAGE_ERROR = 'error'; - public const FlashMessageSuccess = 'success', FlashMessageInfo = 'info', @@ -45,22 +32,15 @@ abstract class BaseEndpoint implements Endpoint /** @var callable[] */ public array $onSaveState = []; - /** @deprecated since 2022-10-29, please use static DIC constructor dependencies. */ - protected Container $container; + public ?User $user = null; - protected Convention $convention; + public LinkGeneratorBridge $linkGenerator; - /** - * @var mixed[] - * @deprecated since 2022-10-29, please use native typed method arguments. - */ - protected array $data = []; + public Convention $convention; /** @var array */ private array $messages = []; - private bool $startupCheck = false; - public function __toString(): string { @@ -68,50 +48,6 @@ public function __toString(): string } - public function startup(): void - { - if (PHP_SAPI !== 'cli' && class_exists('\Baraja\Localization\Localization') === true) { - $httpRequest = $this->container->getByType(Request::class); - $localization = $this->container->getByType(Localization::class); - $localization->processHttpRequest($httpRequest); - } - - $this->startupCheck = true; - } - - - /** - * Get current endpoint name. - * @deprecated since 2022-10-29, please use native class name or meta entity. - */ - final public function getName(): string - { - return (string) preg_replace('/^(?:.*\\\\)?([A-Z0-9][a-z0-9]+)Endpoint$/', '$1', static::class); - } - - - /** - * Get raw data. - * This method obtains an array of all user-passed values. - * Individual values may not correspond to the input validation. - * This method is suitable for processing large amounts of data that - * do not have a predetermined structure that we are able to describe as an object. - * - * @return array - * @deprecated since 2022-10-29, please use native typed method arguments. - */ - public function getData(): array - { - return $this->data; - } - - - final public function setConvention(Convention $convention): void - { - $this->convention = $convention; - } - - /** * @phpstan-return never-return * @throws ThrowResponse @@ -290,14 +226,6 @@ final public function formatBootstrapSelectList(array $haystack): array } - final public function startupCheck(): void - { - if ($this->startupCheck === false) { - throw new \LogicException(sprintf('Method %s::startup() or its descendant doesn\'t call parent::startup()."', static::class)); - } - } - - final public function saveState(): void { foreach ($this->onSaveState as $saveState) { @@ -308,15 +236,11 @@ final public function saveState(): void final public function getUser(): User { - static $user; - if ($user === null) { - if (class_exists('Nette\Security\User') === false) { - throw new \RuntimeException('Service "Nette\Security\User" is not available. Did you install nette/security?'); - } - $user = $this->container->getByType('Nette\Security\User'); + if ($this->user === null) { + throw new \RuntimeException('Service "Baraja\CAS\User" is not available. Did you install baraja-core/cas?'); } - return $user; + return $this->user; } @@ -330,42 +254,18 @@ final public function isUserLoggedIn(): bool } - final public function getUserEntity(): ?IIdentity + final public function getUserEntity(): ?UserIdentity { return $this->getUser()->getIdentity(); } - /** @deprecated since 2022-10-29, please use baraja-core/cas instead. */ - final public function getAuthorizator(): Authorizator - { - $authorizator = $this->getUser()->getAuthorizatorIfExists(); - if ($authorizator === null) { - throw new \RuntimeException('Authorizator has not been set.'); - } - - return $authorizator; - } - - /** * @param array $params */ final public function link(string $dest, array $params = []): string { - static $linkGenerator; - if ($linkGenerator === null) { - if (class_exists('Nette\Application\LinkGenerator') === false) { - throw new \RuntimeException('Service "Nette\Application\LinkGenerator" is not available. Did you install nette/application?'); - } - $linkGenerator = $this->container->getByType('Nette\Application\LinkGenerator'); - } - - try { - return $linkGenerator->link(ltrim($dest, ':'), $params); - } catch (\Throwable $e) { - throw new \InvalidArgumentException($e->getMessage(), $e->getCode()); - } + return $this->linkGenerator->link(ltrim($dest, ':'), $params); } @@ -382,107 +282,4 @@ final public function linkSafe(string $dest, array $params = []): ?string return null; } } - - - /** - * @param array $params - * @param positive-int $httpCode - * @phpstan-return never-return - * @throws ThrowResponse - * @deprecated since 2022-10-29, please remove this feature from your project. Redirect is anti-pattern for API. - */ - final public function redirect(string $dest, array $params = [], int $httpCode = 301): void - { - $this->redirectUrl((string) $this->linkSafe($dest, $params), $httpCode); - } - - - /** - * @param positive-int $httpCode - * @phpstan-return never-return - * @throws ThrowResponse - * @deprecated since 2022-10-29, please remove this feature from your project. Redirect is anti-pattern for API. - */ - final public function redirectUrl(string $url, int $httpCode = 301): void - { - if (Helpers::isUrl($url) === false) { - throw new \InvalidArgumentException(sprintf('Haystack "%s" is not valid URL for redirect.', $url)); - } - throw new ThrowResponse(new RedirectResponse($this->convention, ['url' => $url], $httpCode)); - } - - - /** @deprecated since 2022-10-29, please implement it in your project. */ - final public function getCache(?string $namespace = null): Cache - { - static $storage; - static $cache = []; - $name = sprintf('api---%s', strtolower($namespace ?? $this->getName())); - - if ($storage === null) { - $storage = $this->container->getByType(Storage::class); - } - if (isset($cache[$name]) === false) { - $cache[$name] = new Cache($storage, $name); - } - - return $cache[$name]; - } - - - /** @deprecated since 2022-10-29, please implement it in your project. */ - final public function getTranslator(): Translator - { - static $translator; - if ($translator === null) { - $translator = $this->container->getByType(Translator::class); - } - - return $translator; - } - - - /** - * @param array|mixed ...$parameters - * @deprecated since 2022-10-29, please implement it in your project. - */ - final public function translate(mixed $message, ...$parameters): string - { - return $this->getTranslator()->translate($message, $parameters); - } - - - /** - * @return array - * @deprecated since 2022-10-29, please inject scala parameters to individual DIC services. - */ - final public function getParameters(): array - { - return $this->container->getParameters(); - } - - - /** - * @deprecated since 2022-10-29, please inject scala parameters to individual DIC services. - */ - final public function getParameter(string $key, mixed $defaultValue = null): mixed - { - return $this->container->getParameters()[$key] ?? $defaultValue; - } - - - final public function injectContainer(Container $container): void - { - $this->container = $container; - } - - - /** - * Is it an AJAX request? - * @deprecated since 2022-10-29, please use static helper. - */ - final public function isAjax(): bool - { - return Helpers::isAjax(); - } } diff --git a/src/Endpoint/Endpoint.php b/src/Endpoint/Endpoint.php index cd37aa7..4f8d1a2 100644 --- a/src/Endpoint/Endpoint.php +++ b/src/Endpoint/Endpoint.php @@ -6,15 +6,7 @@ use Baraja\Service; -use Baraja\StructuredApi\Entity\Convention; interface Endpoint extends Service { - public function setConvention(Convention $convention): void; - - public function startup(): void; - - public function startupCheck(): void; - - public function saveState(): void; } diff --git a/src/Endpoint/TestEndpoint.php b/src/Endpoint/TestEndpoint.php index e14e9ee..c841373 100644 --- a/src/Endpoint/TestEndpoint.php +++ b/src/Endpoint/TestEndpoint.php @@ -22,14 +22,14 @@ public function actionDefault(string $hello = 'world'): TestResponse return new TestResponse( name: 'Test API endpoint', hello: $hello, - endpoint: $this->getName(), + endpoint: 'Test', ); } public function postCreateUser(string $username, string $password): CreateUserResponse { - if (strlen($password) < 8) { + if (mb_strlen($password) < 8) { $this->sendError('Password must be at least 8 characters long.'); } diff --git a/src/MetaDataManager.php b/src/MetaDataManager.php index ceddef2..305029a 100644 --- a/src/MetaDataManager.php +++ b/src/MetaDataManager.php @@ -5,6 +5,8 @@ namespace Baraja\StructuredApi; +use Baraja\CAS\User; +use Baraja\StructuredApi\Bridge\LinkGeneratorBridge; use Baraja\StructuredApi\Middleware\Container; use Nette\DI\Extensions\InjectExtension; use Nette\Loaders\RobotLoader; @@ -69,7 +71,14 @@ public static function createEndpointServices(): array public static function endpointInjectDependencies(Endpoint $endpoint, Container $container): void { if ($endpoint instanceof BaseEndpoint) { - $endpoint->injectContainer($container); + $endpoint->user = class_exists(User::class) + ? $container->getByType(User::class) + : null; + $endpoint->linkGenerator = new LinkGeneratorBridge( + class_exists('Nette\Application\LinkGenerator') + ? $container->getByType('Nette\Application\LinkGenerator') + : null, + ); } $injectProperties = InjectExtension::getInjectProperties($endpoint::class); if ($injectProperties === []) { diff --git a/src/Middleware/PermissionExtension.php b/src/Middleware/PermissionExtension.php index 29ca9ce..7f2b696 100644 --- a/src/Middleware/PermissionExtension.php +++ b/src/Middleware/PermissionExtension.php @@ -5,6 +5,7 @@ namespace Baraja\StructuredApi\Middleware; +use Baraja\CAS\User; use Baraja\StructuredApi\Attributes\PublicEndpoint; use Baraja\StructuredApi\Attributes\Role; use Baraja\StructuredApi\Endpoint; @@ -12,13 +13,12 @@ use Baraja\StructuredApi\Helpers; use Baraja\StructuredApi\JsonResponse; use Baraja\StructuredApi\Response; -use Nette\Security\User; final class PermissionExtension implements MatchExtension { public function __construct( - private User $user, private Convention $convention, + private ?User $user = null, ) { } @@ -64,7 +64,7 @@ private function checkPermission(Endpoint $endpoint, string $method, string $act $refClass = new \ReflectionClass($endpoint); $docComment = trim((string) $refClass->getDocComment()); $public = $refClass->getAttributes(PublicEndpoint::class) !== [] || str_contains($docComment, '@public'); - if ($public === false && $this->user->isLoggedIn() === false) { + if ($public === false && ($this->user?->isLoggedIn() ?? false) === false) { throw new \InvalidArgumentException('This API endpoint is private. You must be logged in to use.'); } if ($this->checkRoles($refClass)) { @@ -87,7 +87,7 @@ private function checkPermission(Endpoint $endpoint, string $method, string $act } if ( $public === false - && $this->user->isLoggedIn() === true + && ($this->user?->isLoggedIn() ?? false) === true ) { // private endpoint, but user is logged in return true; } @@ -114,6 +114,9 @@ private function getRoleList(\ReflectionClass|\ReflectionMethod $ref): array private function checkRoles(\ReflectionClass|\ReflectionMethod $ref): bool { + if ($this->user === null) { + return false; + } foreach ($this->getRoleList($ref) as $role) { if ($this->user->isInRole($role) === true) { return true; diff --git a/src/Response/BaseResponse.php b/src/Response/BaseResponse.php index 8cc525e..fc95cab 100644 --- a/src/Response/BaseResponse.php +++ b/src/Response/BaseResponse.php @@ -57,16 +57,6 @@ final public function toArray(): array } - /** - * @return mixed[] - * @deprecated since 2022-07-25, use toArray(). - */ - final public function getArray(): array - { - return $this->toArray(); - } - - final public function getHttpCode(): int { return $this->httpCode; diff --git a/src/Response/Response.php b/src/Response/Response.php index d2bfac3..99c2306 100644 --- a/src/Response/Response.php +++ b/src/Response/Response.php @@ -15,10 +15,4 @@ public function __toString(): string; * @return mixed[] */ public function toArray(): array; - - /** - * @deprecated since 2022-07-25, use toArray(). - * @return mixed[] - */ - public function getArray(): array; } diff --git a/src/Schema/TestSchema.php b/src/Schema/TestSchema.php deleted file mode 100644 index 2ecb72e..0000000 --- a/src/Schema/TestSchema.php +++ /dev/null @@ -1,21 +0,0 @@ - (new Type('string'))->nullable()->pattern('\w{3,}'), - 'email' => (new Type('string'))->pattern('^.+@.+$'), - ]); - } -}