diff --git a/.github/workflows/autocomment.yml b/.github/workflows/autocomment.yml index c40a127b..521e12c4 100644 --- a/.github/workflows/autocomment.yml +++ b/.github/workflows/autocomment.yml @@ -19,5 +19,6 @@ jobs: Please make sure you have followed our contributing guidelines. We will review it as soon as possible. In the meanwhile make sure your PR checks the following boxes - [ ] Is based on an issue - [ ] Has been locally tested - - [ ] Has been tested with the admin UI + - [ ] Has been tested with the admin ui + - [ ] Has been documented - [ ] Has been discussed with the development team in an open channel diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index c37ee2ea..bb6570cd 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -21,5 +21,5 @@ jobs: git config user.name "GitHub Actions" git config user.email "" git add docs/classes - git commit -m "Update phpdoc" || echo "No changes to commit" + git commit -m "Update phpdoc-md" || echo "No changes to commit" git push \ No newline at end of file diff --git a/Service/CacheService.php b/Service/CacheService.php index d33f1e7a..0dc4c3a7 100644 --- a/Service/CacheService.php +++ b/Service/CacheService.php @@ -16,6 +16,7 @@ use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Serializer\SerializerInterface; +use Psr\Log\LoggerInterface; /** * Service to call external sources. @@ -37,6 +38,7 @@ class CacheService private SymfonyStyle $io; private ParameterBagInterface $parameters; private SerializerInterface $serializer; + private LoggerInterface $logger; /** * @param AuthenticationService $authenticationService @@ -88,6 +90,8 @@ public function cleanup() $filter = []; $objects = $collection->find($filter)->toArray(); (isset($this->io) ? $this->io->writeln('Found '.count($objects).'') : ''); + + //$this->logger->info("Ran cache cleanup"); } /** @@ -107,6 +111,7 @@ public function warmup() if (!isset($this->client)) { (isset($this->io) ? $this->io->writeln('No cache client found, halting warmup') : ''); + //$this->info->logger("Ran cache Warmup"); return Command::SUCCESS; } @@ -163,6 +168,7 @@ public function warmup() (isset($this->io) ? $this->io->writeln(['Removing deleted objects', '============']) : ''); $this->removeDataFromCache($this->client->objects->json, 'App:ObjectEntity'); + //$this->logger->info("Ran cache Warmup"); return Command::SUCCESS; } @@ -200,11 +206,6 @@ private function ioCatchException(Exception $exception) */ public function cacheObject(ObjectEntity $objectEntity): ObjectEntity { - // For when we can't generate a schema for an ObjectEntity (for example setting an id on ObjectEntity created with testData) - if (!$objectEntity->getEntity()) { - return $objectEntity; - } - // Backwards compatablity if (!isset($this->client)) { return $objectEntity; @@ -243,6 +244,8 @@ public function cacheObject(ObjectEntity $objectEntity): ObjectEntity (isset($this->io) ? $this->io->writeln('Wrote object '.$objectEntity->getId()->toString().' of type '.$objectEntity->getEntity()->getName().' to cache') : ''); } + + //$this->logger->debug("Cached object"); return $objectEntity; } @@ -264,6 +267,7 @@ public function removeObject(ObjectEntity $object): void $collection = $this->client->objects->json; $collection->findOneAndDelete(['_id'=>$id]); + //$this->logger->debug("Delete object"); } /** @@ -292,6 +296,7 @@ public function getObject(string $id) return $this->cacheObject($object)->toArray(['embedded' => true]); } + //$this->logger->debug("Retrieved object from cache"); return false; } @@ -366,6 +371,8 @@ public function searchObjects(string $search = null, array $filter = [], array $ $total = $collection->count($filter); // Make sure to add the pagination properties in response + + //$this->logger->debug("Searched cache for objects"); return $this->handleResultPagination($completeFilter, $results, $total); } @@ -753,27 +760,27 @@ public function cacheEndpoint(Endpoint $endpoint): Endpoint } if (isset($this->io)) { - $this->io->writeln('Start caching endpoint '.$endpoint->getId()->toString().' with name: '.$endpoint->getName()); + $this->io->writeln('Start caching object '.$endpoint->getId()->toString()); } - $updatedEndpoint = $this->entityManager->getRepository('App:Endpoint')->find($endpoint->getId()); - if ($updatedEndpoint instanceof Endpoint) { - $endpoint = $updatedEndpoint; + $updatedEntity = $this->entityManager->getRepository('App:Endpoint')->find($endpoint->getId()); + if ($updatedEntity instanceof Endpoint) { + $entity = $updatedEntity; } elseif (isset($this->io)) { - $this->io->writeln('Could not find an Endpoint with id: '.$endpoint->getId()->toString()); + $this->io->writeln('Could not find an Endpoint with id: '.$objectEntity->getId()->toString()); } $collection = $this->client->endpoints->json; - $endpointArray = $this->serializer->normalize($endpoint); + $entityArray = $this->serializer->normalize($entity); if ($collection->findOneAndReplace( ['id' => $endpoint->getId()->toString()], - $endpointArray, + $entityArray, ['upsert'=>true] )) { - (isset($this->io) ? $this->io->writeln('Updated endpoint '.$endpoint->getId()->toString().' to cache') : ''); + (isset($this->io) ? $this->io->writeln('Updated object '.$endpoint->getId()->toString().' of type Endpoint to cache') : ''); } else { - (isset($this->io) ? $this->io->writeln('Wrote object '.$endpoint->getId()->toString().' to cache') : ''); + (isset($this->io) ? $this->io->writeln('Wrote object '.$endpoint->getId()->toString().' of type Endpoint to cache') : ''); } return $endpoint; diff --git a/Service/InstallationService.php b/Service/InstallationService.php index cdd7b221..cd729bcb 100644 --- a/Service/InstallationService.php +++ b/Service/InstallationService.php @@ -11,6 +11,7 @@ use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Finder\Finder; +use Psr\Log\LoggerInterface; class InstallationService { @@ -296,21 +297,11 @@ public function handleData($file) if (array_key_exists('_id', $object) && $objectEntity = $this->em->getRepository('App:ObjectEntity')->findOneBy(['id' => $object['_id']])) { $this->io->writeln(['', 'Object '.$object['_id'].' already exists, so updating']); } elseif (array_key_exists('_id', $object)) { - $this->io->writeln('Set id to '.$object['_id']); - - // Nice doctrine setId shizzle - $objectEntity = new ObjectEntity(); - $this->em->persist($objectEntity); - $objectEntity->setId($object['_id']); - $this->em->persist($objectEntity); - $this->em->flush(); - $this->em->refresh($objectEntity); - - $objectEntity = $this->em->getRepository('App:ObjectEntity')->findOneBy(['id' => $object['_id']]); - - $objectEntity->setEntity($entity); - + $this->io->writeln('Set external id to '.$object['_id']); + $objectEntity = new ObjectEntity($entity); $this->io->writeln('Creating new object with existing id '.$objectEntity->getId()); + + $objectEntity->setExternalId($object['_id']); } else { $objectEntity = new ObjectEntity($entity); $this->io->writeln(['', 'Creating new object']); @@ -349,4 +340,48 @@ public function handleInstaller($file) return $installationService->install(); } + + /** + * Handles forced id's on object entities + * + * @param ObjectEntity $objectEntity + * @return ObjectEntity + */ + private function saveOnFixedId(ObjectEntity $objectEntity): ObjectEntity{ + // Save the values + $values = $object->getObjectValues()->toArray(); + $object->clearAllValues(); + + // We have an object entity with a fixed id that isn't in the database, so we need to act + if($objectEntity->getId() && !$this->em->contains($objectEntity)){ + // Sve the id + $id = $objectEntity->getId(); + // Create the entity + $this->em->persist($objectEntity); + $this->em->flush(); + $this->em->refresh($objectEntity); + // Reset the id + $objectEntity->setId($id); + $this->em->persist($objectEntity); + $this->em->flush(); + $objectEntity = $this->entityManager->getRepository('App:ObjectEntity')->findOneBy(['id' => $id]); + } + + // Loop trough the values + foreach ($values as $objectValue){ + // If the value itsself is an object it might also contain fixed id's + foreach ($objectValue->getObjects() as $subobject){ + $subobject = $this->saveOnFixedId($subobject); + } + + $objectEntity->addObjectValue($objectValue); + } + + $this->em->persist($objectEntity); + $this->em->flush(); + + return $objectEntity; + } + + } diff --git a/Service/RequestService.php b/Service/RequestService.php index f66cb90f..0cdcb940 100644 --- a/Service/RequestService.php +++ b/Service/RequestService.php @@ -6,10 +6,8 @@ use App\Entity\Endpoint; use App\Entity\Entity; use App\Entity\Gateway as Source; -use App\Entity\Log; use App\Entity\ObjectEntity; use App\Event\ActionEvent; -use App\Service\LogService; use App\Service\ObjectEntityService; use App\Service\ResponseService; use Doctrine\Common\Collections\Criteria; @@ -20,6 +18,7 @@ use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\Security\Core\Security; use Symfony\Component\Serializer\SerializerInterface; +use Psr\Log\LoggerInterface; /** * Handles incomming request from endpoints or controllers that relate to the gateways object structure (eav). @@ -36,8 +35,7 @@ class RequestService // todo: we might want to move or rewrite code instead of using these services here: private ResponseService $responseService; private ObjectEntityService $objectEntityService; - private LogService $logService; - private CallService $callService; + private LoggerInterface $logger; private Security $security; private EventDispatcherInterface $eventDispatcher; private SerializerInterface $serializer; @@ -47,7 +45,7 @@ class RequestService * @param CacheService $cacheService * @param ResponseService $responseService * @param ObjectEntityService $objectEntityService - * @param LogService $logService + * @param LoggerInterface $RequestLog * @param CallService $callService * @param Security $security * @param EventDispatcherInterface $eventDispatcher @@ -58,7 +56,7 @@ public function __construct( CacheService $cacheService, ResponseService $responseService, ObjectEntityService $objectEntityService, - LogService $logService, + LoggerInterface $RequestLog, CallService $callService, Security $security, EventDispatcherInterface $eventDispatcher, @@ -68,7 +66,7 @@ public function __construct( $this->cacheService = $cacheService; $this->responseService = $responseService; $this->objectEntityService = $objectEntityService; - $this->logService = $logService; + $this->logger = $RequestLog; $this->callService = $callService; $this->security = $security; $this->eventDispatcher = $eventDispatcher; @@ -247,6 +245,8 @@ public function proxyHandler(array $data, array $configuration): Response "No Endpoint in data['endpoint']" : "This Endpoint has no Proxy: {$data['endpoint']->getName()}"; + $this->logger->error('This Endpoint has no Proxy'); + return new Response( json_encode(['Message' => $message]), Response::HTTP_NOT_FOUND, @@ -255,6 +255,9 @@ public function proxyHandler(array $data, array $configuration): Response } if ($proxy instanceof Source && !$proxy->getIsEnabled()) { + + $this->logger->error('Source is not enabled'); + return new Response( json_encode(['Message' => "This Source is not enabled: {$proxy->getName()}"]), Response::HTTP_OK, // This should be ok so we can disable Sources without creating error responses? @@ -278,6 +281,8 @@ public function proxyHandler(array $data, array $configuration): Response ] ); + $this->logger->debug('Handled proxy request'); + // Let create a responce from the guzle call $responce = new Response( $result->getBody()->getContents(), @@ -411,10 +416,11 @@ public function requestHandler(array $data, array $configuration): Response $responseLog = new Response(is_string($this->content) || is_null($this->content) ? $this->content : null, 200, ['CoreBundle' => 'GetItem']); $session = new Session(); $session->set('object', $this->id); - $this->logService->saveLog($this->logService->makeRequest(), $responseLog, 15, is_array($this->content) ? json_encode($this->content) : $this->content); + $this->logger->debug('GET on collection endpoint',['method'=>'GET']); } else { //$this->data['query']['_schema'] = $this->data['endpoint']->getEntities()->first()->getReference(); - $result = $this->cacheService->searchObjects(null, $filters, $allowedSchemas['id']); + $result = $this->cacheService->searchObjects(null, $filters, $allowedSchemas); + $this->logger->debug('GET on item endpoint',['method'=>'GET']); } break; case 'POST': @@ -446,6 +452,7 @@ public function requestHandler(array $data, array $configuration): Response // Use validation to throw an error } + $this->logger->debug('PUT on collection endpoint',['method'=>'PUT']); $result = $this->cacheService->getObject($this->object->getId()); break; case 'PUT': @@ -477,6 +484,8 @@ public function requestHandler(array $data, array $configuration): Response // Use validation to throw an error } + + $this->logger->debug('PUT on collection endpoint',['method'=>'PUT']); $result = $this->cacheService->getObject($this->object->getId()); break; case 'PATCH': @@ -508,6 +517,7 @@ public function requestHandler(array $data, array $configuration): Response // Use validation to throw an error } + $this->logger->debug('PATC on collection endpoint',['method'=>'PATCH']); $result = $this->cacheService->getObject($this->object->getId()); break; case 'DELETE': @@ -531,12 +541,14 @@ public function requestHandler(array $data, array $configuration): Response // $this->cacheService - removeObject($this->id); /* @todo this is hacky, the above schould alredy do this */ $this->entityManager->flush(); + $this->logger->debug('DELETE on collection endpoint',['method'=>'DELETE']); return new Response('Succesfully deleted object', '202'); break; default: - break; + $this->logger->error('Unknown method: '.$this->data['method'],['method'=>$this->data['method']]); return new Response('Unkown method'.$this->data['method'], '404'); + break; } $this->entityManager->flush(); @@ -642,60 +654,6 @@ private function handleMetadataSelf(&$result, array $metadataSelf) $result['_self'] = $resultMetadataSelf; } - /** - * @param array $data The data from the call - * @param array $configuration The configuration from the call - * - * @return array The modified data - */ - public function itemRequestHandler(array $data, array $configuration): array - { - $this->data = $data; - $this->configuration = $configuration; - - $method = $this->data['request']->getMethod(); - $content = $this->data['request']->getContent(); - - // Lets see if we have an object - if (array_key_exists('id', $this->data)) { - $this->id = $data['id']; - if (!$this->object = $this->cacheService->getObject($data['id'])) { - // Throw not found - } - } - - switch ($method) { - case 'GET': - break; - case 'PUT': - - if ($validation = $this->object->validate($content) && $this->object->hydrate($content, true)) { - $this->entityManager->persist($this->object); - } else { - // Use validation to throw an error - } - break; - case 'PATCH': - if ($this->object->hydrate($content) && $validation = $this->object->validate()) { - $this->entityManager->persist($this->object); - } else { - // Use validation to throw an error - } - break; - case 'DELETE': - $this->entityManager->remove($this->object); - - return new Response('', '202'); - break; - default: - break; - } - - $this->entityManager->flush(); - - return $this->createResponse($this->object); - } - /** * This function searches all the objectEntities and formats the data. * @@ -723,6 +681,9 @@ public function searchRequestHandler(array $data, array $configuration): array ]; } + + $this->logger->debug('Handled search request'); + $this->data['response'] = $response = new Response( json_encode($response), 200, diff --git a/Service/UserService.php b/Service/UserService.php new file mode 100644 index 00000000..810e3b04 --- /dev/null +++ b/Service/UserService.php @@ -0,0 +1,143 @@ +entityManager = $entityManager; + $this->authenticationService = $authenticationService; + } + + /** + * @param User $user + * @param string $password + * + * @return bool + */ + public function validatePassword(User $user, string $password): bool + { + + // Todo: this oforuce is hacky as hell and a security danger + return true; + } + + /** + * Generates a JWT token for a user. + * + * @todo should be moved to authentication service + * + * @param array $payload + * + * @return string + */ + public function createJWTToken(array $payload): string + { + $algorithmManager = new AlgorithmManager([new RS512()]); + $pem = $this->fileService->writeFile('privatekey', base64_decode($this->parameterBag->get('private_key'))); + $jwk = JWKFactory::createFromKeyFile($pem); + $this->fileService->removeFile($pem); + + $jwsBuilder = new JWSBuilder($algorithmManager); + $jws = $jwsBuilder + ->create() + ->withPayload(json_encode($payload)) + ->addSignature($jwk, ['alg' => 'RS512']) + ->build(); + + $serializer = new CompactSerializer(); + + return $serializer->serialize($jws, 0); + } + + /** + * Creates a CsrfToken for a user. + * + * @todo should be moved to authentication service + * + * @param User $user + * + * @return CsrfToken + */ + public function createCsrfToken(User $user): CsrfToken + { + $tokenManager = new CsrfTokenManager(); + + return $tokenManager->getToken($user->getId()->toString()); + } + + /** + * Creates the user object responce for login and /me requests. + * + * @param User $user + * + * @return array + */ + public function createResponce(User $user) + { + // prepare the security groups + $groups = []; + $applications = []; + + foreach ($user->getSecurityGroups() as $securityGroup) { + $securityGroups[] = ['id'=>$securityGroup->getId()->toString(), 'name'=>$securityGroup->getName()]; + } + + foreach ($user->getApplications() as $application) { + $applications[] = ['id'=>$application->getId()->toString(), 'name'=>$application->getName()]; + } + + // prepare the responce + $responce = [ + '_self'=> [ + 'id'=> $user->getId()->toString(), + ], + 'id' => $user->getId()->toString(), + 'organisation'=> [ + 'id' => $user->getOrganisation()->getId()->toString(), + 'name'=> $user->getOrganisation()->getName(), + ], + 'applications'=> $user->getApplications()->getId()->toString(), + 'username' => $user->getUsername(), + 'email' => $user->getEmail(), + 'locale' => $user->getLocale(), + 'person' => $user->getPerson(), + 'roles' => $user->getRoles(), + 'groups' => $securityGroups, + 'jwtToken' => $this->createJwtToken($user), + 'csrfToken' => $this->createCsrfToken($user), + 'organisation'=> $user->getOrganisation()->getId()->toString(), + 'application' => $user->getApplications()->getId()->toString(), + ]; + + return $responce; + } + + public function validateToken() + { + } +} diff --git a/docs/classes/Service/RequestService.md b/docs/classes/Service/RequestService.md index 932aa387..67720aa4 100644 --- a/docs/classes/Service/RequestService.md +++ b/docs/classes/Service/RequestService.md @@ -15,7 +15,6 @@ Handles incomming request from endpoints or controllers that relate to the gatew |[createResponse](#requestservicecreateresponse)|Creating the responce object.| |[getId](#requestservicegetid)|Get the ID from given parameters.| |[getSchema](#requestservicegetschema)|Get the schema from given parameters returns false if no schema could be established.| -|[itemRequestHandler](#requestserviceitemrequesthandler)|| |[proxyHandler](#requestserviceproxyhandler)|| |[realRequestQueryAll](#requestservicerealrequestqueryall)|A function to replace Request->query->all() because Request->query->all() will replace some characters with an underscore.| |[requestHandler](#requestservicerequesthandler)|Handles incomming requests and is responsible for generating a responce.| @@ -151,30 +150,6 @@ Get the schema from given parameters returns false if no schema could be establi -