diff --git a/.env b/.env index 5e06d903b..4acd4c6d4 100644 --- a/.env +++ b/.env @@ -172,3 +172,5 @@ RABBITMQ_PASSWORD=changeme! PUBLICCODE= APP_DEFAULT_REDIRECT_URL=http://localhost/login/oidc/dex + +LOG_LEVEL=debug diff --git a/README.md b/README.md index 148b8f26c..bead2140e 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,16 @@ [![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/commonground-gateway)](https://artifacthub.io/packages/search?repo=commonground-gateway) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/b6de6f6071044e1783a145afa27f1829)](https://www.codacy.com/gh/CommonGateway/CoreBundle/dashboard?utm_source=github.com&utm_medium=referral&utm_content=CommonGateway/CoreBundle&utm_campaign=Badge_Grade) + The Common Gateway repository provides a quick Kubernetes wrapper for the Common Gateway Symfony Bundle. In other words, it doesn't aim to be its own code base but simply contains the files needed to create Kubernetes images and Helm installers for the core bundle. -If you are looking for the Common Gateway code base, please refer to the Core Bundle repository instead, as that's where you will find all the appropriate documentation. +If you are looking for the Common Gateway code base, please refer to the [Core Bundle repository](https://github.com/CommonGateway/coreBundle) instead, as that's where you will find all the appropriate documentation. ## Quick start (for local development) +> **Warning** +> The most recent documentation for setting up your common gateway is maintained in [this document](https://github.com/CommonGateway/CoreBundle/blob/master/docs/features/Installation.md). The documentation in this document might or might not be up to date with this documentation. + + > **Dependencies** > - To clone the codebase to your locale machine you will need Git > - To run the gateway on your local machine, you will need Docker Desktop. @@ -91,3 +96,89 @@ If you want your cluster to be able to set up its own certificates for SSL/HTTPS $ kubectl apply -f letsencrypt-ci.yaml --kubeconfig=[path-to-your-kubeconfig] ```` +### Installed dependencies +The common gateway relies on a number of software dependencies the helm chart installs alongside the common gateway. If you want however to connect to existing versions of these dependencies, you can disable them. + +#### PostgreSQL +The common gateway is dependent on a SQL database for internal operations. We recommend to use PostgreSQL as the database type the common gateway was designed with. However we also support MySQL, MariaDB and Microsoft SQL Server, although the latter defers from newer standards and henceforth can cause some issues and therefore is not recommended. + +To disable PostgreSQL: set the setting `postgresql.enabled` to `false`, and enter a SQL url (`pgsql://`, `psql://` for postgres, `mysqli://` for MySQL and MariaDB or `pdo_sqlsrv://`) in the field `postgresql.url`. Also, if the database is a Microsoft SQL Server database, don't forget to change the field `databaseType` to mssql. + +The PostgreSQL database that is installed if `postgresql.enabled` is set to `true` is installed with [this chart](https://artifacthub.io/packages/helm/bitnami/postgresql/12.1.2). This chart contains default resource requests that are not overwritten. + +In case the resource requests and/or limits have to be overridden this can be done using the following parameters: +```yaml +postgresql: + primary: + resources: + limits: {} + requests: {} +``` +The default requests are 256Mi memory and 200m vCPU. + + +#### MongoDB +For serving content quickly the common gateway relies on a document cache which is run in MongoDB. MongoDB is also used to store the logs of the common gateway. + +To disable MongoDB: set the setting `mongodb.enabled` to `false`, and enter a SQL url (`mongodb://`) in the field `mongodb.url`. + +The MongoDB database that is installed if `mongodb.enabled` is set to `true` is installed with [this chart](https://artifacthub.io/packages/helm/bitnami/mongodb/13.4.4). This chart does not contain default resource requests, therefore the gateway chart overrides these requests with the following values: + +```yaml +mongodb: + resources: + requests: + cpu: 1 + memory: 6Gi +``` + +These limits are set to high limits to accommodate for large databases, and can be tweaked to lower values if the size of the database is not expected to exceed a couple of Gigabytes. + +#### RabbitMQ +To run events from the event-driven architecture asynchronously, the common gateway uses a message queue on RabbitMQ. + +The RabbitMQ dependency can be disabled by setting `rabbitmq.enabled` to `false`. However, it is not possible at this time to connect to an external instance of rabbitmq, this means that events cannot be run asynchronously, and that the workers have to be disabled by setting `consumer.replicaCount` to `0`. + +The RabbitMQ message queue that is installed if `rabbitmq.enabled` is set to `true` is installed with [this chart](https://artifacthub.io/packages/helm/bitnami/rabbitmq/11.91.1). This chart does not contain default resource requests, therefore the gateway chart overrides these requests with the following values: + +```yaml +rabbitmq: + resources: + requests: + cpu: 200m + memory: 256Mi +``` + +These are values that are not observed to be exceeded on busy environments with large numbers of asynchronous events. + +#### Redis +For session storage and key value caching, a redis cache is in place. + +The Redis dependency can be disabled by setting `redis.enabled` to `false`. However, it is not possible at this time to connect to an external instance of redis. This means that in order to have consistent session storage the common gateway can only be run on one container by setting the `replicaCount` parameter to `1`. + +The Redis cache that is in stalled if `redis.enabled` is set to `true` is installed with [this chart](https://artifacthub.io/packages/helm/bitnami/redis/17.3.11). This chart does not contain default resource requests, therefore the gateway chart overrides these requests with the following values: + +In case the resource requests and/or limits have to be overridden this can be done using the following parameters: +```yaml +redis: + master: + resources: + requests: + cpu: 20m + memory: 128Mi +``` + +#### Gateway UI +The common gateway also offers its own User Interface for admin. + +This user interface is installed with [this chart](https://raw.githubusercontent.com/ConductionNL/gateway-ui/development/helm/). + +The resource requests for these containers are set to: + +```yaml +gateway-ui: + resources: + requests: + cpu: 10m + memory: 128Mi +``` diff --git a/api/.env b/api/.env index 8caf27abc..06c02c64d 100644 --- a/api/.env +++ b/api/.env @@ -30,3 +30,8 @@ DATABASE_URL="postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVers ###> nelmio/cors-bundle ### CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$' ###< nelmio/cors-bundle ### + +###> symfony/sendinblue-mailer ### +# MAILER_DSN=sendinblue+api://KEY@default +# MAILER_DSN=sendinblue+smtp://USERNAME:PASSWORD@default +###< symfony/sendinblue-mailer ### diff --git a/api/Dockerfile b/api/Dockerfile index c925a99ae..ea5f04b18 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -52,7 +52,7 @@ RUN set -eux; \ pecl install \ apcu-${APCU_VERSION} \ redis \ - amqp \ + amqp-1.11.0 \ sqlsrv-${SQLSRV_VERSION} \ mongodb \ ; \ diff --git a/api/composer.json b/api/composer.json index 928972a95..e770835d7 100644 --- a/api/composer.json +++ b/api/composer.json @@ -13,7 +13,7 @@ "alcaeus/mongo-php-adapter": "^1.2", "api-platform/core": "^2.6", "beberlei/doctrineextensions": "^1.3", - "commongateway/corebundle": "<1.1", + "commongateway/corebundle": "^1.2.30", "composer/package-versions-deprecated": "1.11.99.3", "conduction/commongroundbundle": "dev-feature-gateway", "conduction/digidbundle": "dev-master", @@ -34,6 +34,7 @@ "monolog/monolog": "^2.8.0", "nelmio/cors-bundle": "^2.1", "phpdocumentor/reflection-docblock": "^5.2", + "phpoffice/phpspreadsheet": "^1.29", "phpoffice/phpword": "^0.18.2", "respect/validation": "^2.2", "setono/cron-expression-bundle": "^1.5", @@ -53,6 +54,7 @@ "symfony/proxy-manager-bridge": "5.3.*", "symfony/runtime": "5.3.*", "symfony/security-bundle": "5.3.*", + "symfony/sendinblue-mailer": "5.3.*", "symfony/serializer": "5.3.*", "symfony/twig-bundle": "5.3.*", "symfony/validator": "5.3.*", diff --git a/api/composer.lock b/api/composer.lock index b6bc95996..d774589af 100644 --- a/api/composer.lock +++ b/api/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bf5d953467af3adff8ab65c6597200ed", + "content-hash": "7fea6053a03cf5e0d75d8c8180484df3", "packages": [ { "name": "adbario/php-dot-notation", @@ -660,23 +660,75 @@ }, { "name": "commongateway/corebundle", - "version": "1.0.107", + "version": "1.2.30", "source": { "type": "git", "url": "https://github.com/CommonGateway/CoreBundle.git", - "reference": "90cf1eed699114daefd4c5817c309dd9f7bd8117" + "reference": "86fe52055235f37195e9eaa276f786a7f8f39ac8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CommonGateway/CoreBundle/zipball/90cf1eed699114daefd4c5817c309dd9f7bd8117", - "reference": "90cf1eed699114daefd4c5817c309dd9f7bd8117", + "url": "https://api.github.com/repos/CommonGateway/CoreBundle/zipball/86fe52055235f37195e9eaa276f786a7f8f39ac8", + "reference": "86fe52055235f37195e9eaa276f786a7f8f39ac8", "shasum": "" }, "require": { + "adbario/php-dot-notation": "^3", + "alcaeus/mongo-php-adapter": "^1.2", + "api-platform/core": "^2.6", + "beberlei/doctrineextensions": "^1.3", + "composer/package-versions-deprecated": "1.11.99.3", + "conduction/commongroundbundle": "dev-feature-gateway", + "conduction/digidbundle": "dev-master", + "conduction/samlbundle": "dev-master", + "doctrine/annotations": "^1.0", + "doctrine/doctrine-bundle": "^2.4", + "doctrine/doctrine-migrations-bundle": "^3.1", + "doctrine/orm": "^2.9", + "dompdf/dompdf": "^2", + "endroid/qr-code-bundle": "3.4", + "ext-ctype": "*", + "ext-fileinfo": "*", + "ext-iconv": "*", + "ext-json": "*", + "guzzlehttp/guzzle": "6.5.8", + "guzzlehttp/psr7": "1.9", + "hautelook/alice-bundle": "^2.9.0", + "jwadhams/json-logic-php": "^1.4", "league/flysystem-bundle": "^2.4", "league/flysystem-ftp": "^2.4", + "league/flysystem-ziparchive": "^2.3", + "mongodb/mongodb": "^1.15", + "monolog/monolog": "^2.8.0", + "nelmio/cors-bundle": "^2.1", "php": "^7.4.0", - "symfony/monolog-bundle": "^3.8" + "phpdocumentor/reflection-docblock": "^5.2", + "phpoffice/phpspreadsheet": "^1.29", + "phpoffice/phpword": "^0.18.2", + "respect/validation": "^2.2", + "setono/cron-expression-bundle": "^1.5", + "symfony/asset": "5.3.*", + "symfony/console": "5.3.*", + "symfony/contracts": "^2.4.0", + "symfony/dotenv": "5.3.*", + "symfony/expression-language": "5.3.*", + "symfony/flex": "^1.3.1", + "symfony/framework-bundle": "5.3.*", + "symfony/http-client": "5.3.*", + "symfony/mailer": "5.3.*", + "symfony/mailgun-mailer": "5.3.*", + "symfony/messenger": "5.3.*", + "symfony/monolog-bundle": "^3.8", + "symfony/property-access": "5.3.*", + "symfony/property-info": "5.3.*", + "symfony/proxy-manager-bridge": "5.3.*", + "symfony/runtime": "5.3.*", + "symfony/security-bundle": "5.3.*", + "symfony/serializer": "5.3.*", + "symfony/twig-bundle": "5.3.*", + "symfony/validator": "5.3.*", + "symfony/web-profiler-bundle": "5.3.*", + "symfony/yaml": "5.3.*" }, "conflict": { "doctrine/dbal": "<2.5", @@ -687,19 +739,21 @@ "cache/integration-tests": "dev-master", "doctrine/cache": "~1.6", "doctrine/dbal": "~2.5", + "phpunit/phpunit": "^9.5", "predis/predis": "~1.1", + "symfony/browser-kit": "5.3.*", "symfony/config": "^5.1", + "symfony/css-selector": "5.3.*", "symfony/dependency-injection": "~3.4|~4.1|~5.0", + "symfony/phpunit-bridge": "^6.1", + "symfony/stopwatch": "5.3.*", "symfony/var-dumper": "^4.1.1|^5.1" }, "type": "symfony-bundle", "autoload": { "psr-4": { - "CommonGateway\\CoreBundle\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "CommonGateway\\CoreBundle\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -729,7 +783,7 @@ "issues": "https://github.com/CommonGateway/CoreBundle/issues", "source": "https://github.com/CommonGateway/CoreBundle" }, - "time": "2023-06-16T07:41:34+00:00" + "time": "2023-11-24T15:08:02+00:00" }, { "name": "composer/ca-bundle", @@ -3205,6 +3259,67 @@ }, "time": "2019-11-29T22:50:41+00:00" }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.16.0", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/523407fb06eb9e5f3d59889b3978d5bfe94299c8", + "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8", + "shasum": "" + }, + "require": { + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0" + }, + "require-dev": { + "cerdic/css-tidy": "^1.7 || ^2.0", + "simpletest/simpletest": "dev-master" + }, + "suggest": { + "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.", + "ext-bcmath": "Used for unit conversion and imagecrash protection", + "ext-iconv": "Converts text to and from non-UTF-8 encodings", + "ext-tidy": "Used for pretty-printing HTML" + }, + "type": "library", + "autoload": { + "files": [ + "library/HTMLPurifier.composer.php" + ], + "psr-0": { + "HTMLPurifier": "library/" + }, + "exclude-from-classmap": [ + "/library/HTMLPurifier/Language/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ], + "support": { + "issues": "https://github.com/ezyang/htmlpurifier/issues", + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.16.0" + }, + "time": "2022-09-18T07:06:19+00:00" + }, { "name": "fakerphp/faker", "version": "v1.15.0", @@ -4605,6 +4720,56 @@ ], "time": "2022-01-31T19:07:20+00:00" }, + { + "name": "league/flysystem-ziparchive", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-ziparchive.git", + "reference": "4ed7ed1aaf7e4a8ebf5b86609dd24ba20006b488" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-ziparchive/zipball/4ed7ed1aaf7e4a8ebf5b86609dd24ba20006b488", + "reference": "4ed7ed1aaf7e4a8ebf5b86609dd24ba20006b488", + "shasum": "" + }, + "require": { + "ext-zip": "*", + "league/flysystem": "^2.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\ZipArchive\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "ZIP filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "file", + "files", + "filesystem", + "zip" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem-ziparchive/issues", + "source": "https://github.com/thephpleague/flysystem-ziparchive/tree/2.3.1" + }, + "time": "2021-11-04T18:54:38+00:00" + }, { "name": "league/html-to-markdown", "version": "4.10.0", @@ -4747,6 +4912,191 @@ ], "time": "2022-04-17T13:12:02+00:00" }, + { + "name": "maennchen/zipstream-php", + "version": "2.2.6", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "30ad6f93cf3efe4192bc7a4c9cad11ff8f4f237f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/30ad6f93cf3efe4192bc7a4c9cad11ff8f4f237f", + "reference": "30ad6f93cf3efe4192bc7a4c9cad11ff8f4f237f", + "shasum": "" + }, + "require": { + "myclabs/php-enum": "^1.5", + "php": "^7.4 || ^8.0", + "psr/http-message": "^1.0", + "symfony/polyfill-mbstring": "^1.0" + }, + "require-dev": { + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.9", + "guzzlehttp/guzzle": "^6.5.3 || ^7.2.0", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.4", + "phpunit/phpunit": "^8.5.8 || ^9.4.2", + "vimeo/psalm": "^4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/2.2.6" + }, + "funding": [ + { + "url": "https://github.com/maennchen", + "type": "github" + }, + { + "url": "https://opencollective.com/zipstream", + "type": "open_collective" + } + ], + "time": "2022-11-25T18:57:19+00:00" + }, + { + "name": "markbaker/complex", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", + "keywords": [ + "complex", + "mathematics" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPComplex/issues", + "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2" + }, + "time": "2022-12-06T16:21:08+00:00" + }, + { + "name": "markbaker/matrix", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPMatrix.git", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "^4.0", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "sebastian/phpcpd": "^4.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@demon-angel.eu" + } + ], + "description": "PHP Class for working with matrices", + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "keywords": [ + "mathematics", + "matrix", + "vector" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPMatrix/issues", + "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1" + }, + "time": "2022-12-02T22:17:43+00:00" + }, { "name": "masterminds/html5", "version": "2.7.5", @@ -5852,6 +6202,111 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "time": "2020-09-17T18:55:26+00:00" }, + { + "name": "phpoffice/phpspreadsheet", + "version": "1.29.0", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "fde2ccf55eaef7e86021ff1acce26479160a0fa0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fde2ccf55eaef7e86021ff1acce26479160a0fa0", + "reference": "fde2ccf55eaef7e86021ff1acce26479160a0fa0", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "ezyang/htmlpurifier": "^4.15", + "maennchen/zipstream-php": "^2.1 || ^3.0", + "markbaker/complex": "^3.0", + "markbaker/matrix": "^3.0", + "php": "^7.4 || ^8.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-main", + "dompdf/dompdf": "^1.0 || ^2.0", + "friendsofphp/php-cs-fixer": "^3.2", + "mitoteam/jpgraph": "^10.3", + "mpdf/mpdf": "^8.1.1", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^1.1", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^8.5 || ^9.0 || ^10.0", + "squizlabs/php_codesniffer": "^3.7", + "tecnickcom/tcpdf": "^6.5" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "ext-intl": "PHP Internationalization Functions", + "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "support": { + "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.29.0" + }, + "time": "2023-06-14T22:48:31+00:00" + }, { "name": "phpoffice/phpword", "version": "0.18.2", @@ -6370,6 +6825,57 @@ }, "time": "2021-05-03T11:20:27+00:00" }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/master" + }, + "time": "2017-10-23T01:57:42+00:00" + }, { "name": "ralouphie/getallheaders", "version": "3.0.3", @@ -11635,6 +12141,71 @@ ], "time": "2021-11-05T16:25:46+00:00" }, + { + "name": "symfony/sendinblue-mailer", + "version": "v5.3.14", + "source": { + "type": "git", + "url": "https://github.com/symfony/sendinblue-mailer.git", + "reference": "269383a0dac2ab4d5a1a0f0c042ba114d132cb24" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/sendinblue-mailer/zipball/269383a0dac2ab4d5a1a0f0c042ba114d132cb24", + "reference": "269383a0dac2ab4d5a1a0f0c042ba114d132cb24", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/mailer": "^5.1" + }, + "require-dev": { + "symfony/http-client": "^4.4|^5.0" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mailer\\Bridge\\Sendinblue\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Sendinblue Mailer Bridge", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/sendinblue-mailer/tree/v5.3.14" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:51:59+00:00" + }, { "name": "symfony/serializer", "version": "v5.3.12", @@ -14983,5 +15554,5 @@ "ext-json": "*" }, "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/api/config/packages/common_ground.yaml b/api/config/packages/common_ground.yaml index 5fd1ca595..d40100742 100644 --- a/api/config/packages/common_ground.yaml +++ b/api/config/packages/common_ground.yaml @@ -78,6 +78,8 @@ parameters: env(DOCUMENTATION_LICENCE_URL): "https://www.apache.org/licenses/LICENSE-2.0.html" env(DOCUMENTATION_VERSION): "1.0.1" + env(LOG_LEVEL): "warning" + env(APP_DEFAULT_REDIRECT_URL): "http://localhost" env(APP_DEFAULT_BACK_URL): "http://localhost" @@ -142,6 +144,7 @@ parameters: app_shasign: "%env(APP_SHASIGN)%" mode: "%env(APP_MODE)%" + log_level: "%env(LOG_LEVEL)%" # Busnes Engine env(APP_COMMONGROUND_BE_LOCATION): "https://ac.%env(APP_DOMAIN)%" diff --git a/api/config/packages/doctrine.yaml b/api/config/packages/doctrine.yaml index 89fbc13ba..4a17d0314 100644 --- a/api/config/packages/doctrine.yaml +++ b/api/config/packages/doctrine.yaml @@ -18,6 +18,16 @@ doctrine: dir: '%kernel.project_dir%/src/Entity' prefix: 'App\Entity' alias: App + metadata_cache_driver: + type: service + id: doctrine.system_cache_provider + query_cache_driver: + type: service + id: doctrine.system_cache_provider + result_cache_driver: + type: service + id: doctrine.result_cache_provider + dql: string_functions: regexp_replace: DoctrineExtensions\Query\Postgresql\RegexpReplace diff --git a/api/config/packages/framework.yaml b/api/config/packages/framework.yaml index f3ecdd32e..23c913ea2 100644 --- a/api/config/packages/framework.yaml +++ b/api/config/packages/framework.yaml @@ -19,7 +19,8 @@ framework: cache: prefix_seed: commonground_gateway_cache - app: cache.adapter.redis_tag_aware + app: cache.adapter.redis + system: cache.adapter.redis default_redis_provider: "redis://%env(REDIS_HOST)%:%env(int:REDIS_PORT)%" parameters: samesite: none diff --git a/api/config/packages/monolog.yaml b/api/config/packages/monolog.yaml index fba6cff82..1be6718da 100644 --- a/api/config/packages/monolog.yaml +++ b/api/config/packages/monolog.yaml @@ -26,7 +26,7 @@ when@dev: nested: type: mongo path: "%kernel.logs_dir%/%kernel.environment%.log" - level: debug + level: "%log_level%" mongo: host: "%cache_url%" database: logs @@ -35,7 +35,7 @@ when@dev: endpointLog: type: mongo path: "%kernel.logs_dir%/%kernel.environment%.endpoint.log" - level: debug + level: "%log_level%" channels: ["endpoint"] mongo: host: "%cache_url%" @@ -45,7 +45,7 @@ when@dev: requestLog: type: mongo path: "%kernel.logs_dir%/%kernel.environment%.request.log" - level: debug + level: "%log_level%" channels: ["request"] mongo: host: "%cache_url%" @@ -55,7 +55,7 @@ when@dev: schemaLog: type: mongo path: "%kernel.logs_dir%/%kernel.environment%.schema.log" - level: debug + level: "%log_level%" channels: ["schema"] mongo: host: "%cache_url%" @@ -65,7 +65,7 @@ when@dev: cronjobLog: type: mongo path: "%kernel.logs_dir%/%kernel.environment%.cronjob.log" - level: debug + level: "%log_level%" channels: ["cronjob"] mongo: host: "%cache_url%" @@ -74,7 +74,7 @@ when@dev: id: monolog.logger.mongo actionLog: type: mongo - level: debug + level: "%log_level%" channels: ["action"] mongo: host: "%cache_url%" @@ -86,7 +86,7 @@ when@dev: objectLog: type: mongo path: "%kernel.logs_dir%/%kernel.environment%.object.log" - level: debug + level: "%log_level%" formatter: monolog.formatter.json channels: ["object"] mongo: @@ -97,7 +97,7 @@ when@dev: synchronizationLog: type: mongo path: "%kernel.logs_dir%/%kernel.environment%.synchronization.log" - level: debug + level: "%log_level%" channels: ["synchronization"] mongo: host: "%cache_url%" @@ -107,7 +107,7 @@ when@dev: pluginLog: type: mongo path: "%kernel.logs_dir%/%kernel.environment%.synchronization.log" - level: debug + level: "%log_level%" channels: ["plugin"] mongo: host: "%cache_url%" @@ -117,7 +117,7 @@ when@dev: cacheLog: type: mongo path: "%kernel.logs_dir%/%kernel.environment%.cache.log" - level: debug + level: "%log_level%" channels: ["cache"] mongo: host: "%cache_url%" @@ -127,7 +127,7 @@ when@dev: mappingLog: type: mongo path: "%kernel.logs_dir%/%kernel.environment%.cache.log" - level: debug + level: "%log_level%" channels: ["mapping"] mongo: host: "%cache_url%" @@ -137,7 +137,7 @@ when@dev: callLog: type: mongo path: "%kernel.logs_dir%/%kernel.environment%.call.log" - level: debug + level: "%log_level%" channels: ["call"] mongo: host: "%cache_url%" @@ -169,7 +169,7 @@ when@test: nested: type: stream path: "%kernel.logs_dir%/%kernel.environment%.log" - level: debug + level: "%log_level%" when@prod: monolog: @@ -196,7 +196,7 @@ when@prod: endpointLog: type: mongo path: "%kernel.logs_dir%/%kernel.environment%.endpoint.log" - level: warning + level: "%log_level%" channels: ["endpoint"] mongo: host: "%cache_url%" @@ -206,7 +206,7 @@ when@prod: requestLog: type: mongo path: "%kernel.logs_dir%/%kernel.environment%.request.log" - level: warning + level: "%log_level%" channels: ["request"] mongo: host: "%cache_url%" @@ -216,7 +216,7 @@ when@prod: schemaLog: type: mongo path: "%kernel.logs_dir%/%kernel.environment%.schema.log" - level: warning + level: "%log_level%" channels: ["schema"] mongo: host: "%cache_url%" @@ -226,7 +226,7 @@ when@prod: cronjobLog: type: mongo path: "%kernel.logs_dir%/%kernel.environment%.cronjob.log" - level: warning + level: "%log_level%" channels: ["cronjob"] mongo: host: "%cache_url%" @@ -235,7 +235,7 @@ when@prod: id: monolog.logger.mongo actionLog: type: mongo - level: warning + level: "%log_level%" channels: ["action"] mongo: host: "%cache_url%" @@ -247,7 +247,7 @@ when@prod: objectLog: type: mongo path: "%kernel.logs_dir%/%kernel.environment%.object.log" - level: warning + level: "%log_level%" formatter: monolog.formatter.json channels: ["object"] mongo: @@ -258,7 +258,7 @@ when@prod: synchronizationLog: type: mongo path: "%kernel.logs_dir%/%kernel.environment%.synchronization.log" - level: warning + level: "%log_level%" channels: ["synchronization"] mongo: host: "%cache_url%" @@ -268,7 +268,7 @@ when@prod: pluginLog: type: mongo path: "%kernel.logs_dir%/%kernel.environment%.synchronization.log" - level: warning + level: "%log_level%" channels: ["plugin"] mongo: host: "%cache_url%" @@ -278,7 +278,7 @@ when@prod: cacheLog: type: mongo path: "%kernel.logs_dir%/%kernel.environment%.cache.log" - level: warning + level: "%log_level%" channels: ["cache"] mongo: host: "%cache_url%" @@ -288,7 +288,7 @@ when@prod: mappingLog: type: mongo path: "%kernel.logs_dir%/%kernel.environment%.cache.log" - level: warning + level: "%log_level%" channels: ["mapping"] mongo: host: "%cache_url%" @@ -298,7 +298,7 @@ when@prod: callLog: type: mongo path: "%kernel.logs_dir%/%kernel.environment%.call.log" - level: warning + level: "%log_level%" channels: ["call"] mongo: host: "%cache_url%" diff --git a/api/config/packages/prod/messenger.yaml b/api/config/packages/prod/messenger.yaml new file mode 100644 index 000000000..24641e04e --- /dev/null +++ b/api/config/packages/prod/messenger.yaml @@ -0,0 +1,33 @@ +parameters: + env(MESSENGER_TRANSPORT_DSN): "amqp://%env(RABBITMQ_USERNAME)%:%env(RABBITMQ_PASSWORD)%@%env(RABBITMQ_HOST)%:%env(RABBITMQ_PORT)%/%2f/messages" + +framework: + messenger: + # Uncomment this (and the failed transport below) to send failed messages to this transport for later handling. + failure_transport: failed + + transports: + # https://symfony.com/doc/current/messenger.html#transport-configuration + async: + dsn: '%env(MESSENGER_TRANSPORT_DSN)%' + + retry_strategy: + max_retries: 5 + delay: 5000 + multiplier: 2 + max_delay: 300000 + failed: 'doctrine://default' + buses: + messenger.bus.default: + middleware: + - router_context + + + + routing: + # Route your messages to the transports + 'App\Message\ActionMessage': async + 'App\Message\NotificationMessage': async + 'App\Message\SyncPageMessage': async + 'CommonGateway\CoreBundle\Message\CacheMessage': async + 'CommonGateway\CoreBundle\Message\ValueMessage': async diff --git a/api/config/services.yaml b/api/config/services.yaml index e23ae0023..1e33357ab 100644 --- a/api/config/services.yaml +++ b/api/config/services.yaml @@ -50,7 +50,7 @@ services: # - 'my_prefix' # TODO: test if we still need this here, might be deprecated - App\Subscriber\ValueDatabaseSubscriber: + CommonGateway\CoreBundle\Subscriber\ValueDatabaseSubscriber: tags: - name: 'doctrine.event_subscriber' priority: 0 @@ -90,3 +90,22 @@ services: class: App\Logger\SessionDataProcessor tags: - { name: monolog.processor } + + doctrine.result_cache_provider: + class: Symfony\Component\Cache\DoctrineProvider + arguments: + - '@doctrine.result_cache_pool' + doctrine.system_cache_provider: + class: Symfony\Component\Cache\DoctrineProvider + arguments: + - '@doctrine.system_cache_pool' + + doctrine.system_cache_pool: + parent: 'cache.app' + tags: + - { name: 'cache.pool', namespace: 'app' } + + doctrine.result_cache_pool: + parent: 'cache.app' + tags: + - { name: 'cache.pool', namespace: 'app' } diff --git a/api/docker/php/docker-entrypoint.sh b/api/docker/php/docker-entrypoint.sh index 7050456b6..a7a99f934 100644 --- a/api/docker/php/docker-entrypoint.sh +++ b/api/docker/php/docker-entrypoint.sh @@ -34,6 +34,14 @@ if [ "$1" = 'php-fpm' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then echo "Creating the database" bin/console doctrine:database:create --if-not-exists --no-interaction + # Make sure we clear query,result & metadata doctrine cache before migrations during init + if [ "$APP_INIT" = 'true' ]; then + echo "Clearing query,result & metadata doctrine cache" + bin/console doctrine:cache:clear-query + bin/console doctrine:cache:clear-result + bin/console doctrine:cache:clear-metadata + fi + # Get the database inline with the newest version. echo "Migrating the database to the currently used version" bin/console doctrine:migrations:migrate --no-interaction diff --git a/api/docs/classes/App/Entity/Organization.md b/api/docs/classes/App/Entity/Organization.md index 266aa9004..d429e8c55 100644 --- a/api/docs/classes/App/Entity/Organization.md +++ b/api/docs/classes/App/Entity/Organization.md @@ -1,6 +1,6 @@ # App\Entity\Organization -This entity holds the information about an Organisation. +This entity holds the information about an Organization. diff --git a/api/docs/classes/App/Entity/User.md b/api/docs/classes/App/Entity/User.md index bc792a043..a51baa852 100644 --- a/api/docs/classes/App/Entity/User.md +++ b/api/docs/classes/App/Entity/User.md @@ -8,33 +8,33 @@ This entity holds the information about an User. ## Methods -| Name | Description | -|------|-------------| -|[__construct](#user__construct)|| -|[addApplication](#useraddapplication)|| -|[addUserGroup](#useraddusergroup)|| -|[getApplications](#usergetapplications)|| -|[getDateCreated](#usergetdatecreated)|| -|[getDateModified](#usergetdatemodified)|| -|[getEmail](#usergetemail)|| -|[getId](#usergetid)|| -|[getLocale](#usergetlocale)|| -|[getName](#usergetname)|| -|[getOrganisation](#usergetorganization)|| -|[getPassword](#usergetpassword)|| -|[getPerson](#usergetperson)|| -|[getRoles](#usergetroles)|| -|[getUserGroups](#usergetusergroups)|| -|[removeApplication](#userremoveapplication)|| -|[removeUserGroup](#userremoveusergroup)|| -|[setDateCreated](#usersetdatecreated)|| -|[setDateModified](#usersetdatemodified)|| -|[setEmail](#usersetemail)|| -|[setLocale](#usersetlocale)|| -|[setName](#usersetname)|| -|[setOrganisation](#usersetorganization)|| -|[setPassword](#usersetpassword)|| -|[setPerson](#usersetperson)|| +| Name | Description | +|---------------------------------------------|-------------| +| [__construct](#user__construct) || +| [addApplication](#useraddapplication) || +| [addUserGroup](#useraddusergroup) || +| [getApplications](#usergetapplications) || +| [getDateCreated](#usergetdatecreated) || +| [getDateModified](#usergetdatemodified) || +| [getEmail](#usergetemail) || +| [getId](#usergetid) || +| [getLocale](#usergetlocale) || +| [getName](#usergetname) || +| [getOrganization](#usergetorganization) || +| [getPassword](#usergetpassword) || +| [getPerson](#usergetperson) || +| [getRoles](#usergetroles) || +| [getUserGroups](#usergetusergroups) || +| [removeApplication](#userremoveapplication) || +| [removeUserGroup](#userremoveusergroup) || +| [setDateCreated](#usersetdatecreated) || +| [setDateModified](#usersetdatemodified) || +| [setEmail](#usersetemail) || +| [setLocale](#usersetlocale) || +| [setName](#usersetname) || +| [setOrganisation](#usersetorganization) || +| [setPassword](#usersetpassword) || +| [setPerson](#usersetperson) || @@ -279,12 +279,12 @@ This entity holds the information about an User.
-### User::getOrganisation +### User::getOrganization **Description** ```php - getOrganisation (void) + getOrganization (void) ``` @@ -567,12 +567,12 @@ This entity holds the information about an User.
-### User::setOrganisation +### User::setOrganization **Description** ```php - setOrganisation (void) + setOrganization (void) ``` diff --git a/api/helm/commonground-gateway-1.4.6.tgz b/api/helm/commonground-gateway-1.4.6.tgz index 0bd7f532a..958f75c56 100644 Binary files a/api/helm/commonground-gateway-1.4.6.tgz and b/api/helm/commonground-gateway-1.4.6.tgz differ diff --git a/api/helm/commonground-gateway-1.4.7.tgz b/api/helm/commonground-gateway-1.4.7.tgz new file mode 100644 index 000000000..6e8746754 Binary files /dev/null and b/api/helm/commonground-gateway-1.4.7.tgz differ diff --git a/api/helm/commonground-gateway-1.5.0.tgz b/api/helm/commonground-gateway-1.5.0.tgz new file mode 100644 index 000000000..32686f6b9 Binary files /dev/null and b/api/helm/commonground-gateway-1.5.0.tgz differ diff --git a/api/helm/commonground-gateway-1.5.1.tgz b/api/helm/commonground-gateway-1.5.1.tgz new file mode 100644 index 000000000..eb7eb829b Binary files /dev/null and b/api/helm/commonground-gateway-1.5.1.tgz differ diff --git a/api/helm/commonground-gateway-1.5.2.tgz b/api/helm/commonground-gateway-1.5.2.tgz new file mode 100644 index 000000000..283429908 Binary files /dev/null and b/api/helm/commonground-gateway-1.5.2.tgz differ diff --git a/api/helm/commonground-gateway-1.5.3.tgz b/api/helm/commonground-gateway-1.5.3.tgz new file mode 100644 index 000000000..ec6880888 Binary files /dev/null and b/api/helm/commonground-gateway-1.5.3.tgz differ diff --git a/api/helm/commonground-gateway-1.5.4.tgz b/api/helm/commonground-gateway-1.5.4.tgz new file mode 100644 index 000000000..3471833d6 Binary files /dev/null and b/api/helm/commonground-gateway-1.5.4.tgz differ diff --git a/api/helm/commonground-gateway/Chart.yaml b/api/helm/commonground-gateway/Chart.yaml index 8238347d3..df3821ec3 100644 --- a/api/helm/commonground-gateway/Chart.yaml +++ b/api/helm/commonground-gateway/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.4.6 +version: 1.5.4 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/api/helm/commonground-gateway/templates/deployment.yaml b/api/helm/commonground-gateway/templates/deployment.yaml index 14b03e981..40d516cdb 100644 --- a/api/helm/commonground-gateway/templates/deployment.yaml +++ b/api/helm/commonground-gateway/templates/deployment.yaml @@ -163,6 +163,8 @@ spec: env: - name: APP_ENV value: {{ .Values.config.env }} + - name: LOG_LEVEL + value: {{ .Values.config.logLevel }} - name: APP_AUTH valueFrom: configMapKeyRef: diff --git a/api/helm/commonground-gateway/templates/ingress.yaml b/api/helm/commonground-gateway/templates/ingress.yaml index d9810aa75..90d3503cf 100644 --- a/api/helm/commonground-gateway/templates/ingress.yaml +++ b/api/helm/commonground-gateway/templates/ingress.yaml @@ -29,8 +29,7 @@ metadata: {{/* nginx.ingress.kubernetes.io/auth-tls-verify: optional_no_ca*/}} {{/* nginx.ingress.kubernetes.io/auth-tls-verify-depth: '4'*/}} {{- if not .Values.ingress.annotations }} - nginx.ingress.kubernetes.io/cors-allow-origin: https://admin.{{ .Values.global.domain }} - nginx.ingress.kubernetes.io/enable-cors: "true" + nginx.ingress.kubernetes.io/proxy-body-size: {{ .Values.ingress.maxRequestSize }} {{- end }} {{ end }} {{ if eq .Values.ingress.className "traefik"}} diff --git a/api/helm/commonground-gateway/values.yaml b/api/helm/commonground-gateway/values.yaml index 8166c264f..baa9eed47 100644 --- a/api/helm/commonground-gateway/values.yaml +++ b/api/helm/commonground-gateway/values.yaml @@ -31,9 +31,11 @@ config: crontab: '*/5 * * * *' # https://crontab.guru/ appAuth: false # If we should check and care about authorization in the cronRunner pods. defaultRedirectUrl: useDomain - defaultBackUrl: useDomain + defaultBackUrl: useDomain + logLevel: warning documentation_description: "This documentation contains the endpoints on your commonground gateway. + # Authentication The gateway offers three forms of authentication: @@ -115,34 +117,30 @@ php: pullPolicy: Always # Overrides the image tag whose default is the chart appVersion. tag: "latest" - resources: {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. -# limits: -# cpu: 400m -# memory: 512Mi -# requests: -# cpu: 200m -# memory: 256Mi + resources: + limits: + cpu: 2 + memory: 2Gi + requests: + cpu: 1 + memory: 1Gi nginx: repository: ghcr.io/conductionnl/commonground-gateway-nginx pullPolicy: Always tag: "latest" - resources: {} + resources: # We usually recommend not to specify default resources and to leave this as a conscious # choice for the user. This also increases chances charts run on environments with little # resources, such as Minikube. If you do want to specify resources, uncomment the following # lines, adjust them as necessary, and remove the curly braces after 'resources:'. -# limits: -# cpu: 200m -# memory: 128Mi -# requests: -# cpu: 100m -# memory: 64Mi + limits: + cpu: 200m + memory: 128Mi + requests: + cpu: 100m + memory: 64Mi redis: enabled: true @@ -151,6 +149,11 @@ redis: auth: enabled: false pullPolicy: IfNotPresent + master: + resources: + requests: + cpu: 20m + memory: 128Mi rabbitmq: auth: @@ -158,6 +161,11 @@ rabbitmq: password: '!changeMe!' service: port: 5672 + ## Tweak these limits yourself, this is an indicative setting for larger message queues. + resources: + requests: + cpu: 200m + memory: 256Mi imagePullSecrets: [] nameOverride: "" @@ -187,6 +195,7 @@ postgresql: # ref: https://kubernetes.io/docs/user-guide/persistent-volumes persistence: enabled: true + ## Resource requests for postgresql are set by default by the bitnami chart, but can be overruled here. mongodb: enabled: true @@ -198,9 +207,18 @@ mongodb: enabled: true rootUser: api-platform rootPassword: "!ChangeMe!" + resources: + requests: + ## Tweak these limits yourself, this is an indicative setting for large databases. + cpu: 1 + memory: 6Gi gateway-ui: enabled: true + resources: + requests: + cpu: 10m + memory: 128Mi consumer: replicaCount: 5 @@ -230,12 +248,6 @@ securityContextNginx: capabilities: drop: - all - add: - - chown - - dac_override - - setgid - - setuid - - net_bind_service runAsUser: 101 # # Warning: the Nginx image is at this moment not able to run on a read-only filesystem. # readOnlyRootFilesystem: false @@ -248,6 +260,7 @@ service: ingress: enabled: true className: "nginx" + maxRequestSize: 50m path: / pathType: ImplementationSpecific annotations: {} diff --git a/api/helm/index.yaml b/api/helm/index.yaml index 8b1f6dfd1..d830bdf74 100644 --- a/api/helm/index.yaml +++ b/api/helm/index.yaml @@ -3,7 +3,7 @@ entries: commonground-gateway: - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:28.0059298+02:00" + created: "2023-10-18T16:45:04.057481884+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -25,7 +25,187 @@ entries: repository: https://raw.githubusercontent.com/ConductionNL/gateway-ui/development/helm/ version: 0.1.7 description: A Helm chart for Kubernetes - digest: 7342de0b4ff8c303d7fa5f7ec88fcfd143c5e2b7273b3c15873001b3cfc4d72a + digest: 0c49e55f583b242b21ce9540eb9b0a9c5bd04ce654ef89fe54742bb6b75be574 + name: commonground-gateway + type: application + urls: + - commonground-gateway-1.5.4.tgz + version: 1.5.4 + - apiVersion: v2 + appVersion: "2.2" + created: "2023-10-18T16:45:04.038953722+02:00" + dependencies: + - condition: postgresql.enabled + name: postgresql + repository: https://charts.bitnami.com/bitnami + version: 12.1.2 + - condition: redis.enabled + name: redis + repository: https://charts.bitnami.com/bitnami + version: 17.3.11 + - name: rabbitmq + repository: https://charts.bitnami.com/bitnami + version: 11.9.1 + - condition: mongodb.enabled + name: mongodb + repository: https://charts.bitnami.com/bitnami + version: 13.4.4 + - condition: gateway-ui.enabled + name: gateway-ui + repository: https://raw.githubusercontent.com/ConductionNL/gateway-ui/development/helm/ + version: 0.1.7 + description: A Helm chart for Kubernetes + digest: f3367f85691a72ee2022df449e7d817e965a9597180964df8e748f507d7c4e56 + name: commonground-gateway + type: application + urls: + - commonground-gateway-1.5.3.tgz + version: 1.5.3 + - apiVersion: v2 + appVersion: "2.2" + created: "2023-10-18T16:45:04.018785986+02:00" + dependencies: + - condition: postgresql.enabled + name: postgresql + repository: https://charts.bitnami.com/bitnami + version: 12.1.2 + - condition: redis.enabled + name: redis + repository: https://charts.bitnami.com/bitnami + version: 17.3.11 + - name: rabbitmq + repository: https://charts.bitnami.com/bitnami + version: 11.9.1 + - condition: mongodb.enabled + name: mongodb + repository: https://charts.bitnami.com/bitnami + version: 13.4.4 + - condition: gateway-ui.enabled + name: gateway-ui + repository: https://raw.githubusercontent.com/ConductionNL/gateway-ui/development/helm/ + version: 0.1.7 + description: A Helm chart for Kubernetes + digest: df9d7501a8b188f0b2b92d792363ca65f90da57cd14bb08e45aa5f8de2b38533 + name: commonground-gateway + type: application + urls: + - commonground-gateway-1.5.2.tgz + version: 1.5.2 + - apiVersion: v2 + appVersion: "2.2" + created: "2023-10-18T16:45:03.999122238+02:00" + dependencies: + - condition: postgresql.enabled + name: postgresql + repository: https://charts.bitnami.com/bitnami + version: 12.1.2 + - condition: redis.enabled + name: redis + repository: https://charts.bitnami.com/bitnami + version: 17.3.11 + - name: rabbitmq + repository: https://charts.bitnami.com/bitnami + version: 11.9.1 + - condition: mongodb.enabled + name: mongodb + repository: https://charts.bitnami.com/bitnami + version: 13.4.4 + - condition: gateway-ui.enabled + name: gateway-ui + repository: https://raw.githubusercontent.com/ConductionNL/gateway-ui/development/helm/ + version: 0.1.7 + description: A Helm chart for Kubernetes + digest: 365411075645a12918b22f2848e4cadac5ae395ecbcfef00531f32f0ca2f9946 + name: commonground-gateway + type: application + urls: + - commonground-gateway-1.5.1.tgz + version: 1.5.1 + - apiVersion: v2 + appVersion: "2.2" + created: "2023-10-18T16:45:03.980292881+02:00" + dependencies: + - condition: postgresql.enabled + name: postgresql + repository: https://charts.bitnami.com/bitnami + version: 12.1.2 + - condition: redis.enabled + name: redis + repository: https://charts.bitnami.com/bitnami + version: 17.3.11 + - name: rabbitmq + repository: https://charts.bitnami.com/bitnami + version: 11.9.1 + - condition: mongodb.enabled + name: mongodb + repository: https://charts.bitnami.com/bitnami + version: 13.4.4 + - condition: gateway-ui.enabled + name: gateway-ui + repository: https://raw.githubusercontent.com/ConductionNL/gateway-ui/development/helm/ + version: 0.1.7 + description: A Helm chart for Kubernetes + digest: 82edcc49002b7b34ba07db08699d1cf1e11b6b8530b0e6881c6e64a08ae7568b + name: commonground-gateway + type: application + urls: + - commonground-gateway-1.5.0.tgz + version: 1.5.0 + - apiVersion: v2 + appVersion: "2.2" + created: "2023-10-18T16:45:03.959404358+02:00" + dependencies: + - condition: postgresql.enabled + name: postgresql + repository: https://charts.bitnami.com/bitnami + version: 12.1.2 + - condition: redis.enabled + name: redis + repository: https://charts.bitnami.com/bitnami + version: 17.3.11 + - name: rabbitmq + repository: https://charts.bitnami.com/bitnami + version: 11.9.1 + - condition: mongodb.enabled + name: mongodb + repository: https://charts.bitnami.com/bitnami + version: 13.4.4 + - condition: gateway-ui.enabled + name: gateway-ui + repository: https://raw.githubusercontent.com/ConductionNL/gateway-ui/development/helm/ + version: 0.1.7 + description: A Helm chart for Kubernetes + digest: 1c37be973db456a71f375f4ee417f184d7e0485ce75bf3830cc9456c1a1e61ad + name: commonground-gateway + type: application + urls: + - commonground-gateway-1.4.7.tgz + version: 1.4.7 + - apiVersion: v2 + appVersion: "2.2" + created: "2023-10-18T16:45:03.940477939+02:00" + dependencies: + - condition: postgresql.enabled + name: postgresql + repository: https://charts.bitnami.com/bitnami + version: 12.1.2 + - condition: redis.enabled + name: redis + repository: https://charts.bitnami.com/bitnami + version: 17.3.11 + - name: rabbitmq + repository: https://charts.bitnami.com/bitnami + version: 11.9.1 + - condition: mongodb.enabled + name: mongodb + repository: https://charts.bitnami.com/bitnami + version: 13.4.4 + - condition: gateway-ui.enabled + name: gateway-ui + repository: https://raw.githubusercontent.com/ConductionNL/gateway-ui/development/helm/ + version: 0.1.7 + description: A Helm chart for Kubernetes + digest: aec1a5d09ff03b1fb2e865b6df284a2baacddf416cfc0107fcfbed4bc39ee99d name: commonground-gateway type: application urls: @@ -33,7 +213,7 @@ entries: version: 1.4.6 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.987511+02:00" + created: "2023-10-18T16:45:03.921531791+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -63,7 +243,7 @@ entries: version: 1.4.5 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.9685109+02:00" + created: "2023-10-18T16:45:03.901356898+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -93,7 +273,7 @@ entries: version: 1.4.4 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.9485112+02:00" + created: "2023-10-18T16:45:03.881647042+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -123,7 +303,7 @@ entries: version: 1.4.3 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.9289353+02:00" + created: "2023-10-18T16:45:03.86265352+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -153,7 +333,7 @@ entries: version: 1.4.2 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.9109356+02:00" + created: "2023-10-18T16:45:03.842650213+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -183,7 +363,7 @@ entries: version: 1.4.1 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.8906957+02:00" + created: "2023-10-18T16:45:03.822713024+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -213,7 +393,7 @@ entries: version: 1.4.0 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.8706958+02:00" + created: "2023-10-18T16:45:03.802685235+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -243,7 +423,7 @@ entries: version: 1.3.1 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.8526952+02:00" + created: "2023-10-18T16:45:03.782985437+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -273,7 +453,7 @@ entries: version: 1.3.0 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.8346368+02:00" + created: "2023-10-18T16:45:03.761441521+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -303,7 +483,7 @@ entries: version: 1.2.9 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.8156366+02:00" + created: "2023-10-18T16:45:03.742357684+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -329,7 +509,7 @@ entries: version: 1.2.8 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.7964976+02:00" + created: "2023-10-18T16:45:03.723257181+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -355,7 +535,7 @@ entries: version: 1.2.7 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.7774979+02:00" + created: "2023-10-18T16:45:03.701902186+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -381,7 +561,7 @@ entries: version: 1.2.6 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.758498+02:00" + created: "2023-10-18T16:45:03.683104297+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -407,7 +587,7 @@ entries: version: 1.2.5 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.741108+02:00" + created: "2023-10-18T16:45:03.663426416+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -433,7 +613,7 @@ entries: version: 1.2.4 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.7231079+02:00" + created: "2023-10-18T16:45:03.644969076+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -459,7 +639,7 @@ entries: version: 1.2.3 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.705097+02:00" + created: "2023-10-18T16:45:03.623540997+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -485,7 +665,7 @@ entries: version: 1.2.2 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.6871328+02:00" + created: "2023-10-18T16:45:03.604204858+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -511,7 +691,7 @@ entries: version: 1.2.1 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.668846+02:00" + created: "2023-10-18T16:45:03.585485+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -537,7 +717,7 @@ entries: version: 1.2.0 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.6507436+02:00" + created: "2023-10-18T16:45:03.566753846+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -563,7 +743,7 @@ entries: version: 1.1.4 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.6327433+02:00" + created: "2023-10-18T16:45:03.548107215+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -589,7 +769,7 @@ entries: version: 1.1.3 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.6137456+02:00" + created: "2023-10-18T16:45:03.529070757+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -615,7 +795,7 @@ entries: version: 1.1.2 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.5946248+02:00" + created: "2023-10-18T16:45:03.510619228+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -641,7 +821,7 @@ entries: version: 1.1.1 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.5776252+02:00" + created: "2023-10-18T16:45:03.490137481+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -667,7 +847,7 @@ entries: version: 1.1.0 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.5601079+02:00" + created: "2023-10-18T16:45:03.468728137+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -693,7 +873,7 @@ entries: version: 1.0.7 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.5415566+02:00" + created: "2023-10-18T16:45:03.450250564+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -719,7 +899,7 @@ entries: version: 1.0.6 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.5225561+02:00" + created: "2023-10-18T16:45:03.433485833+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -745,7 +925,7 @@ entries: version: 1.0.5 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.5051889+02:00" + created: "2023-10-18T16:45:03.413885966+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -771,7 +951,7 @@ entries: version: 1.0.4 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.4871892+02:00" + created: "2023-10-18T16:45:03.392084738+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -797,7 +977,7 @@ entries: version: 1.0.3 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.4691893+02:00" + created: "2023-10-18T16:45:03.372124651+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -823,7 +1003,7 @@ entries: version: 1.0.2 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.4492345+02:00" + created: "2023-10-18T16:45:03.353057129+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -849,7 +1029,7 @@ entries: version: 1.0.1 - apiVersion: v2 appVersion: "2.2" - created: "2023-06-30T11:39:27.4312343+02:00" + created: "2023-10-18T16:45:03.333514992+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -875,7 +1055,7 @@ entries: version: 1.0.0 - apiVersion: v2 appVersion: 1.16.0 - created: "2023-06-30T11:39:27.3303269+02:00" + created: "2023-10-18T16:45:03.212457732+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -897,7 +1077,7 @@ entries: version: 0.1.12 - apiVersion: v2 appVersion: 1.16.0 - created: "2023-06-30T11:39:27.3182536+02:00" + created: "2023-10-18T16:45:03.199088584+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -919,7 +1099,7 @@ entries: version: 0.1.11 - apiVersion: v2 appVersion: 1.16.0 - created: "2023-06-30T11:39:27.3062548+02:00" + created: "2023-10-18T16:45:03.184067849+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -941,7 +1121,7 @@ entries: version: 0.1.10 - apiVersion: v2 appVersion: 1.16.0 - created: "2023-06-30T11:39:27.412246+02:00" + created: "2023-10-18T16:45:03.314812842+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -963,7 +1143,7 @@ entries: version: 0.1.9 - apiVersion: v2 appVersion: 1.16.0 - created: "2023-06-30T11:39:27.4012463+02:00" + created: "2023-10-18T16:45:03.299412479+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -985,7 +1165,7 @@ entries: version: 0.1.8 - apiVersion: v2 appVersion: 1.16.0 - created: "2023-06-30T11:39:27.3902457+02:00" + created: "2023-10-18T16:45:03.287541405+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -1007,7 +1187,7 @@ entries: version: 0.1.7 - apiVersion: v2 appVersion: 1.16.0 - created: "2023-06-30T11:39:27.376246+02:00" + created: "2023-10-18T16:45:03.271231516+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -1026,7 +1206,7 @@ entries: version: 0.1.6 - apiVersion: v2 appVersion: 1.16.0 - created: "2023-06-30T11:39:27.3682364+02:00" + created: "2023-10-18T16:45:03.260098809+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -1045,7 +1225,7 @@ entries: version: 0.1.5 - apiVersion: v2 appVersion: 1.16.0 - created: "2023-06-30T11:39:27.3593269+02:00" + created: "2023-10-18T16:45:03.249620454+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -1064,7 +1244,7 @@ entries: version: 0.1.4 - apiVersion: v2 appVersion: 1.16.0 - created: "2023-06-30T11:39:27.3503269+02:00" + created: "2023-10-18T16:45:03.239896838+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -1083,7 +1263,7 @@ entries: version: 0.1.3 - apiVersion: v2 appVersion: 1.16.0 - created: "2023-06-30T11:39:27.3423268+02:00" + created: "2023-10-18T16:45:03.229631242+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -1102,7 +1282,7 @@ entries: version: 0.1.2 - apiVersion: v2 appVersion: 1.16.0 - created: "2023-06-30T11:39:27.2932536+02:00" + created: "2023-10-18T16:45:03.168916997+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -1121,7 +1301,7 @@ entries: version: 0.1.1 - apiVersion: v2 appVersion: 1.16.0 - created: "2023-06-30T11:39:27.283254+02:00" + created: "2023-10-18T16:45:03.154838459+02:00" dependencies: - condition: postgresql.enabled name: postgresql @@ -1138,4 +1318,4 @@ entries: urls: - commonground-gateway-0.1.0.tgz version: 0.1.0 -generated: "2023-06-30T11:39:27.272245+02:00" +generated: "2023-10-18T16:45:03.143413979+02:00" diff --git a/api/migrations/Version20230228084820.php b/api/migrations/Version20230228084820.php index 7255ac41a..51a9a8365 100644 --- a/api/migrations/Version20230228084820.php +++ b/api/migrations/Version20230228084820.php @@ -31,7 +31,6 @@ public function up(Schema $schema): void public function down(Schema $schema): void { // this down() migration is auto-generated, please modify it to your needs - $this->addSql('CREATE SCHEMA public'); $this->addSql('ALTER TABLE cronjob DROP reference'); $this->addSql('ALTER TABLE cronjob DROP version'); $this->addSql('ALTER TABLE collection_entity DROP reference'); diff --git a/api/migrations/Version20230228095524.php b/api/migrations/Version20230228095524.php index 31c68443d..b81793b5c 100644 --- a/api/migrations/Version20230228095524.php +++ b/api/migrations/Version20230228095524.php @@ -27,7 +27,6 @@ public function up(Schema $schema): void public function down(Schema $schema): void { // this down() migration is auto-generated, please modify it to your needs - $this->addSql('CREATE SCHEMA public'); $this->addSql('ALTER TABLE endpoint DROP reference'); $this->addSql('ALTER TABLE endpoint DROP version'); } diff --git a/api/migrations/Version20230303141510.php b/api/migrations/Version20230303141510.php index 822b540f6..2c2afd818 100644 --- a/api/migrations/Version20230303141510.php +++ b/api/migrations/Version20230303141510.php @@ -27,7 +27,6 @@ public function up(Schema $schema): void public function down(Schema $schema): void { // this down() migration is auto-generated, please modify it to your needs - $this->addSql('CREATE SCHEMA public'); $this->addSql('ALTER TABLE gateway DROP endpoints_config'); } } diff --git a/api/migrations/Version20230309160743.php b/api/migrations/Version20230309160743.php index d3ee7bebe..3da4c568c 100644 --- a/api/migrations/Version20230309160743.php +++ b/api/migrations/Version20230309160743.php @@ -27,7 +27,6 @@ public function up(Schema $schema): void public function down(Schema $schema): void { // this down() migration is auto-generated, please modify it to your needs - $this->addSql('CREATE SCHEMA public'); $this->addSql('ALTER TABLE application DROP configuration'); } } diff --git a/api/migrations/Version20230328151236.php b/api/migrations/Version20230328151236.php index 3595cc2e6..aecf9f48b 100644 --- a/api/migrations/Version20230328151236.php +++ b/api/migrations/Version20230328151236.php @@ -52,7 +52,6 @@ public function up(Schema $schema): void public function down(Schema $schema): void { // This down() migration is auto-generated, please modify it to your needs. - $this->addSql('CREATE SCHEMA public'); $this->addSql('ALTER TABLE security_group DROP reference'); $this->addSql('ALTER TABLE security_group DROP version'); $this->addSql('ALTER TABLE "user" DROP reference'); diff --git a/api/migrations/Version20230504111926.php b/api/migrations/Version20230504111926.php index 45bcf9466..9cb7cad35 100644 --- a/api/migrations/Version20230504111926.php +++ b/api/migrations/Version20230504111926.php @@ -29,7 +29,6 @@ public function up(Schema $schema): void public function down(Schema $schema): void { // this down() migration is auto-generated, please modify it to your needs - $this->addSql('CREATE SCHEMA public'); $this->addSql('DROP TABLE "gateway_audit_trail"'); $this->addSql('ALTER TABLE audit_trail ALTER id TYPE UUID'); $this->addSql('ALTER TABLE audit_trail ALTER id DROP DEFAULT'); diff --git a/api/migrations/Version20230602151620.php b/api/migrations/Version20230602151620.php index e8b03f5d0..a6a6a49cd 100644 --- a/api/migrations/Version20230602151620.php +++ b/api/migrations/Version20230602151620.php @@ -26,7 +26,6 @@ public function up(Schema $schema): void public function down(Schema $schema): void { // this down() migration is auto-generated, please modify it to your needs - $this->addSql('CREATE SCHEMA public'); $this->addSql('DROP INDEX entity_attribute_unique'); } } diff --git a/api/migrations/Version20230626125323.php b/api/migrations/Version20230626125323.php index 2f4f71ec3..dfb936b4b 100644 --- a/api/migrations/Version20230626125323.php +++ b/api/migrations/Version20230626125323.php @@ -26,7 +26,6 @@ public function up(Schema $schema): void public function down(Schema $schema): void { // this down() migration is auto-generated, please modify it to your needs - $this->addSql('CREATE SCHEMA public'); $this->addSql('ALTER TABLE attribute ALTER allow_cascade DROP DEFAULT'); } } diff --git a/api/migrations/Version20230714084101.php b/api/migrations/Version20230714084101.php new file mode 100644 index 000000000..f7e73894f --- /dev/null +++ b/api/migrations/Version20230714084101.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE attribute ADD cache_sub_objects BOOLEAN DEFAULT \'false\' NOT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE attribute DROP cache_sub_objects'); + } +} diff --git a/api/migrations/Version20230907115512.php b/api/migrations/Version20230907115512.php new file mode 100644 index 000000000..5735ba4dd --- /dev/null +++ b/api/migrations/Version20230907115512.php @@ -0,0 +1,33 @@ +addSql('ALTER TABLE template ADD version VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE template ALTER organization_id DROP NOT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE template DROP version'); + $this->addSql('ALTER TABLE template ALTER organization_id SET NOT NULL'); + } +} diff --git a/api/migrations/Version20230908113705.php b/api/migrations/Version20230908113705.php new file mode 100644 index 000000000..683e56721 --- /dev/null +++ b/api/migrations/Version20230908113705.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE template ADD reference VARCHAR(255) DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE template DROP reference'); + } +} diff --git a/api/migrations/Version20230922133622.php b/api/migrations/Version20230922133622.php new file mode 100644 index 000000000..12ef7ca0e --- /dev/null +++ b/api/migrations/Version20230922133622.php @@ -0,0 +1,33 @@ +addSql('ALTER TABLE action ADD user_id VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE cronjob ADD user_id VARCHAR(255) DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE action DROP user_id'); + $this->addSql('ALTER TABLE cronjob DROP user_id'); + } +} diff --git a/api/migrations/Version20230926112500.php b/api/migrations/Version20230926112500.php new file mode 100644 index 000000000..8456f62ce --- /dev/null +++ b/api/migrations/Version20230926112500.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE "user" RENAME COLUMN organisation_id TO organization_id'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE "user" RENAME COLUMN organization_id TO organisation_id'); + } +} diff --git a/api/migrations/Version20231110140316.php b/api/migrations/Version20231110140316.php new file mode 100644 index 000000000..72fb31735 --- /dev/null +++ b/api/migrations/Version20231110140316.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE synchronization ADD sha VARCHAR(255) DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE synchronization DROP sha'); + } +} diff --git a/api/migrations/Version20231123112218.php b/api/migrations/Version20231123112218.php new file mode 100644 index 000000000..d9f5e1d82 --- /dev/null +++ b/api/migrations/Version20231123112218.php @@ -0,0 +1,60 @@ +logging boolean to Gateway->configLogging array'; + + }//end getDescription() + + + /** + * Migrate up. + * + * @param Schema $schema Schema. + * @return void + */ + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE gateway ALTER logging TYPE TEXT'); + $this->addSql('ALTER TABLE gateway ALTER logging SET DEFAULT \'a:10:{s:10:"callMethod";b:1;s:7:"callUrl";b:1;s:9:"callQuery";b:1;s:15:"callContentType";b:1;s:8:"callBody";b:1;s:18:"responseStatusCode";b:1;s:19:"responseContentType";b:1;s:12:"responseBody";b:1;s:16:"maxCharCountBody";i:500;s:21:"maxCharCountErrorBody";i:2000;}\''); + $this->addSql('UPDATE gateway SET logging = \'a:10:{s:10:"callMethod";b:1;s:7:"callUrl";b:1;s:9:"callQuery";b:1;s:15:"callContentType";b:1;s:8:"callBody";b:1;s:18:"responseStatusCode";b:1;s:19:"responseContentType";b:1;s:12:"responseBody";b:1;s:16:"maxCharCountBody";i:500;s:21:"maxCharCountErrorBody";i:2000;}\''); + $this->addSql('ALTER TABLE gateway ALTER logging SET NOT NULL'); + $this->addSql('COMMENT ON COLUMN gateway.logging IS \'(DC2Type:array)\''); + $this->addSql('ALTER TABLE gateway RENAME COLUMN logging TO logging_config'); + }//end up() + + /** + * Migrate down. + * + * @param Schema $schema Schema. + * @return void + */ + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE gateway RENAME COLUMN logging_config TO logging'); + $this->addSql('ALTER TABLE gateway ALTER logging DROP NOT NULL'); + $this->addSql('ALTER TABLE gateway ALTER logging DROP DEFAULT'); + $this->addSql('UPDATE gateway SET logging = NULL'); + $this->addSql('ALTER TABLE gateway ALTER logging TYPE BOOLEAN USING logging::boolean'); + $this->addSql('COMMENT ON COLUMN gateway.logging IS NULL'); + }//end down() +}//end class diff --git a/api/src/Command/ClearAnonymousCacheCommand.php b/api/src/Command/ClearAnonymousCacheCommand.php index 7e1ccd30f..1413a2266 100644 --- a/api/src/Command/ClearAnonymousCacheCommand.php +++ b/api/src/Command/ClearAnonymousCacheCommand.php @@ -16,6 +16,7 @@ * @license EUPL * * @category Command + * @deprecated */ class ClearAnonymousCacheCommand extends Command { diff --git a/api/src/Command/ClearObjectsFromCacheCommand.php b/api/src/Command/ClearObjectsFromCacheCommand.php index 7b20bfdad..28e901de0 100644 --- a/api/src/Command/ClearObjectsFromCacheCommand.php +++ b/api/src/Command/ClearObjectsFromCacheCommand.php @@ -26,6 +26,7 @@ * @license EUPL * * @category Command + * @deprecated */ class ClearObjectsFromCacheCommand extends Command { diff --git a/api/src/Command/CronjobCommand.php b/api/src/Command/CronjobCommand.php index 99e914b57..fdefafa5b 100644 --- a/api/src/Command/CronjobCommand.php +++ b/api/src/Command/CronjobCommand.php @@ -5,6 +5,7 @@ namespace App\Command; use App\Entity\Cronjob; +use App\Entity\User; use App\Event\ActionEvent; use Cron\CronExpression; use Doctrine\ORM\EntityManagerInterface; @@ -66,6 +67,8 @@ protected function configure(): void /** * This function makes action events. * + * After running this function, even if it returns an exception, currentCronJobUserId should always be removed from cache. + * * @param Cronjob $cronjob * @param SymfonyStyle $io * @@ -85,6 +88,16 @@ public function makeActionEvent(Cronjob $cronjob, SymfonyStyle $io): void $io->newLine(); $io->newLine(); + // Keep track of the user used for running this CronJob. + // After makeActionEvent() is done, even if it returns an exception, currentCronJobUserId should be removed from cache (outside this function) + $this->session->remove('currentCronjobUserId'); + if ($cronjob->getUserId() !== null && Uuid::isValid($cronjob->getUserId()) === true) { + $user = $this->entityManager->getRepository('App:User')->find($cronjob->getUserId()); + if ($user instanceof User === true) { + $this->session->set('currentCronjobUserId', $cronjob->getUserId()); + } + } + $throws = $cronjob->getThrows(); foreach ($throws as $key => $throw) { $io->block("Dispatch ActionEvent for Throw: \"$throw\""); @@ -158,6 +171,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io->block("Trace: {$exception->getTraceAsString()}"); $errorCount++; } + // Make sure we remove currentCronJobUserid from cache. + $this->session->remove('currentCronJobUserId'); $io->progressAdvance(); } @@ -186,6 +201,7 @@ private function handleCronjobIoStart(SymfonyStyle $io, Cronjob $cronjob) ['Id' => $cronjob->getId()->toString()], ['Name' => $cronjob->getName()], ['Description' => $cronjob->getDescription()], + ['UserId' => $cronjob->getUserId()], ['Crontab' => $cronjob->getCrontab()], ['Throws' => implode(', ', $cronjob->getThrows())], // ['Data' => "[{$this->objectEntityService->implodeMultiArray($cronjob->getData())}]"], diff --git a/api/src/Command/InitializationCommand.php b/api/src/Command/InitializationCommand.php index 2fb8fcc22..6d4ac71ba 100644 --- a/api/src/Command/InitializationCommand.php +++ b/api/src/Command/InitializationCommand.php @@ -235,6 +235,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int 'group.admin', 'admin.GET', 'admin.POST', + 'admin.PATCH', 'admin.PUT', 'admin.DELETE', ] @@ -257,7 +258,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $user->setPassword($this->hasher->hashPassword($user, '!ChangeMe!')); $user->addSecurityGroup($securityGroupAdmin); $user->addApplication($application); - $user->setOrganisation($organization); + $user->setOrganization($organization); $this->entityManager->persist($user); } else { diff --git a/api/src/Controller/ConvenienceController.php b/api/src/Controller/ConvenienceController.php index 0ec49c583..32f7f46c6 100755 --- a/api/src/Controller/ConvenienceController.php +++ b/api/src/Controller/ConvenienceController.php @@ -9,7 +9,7 @@ use App\Service\ObjectEntityService; use App\Service\PackagesService; use App\Service\ParseDataService; -use App\Subscriber\ActionSubscriber; +use CommonGateway\CoreBundle\Subscriber\ActionSubscriber; use CommonGateway\CoreBundle\Service\MappingService; use Doctrine\ORM\EntityManagerInterface; use Exception; diff --git a/api/src/Controller/LoginController.php b/api/src/Controller/LoginController.php index bbde1d553..0ca3a1caf 100644 --- a/api/src/Controller/LoginController.php +++ b/api/src/Controller/LoginController.php @@ -42,8 +42,6 @@ public function __construct(CacheInterface $cache, EntityManagerInterface $entit */ public function MeAction(Request $request, CommonGroundService $commonGroundService, ObjectEntityService $objectEntityService) { - $this->cache->invalidateTags(['grantedScopes']); - $userService = new UserService($commonGroundService, $objectEntityService); if ($this->getUser()) { $result = [ @@ -54,9 +52,8 @@ public function MeAction(Request $request, CommonGroundService $commonGroundServ 'last_name' => $this->getUser()->getLastName(), 'name' => $this->getUser()->getName(), 'email' => $this->getUser()->getEmail(), - // TODO: if we have no person connected to this user create one? with $this->createPersonForUser() - 'person' => $userService->getPersonForUser($this->getUser()), - 'organization' => $userService->getOrganizationForUser($this->getUser()), + 'person' => $userService->getPersonForUser($this->getUser()), // Get person ObjectEntity (->Entity with function = person) by id + 'organization' => $userService->getOrganizationForUser($this->getUser()), // Get organization ObjectEntity (->Entity with function = organization) by id ]; $result = json_encode($result); } else { diff --git a/api/src/Controller/UserController.php b/api/src/Controller/UserController.php index d9fb2c201..4cc28408a 100644 --- a/api/src/Controller/UserController.php +++ b/api/src/Controller/UserController.php @@ -64,7 +64,7 @@ public function resetTokenAction(SerializerInterface $serializer, \CommonGateway $accessToken = $this->authenticationService->refreshAccessToken($session->get('refresh_token'), $session->get('authenticator')); $user = $this->getUser(); if ($user instanceof AuthenticationUser === false) { - return new Response('User not found', 401); + return new Response(json_encode(["Message" => 'User not found.']), 401, ['Content-type' => 'application/json']); } $serializeUser = new User(); @@ -95,30 +95,28 @@ public function resetTokenAction(SerializerInterface $serializer, \CommonGateway $status = 200; $user = $this->getUser(); if ($user instanceof AuthenticationUser === false) { - return new Response('User not found', 401); + return new Response(json_encode(["Message" => 'User not found.']), 401, ['Content-type' => 'application/json']); } $user = $this->entityManager->getRepository('App:User')->find($user->getUserIdentifier()); - if ($user->getOrganisation() !== null) { - $organizations[] = $user->getOrganisation(); - } - foreach ($user->getApplications() as $application) { - if ($application->getOrganization() !== null) { - $organizations[] = $application->getOrganization(); - } - } + // Set organization id and user id in session + $this->session->set('user', $user->getId()->toString()); + $this->session->set('organization', $user->getOrganization() !== null ? $user->getOrganization()->getId()->toString() : null); - // If user has no organization, we default activeOrganization to an organization of a userGroup this user has and else the application organization; - $this->session->set('activeOrganization', $user->getOrganisation()->getId()->toString()); + $response = $this->validateUserApp($user); + if ($response !== null) + return $response; + // TODO: maybe do not just get the first Application here, but get application using ApplicationService->getApplication() and ... + // todo... if this returns an application check if the user is part of this application or one of the organizations of this application? $user->setJwtToken($authenticationService->createJwtToken($user->getApplications()[0]->getPrivateKey(), $authenticationService->serializeUser($user, $this->session))); return new Response($serializer->serialize($user, 'json'), $status, ['Content-type' => 'application/json']); } /** - * Create an authentication user from a entity user. + * Create an authentication user from an entity user. * * @param User $user The user to log in. * @@ -145,7 +143,7 @@ public function createAuthenticationUser(User $user): AuthenticationUser 'id' => $user->getId()->toString(), 'email' => $user->getEmail(), 'locale' => $user->getLocale(), - 'organization' => $user->getOrganisation()->getId()->toString(), + 'organization' => $user->getOrganization()->getId()->toString(), 'roles' => $roleArray['roles'], ]; @@ -166,10 +164,11 @@ public function createAuthenticationUser(User $user): AuthenticationUser } /** - * Add the logged in user to session. + * Add the logged-in user to session. * - * @param User $user The user to log in. + * @param User $user The user to log in. * @param EventDispatcherInterface $eventDispatcher The event dispatcher. + * @param Request $request * * @return void */ @@ -203,18 +202,16 @@ public function apiLoginAction(Request $request, UserPasswordHasherInterface $ha return new Response(json_encode($response), 401, ['Content-type' => 'application/json']); } - if ($user->getOrganisation() !== null) { - $organizations[] = $user->getOrganisation(); - } - foreach ($user->getApplications() as $application) { - if ($application->getOrganization() !== null) { - $organizations[] = $application->getOrganization(); - } - } + // Set organization id and user id in session + $this->session->set('user', $user->getId()->toString()); + $this->session->set('organization', $user->getOrganization() !== null ? $user->getOrganization()->getId()->toString() : null); - // If user has no organization, we default activeOrganization to an organization of a userGroup this user has and else the application organization; - $this->session->set('activeOrganization', $user->getOrganisation()->getId()->toString()); + $response = $this->validateUserApp($user); + if ($response !== null) + return $response; + // TODO: maybe do not just get the first Application here, but get application using ApplicationService->getApplication() and ... + // todo... if this returns an application check if the user is part of this application or one of the organizations of this application? $token = $authenticationService->createJwtToken($user->getApplications()[0]->getPrivateKey(), $authenticationService->serializeUser($user, $this->session)); $user->setJwtToken($token); @@ -231,7 +228,68 @@ public function apiLoginAction(Request $request, UserPasswordHasherInterface $ha return $this->redirect($request->query->get('redirectUrl')); } - return new Response($serializer->serialize($user, 'json'), $status, ['Content-type' => 'application/json']); + $serializedUser = $serializer->serialize($user, 'json'); + $userArray = json_decode($serializedUser, true); + $userArray = $this->cleanupLoginResponse($userArray); + + return new Response(json_encode($userArray), $status, ['Content-type' => 'application/json']); + } + + /** + * Checks if $user has an application and if that application has a PrivateKey set. If not return error Response. + * + * @param User $user A user to check. + * + * @return Response|null Error Response or null. + */ + private function validateUserApp(User $user): ?Response + { + if (empty($user->getApplications()) === true) { + return new Response( + json_encode(["Message" => 'This user is not yet connected to any application.']), + 409, + ['Content-type' => 'application/json'] + ); + } + + if (empty($user->getApplications()[0]->getPrivateKey()) === true) { + return new Response( + json_encode(["Message" => "Can't create a token because application ({$user->getApplications()[0]->getId()->toString()}) doesn't have a PrivateKey."]), + 409, + ['Content-type' => 'application/json'] + ); + } + + return null; + } + + /** + * Removes some sensitive data from the login response. + * + * @param array $userArray The logged in User Object as array. + * + * @return array The updated user array. + */ + private function cleanupLoginResponse(array $userArray): array + { + if (isset($userArray['organization']['users']) === true) { + unset($userArray['organization']['users']); + } + if (isset($userArray['organization']['applications']) === true) { + foreach ($userArray['organization']['applications'] as &$application) { + unset($application['organization']); + } + } + foreach ($userArray['applications'] as &$application) { + unset($application['organization']); + } + foreach ($userArray['securityGroups'] as &$securityGroup) { + unset($securityGroup['users']); + unset($securityGroup['parent']); + unset($securityGroup['children']); + } + + return $userArray; } private function getActiveOrganization(array $user, array $organizations): ?string @@ -255,7 +313,7 @@ private function getActiveOrganization(array $user, array $organizations): ?stri } /** - * Get all the child organisations for an organisation. + * Get all the child organizations for an organization. * * @param array $organizations * @param string $organization @@ -284,7 +342,7 @@ private function getSubOrganizations(array $organizations, string $organization, } /** - * Get al the parent organizations for an organisation. + * Get al the parent organizations for an organization. * * @param array $organizations * @param string $organization diff --git a/api/src/Controller/ZZController.php b/api/src/Controller/ZZController.php index b62f97b23..8806b7fed 100644 --- a/api/src/Controller/ZZController.php +++ b/api/src/Controller/ZZController.php @@ -9,6 +9,7 @@ use App\Service\ProcessingLogService; use CommonGateway\CoreBundle\Service\EndpointService; use CommonGateway\CoreBundle\Service\RequestService; +use Exception; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -31,13 +32,13 @@ class ZZController extends AbstractController * @Route("/admin/objects") * @Route("/admin/objects/{id}", requirements={"path" = ".+"}) * - * @param string|null $path - * @param Request $request + * @param string|null $id + * @param Request $request * @param SerializerInterface $serializer - * @param HandlerService $handlerService - * @param RequestService $requestService + * @param RequestService $requestService * * @return Response + * @throws Exception */ public function objectAction( ?string $id, @@ -57,22 +58,36 @@ public function objectAction( } /** - * This function dynamicly handles the api endpoints. + * This function dynamically handles the custom endpoints. * - * @Route("/api/{path}", name="dynamic_route", requirements={"path" = ".+"}) + * @Route("/{prefix}/api/{path}", name="dynamic_route_second", requirements={"path" = ".+"}) * - * @param string|null $path - * @param Request $request - * @param DocumentService $documentService - * @param HandlerService $handlerService - * @param SerializerInterface $serializer - * @param LogService $logService - * @param ProcessingLogService $processingLogService - * @param RequestService $requestService + * @param string|null $path + * @param string|null $bundle + * @param Request $request + * @param EndpointService $endpointService + * @return Response + * @throws Exception + */ + public function dynamicCustomAction( + ?string $path, + ?string $bundle, + Request $request, + EndpointService $endpointService + ): Response { + return $endpointService->handleRequest($request); + } + + /** + * This function dynamically handles the api endpoints. * - * @throws GatewayException + * @Route("/api/{path}", name="dynamic_route", requirements={"path" = ".+"}) * + * @param string|null $path + * @param Request $request + * @param EndpointService $endpointService * @return Response + * @throws Exception */ public function dynamicAction( ?string $path, @@ -101,7 +116,7 @@ private function getParametersFromRequest(?array $parameters = [], ?Request $req try { $parameters['body'] = $request->toArray(); - } catch (\Exception $exception) { + } catch (Exception $exception) { } $parameters['crude_body'] = $request->getContent(); @@ -139,6 +154,7 @@ private function getAcceptType(Request $request): string case 'application/pdf': return 'pdf'; case 'application/json': + case '*/*': return 'json'; case 'application/json+hal': case 'application/hal+json': @@ -158,6 +174,16 @@ private function getAcceptType(Request $request): string case 'text/xml': case 'application/xml': return 'xml'; + case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': + return 'xlsx'; + case 'text/csv': + return 'csv'; + case 'text/html': + return 'html'; + case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': + return 'docx'; + case 'application/json+aggregations': + return 'aggregations'; }//end switch throw new BadRequestHttpException('No proper accept could be determined'); diff --git a/api/src/Entity/Action.php b/api/src/Entity/Action.php index 093633d79..17c9e6cb4 100644 --- a/api/src/Entity/Action.php +++ b/api/src/Entity/Action.php @@ -171,6 +171,16 @@ class Action */ private ?array $configuration = []; + /** + * @var string|null The userId of a user. This user will be used to run this Action for, if there is no logged-in user. + * This helps when, for example: setting the organization of newly created ObjectEntities while running this Action. + * + * @Groups({"read","write"}) + * + * @ORM\Column(type="string", length=255, nullable=true) + */ + private ?string $userId = null; + /** * @Groups({"read", "write"}) * @@ -481,6 +491,18 @@ public function setConfiguration(?array $configuration): self return $this; } + public function getUserId(): ?string + { + return $this->userId; + } + + public function setUserId(?string $userId): self + { + $this->userId = $userId; + + return $this; + } + public function getIsLockable(): ?bool { return $this->isLockable; diff --git a/api/src/Entity/Attribute.php b/api/src/Entity/Attribute.php index 97b20800c..1bcd2d559 100644 --- a/api/src/Entity/Attribute.php +++ b/api/src/Entity/Attribute.php @@ -218,6 +218,7 @@ class Attribute /** * @var bool whether the properties of the original object are automatically include. + * @deprecated * * @Groups({"read","write"}) * @@ -846,6 +847,15 @@ class Attribute */ private $reference = null; + /** + * @var Boolean Whether sub-objects in this value should be re-cached. + * + * @Groups({"read", "write"}) + * + * @ORM\Column(type="boolean", options={"default": false}) + */ + private bool $cacheSubObjects = false; + public function __construct() { $this->attributeValues = new ArrayCollection(); @@ -2040,4 +2050,16 @@ public function setInversedByPropertyName(?string $inversedByPropertyName): self return $this; } + + public function getCacheSubObjects(): ?bool + { + return $this->cacheSubObjects; + } + + public function setCacheSubObjects(bool $cacheSubObjects): self + { + $this->cacheSubObjects = $cacheSubObjects; + + return $this; + } } diff --git a/api/src/Entity/AuditTrail.php b/api/src/Entity/AuditTrail.php index f761f8248..17e0f1517 100644 --- a/api/src/Entity/AuditTrail.php +++ b/api/src/Entity/AuditTrail.php @@ -187,7 +187,9 @@ class AuditTrail /** * @var ?DateTime The creation date of the audit trail * - * @Groups({"read", "write"}) + * @Groups({"read"}) + * + * @Gedmo\Timestampable(on="create") * * @ORM\Column(type="datetime", nullable=true) */ @@ -370,12 +372,19 @@ public function setResourceView(?string $resourceView): self return $this; } - public function getCreationDate(): ?\DateTimeInterface + /** + * Getter for creationDate. + * CoreBundle->ReadUnreadService->getDateRead() return type requires this to have return type DateTime. Not DateTimeInterface! + * Please test it if you want to change this. + * + * @return DateTime|null + */ + public function getCreationDate(): ?DateTime { return $this->creationDate; } - public function setCreationDate(?\DateTimeInterface $creationDate): self + public function setCreationDate(?DateTime $creationDate): self { $this->creationDate = $creationDate; diff --git a/api/src/Entity/Cronjob.php b/api/src/Entity/Cronjob.php index 5d279456f..862ba6b03 100644 --- a/api/src/Entity/Cronjob.php +++ b/api/src/Entity/Cronjob.php @@ -123,6 +123,16 @@ class Cronjob */ private string $crontab = '*/5 * * * *'; + /** + * @var string|null The userId of a user. This user will be used to run this Cronjob for, if there is no logged-in user. + * This helps when, for example: setting the organization of newly created ObjectEntities while running this Cronjob. + * + * @Groups({"read","write"}) + * + * @ORM\Column(type="string", length=255, nullable=true) + */ + private ?string $userId = null; + /** * @var array The actions that put on the stack by the crontab. * @@ -320,6 +330,18 @@ public function setCrontab(string $crontab): self return $this; } + public function getUserId(): ?string + { + return $this->userId; + } + + public function setUserId(?string $userId): self + { + $this->userId = $userId; + + return $this; + } + public function getThrows(): ?array { return $this->throws; diff --git a/api/src/Entity/Endpoint.php b/api/src/Entity/Endpoint.php index e4eff4faa..567c865c4 100644 --- a/api/src/Entity/Endpoint.php +++ b/api/src/Entity/Endpoint.php @@ -430,6 +430,10 @@ public function fromSchema(array $schema, array $default = []) array_key_exists('tags', $schema) ? $this->setTags($schema['tags']) : ''; array_key_exists('entities', $schema) ? $this->setEntities($schema['entities']) : ''; + if (array_key_exists('loggingConfig', $schema) === true) { + $this->setLoggingConfig($schema['loggingConfig']); + } + /*@depricated kept here for lagacy */ $this->setMethod(array_key_exists('method', $schema) ? $schema['method'] : 'GET'); $this->setOperationType(array_key_exists('operationType', $schema) ? $schema['operationType'] : 'GET'); diff --git a/api/src/Entity/Entity.php b/api/src/Entity/Entity.php index ba760fdf4..a3091daed 100644 --- a/api/src/Entity/Entity.php +++ b/api/src/Entity/Entity.php @@ -182,7 +182,7 @@ class Entity private $extend = false; /** - * Whether objects created from this entity should be available to child organisations. + * Whether objects created from this entity should be available to child organizations. * * @Groups({"read","write"}) * @@ -1067,6 +1067,9 @@ public function fromSchema(array $schema): self if (array_key_exists('nameProperties', $schema)) { $this->setNameProperties($schema['nameProperties']); } + if (array_key_exists('createAuditTrails', $schema)) { + $this->setCreateAuditTrails($schema['createAuditTrails']); + } // Properties. if (array_key_exists('properties', $schema)) { @@ -1129,6 +1132,7 @@ public function toSchema(?ObjectEntity $objectEntity = null): array 'exclude' => $this->isExcluded(), 'maxDepth' => $this->getMaxDepth(), 'nameProperties' => $this->getNameProperties(), + 'createAuditTrails' => $this->getCreateAuditTrails(), 'required' => [], 'properties' => [], ]; diff --git a/api/src/Entity/Gateway.php b/api/src/Entity/Gateway.php index fc0e8c491..9ff175dff 100644 --- a/api/src/Entity/Gateway.php +++ b/api/src/Entity/Gateway.php @@ -559,11 +559,27 @@ class Gateway private ?string $documentation = null; /** - * Setting logging to true will couse ALL responses to be logged (normaly we only log errors). Doing so wil dramaticly slow down the gateway and couse an increase in database size. This is not recomended outside of development purposes. + * Configuration for logging, when an api call is made on the source we can log some information for this call. + * With this array you can enable/disable what will be logged. * - * @ORM\Column(type="boolean", nullable=true) + * @Assert\NotNull + * + * @Groups({"read","write"}) + * + * @ORM\Column(type="array") */ - private $logging; + private array $loggingConfig = [ + 'callMethod' => true, + 'callUrl' => true, + 'callQuery' => true, + 'callContentType' => true, + 'callBody' => true, + 'responseStatusCode' => true, + 'responseContentType' => true, + 'responseBody' => true, + 'maxCharCountBody' => 500, + 'maxCharCountErrorBody' => 2000, + ]; /** * @var array ... @@ -792,6 +808,7 @@ public function fromSchema(array $schema): self array_key_exists('jwtId', $schema) ? $this->setJwtId($schema['jwtId']) : ''; array_key_exists('username', $schema) ? $this->setUsername($schema['username']) : ''; array_key_exists('documentation', $schema) ? $this->setDocumentation($schema['documentation']) : ''; + array_key_exists('loggingConfig', $schema) ? $this->setLoggingConfig($schema['loggingConfig']) : ''; array_key_exists('headers', $schema) ? $this->setHeaders($schema['headers']) : ''; array_key_exists('translationConfig', $schema) ? $this->setTranslationConfig($schema['translationConfig']) : ''; array_key_exists('type', $schema) ? $this->setType($schema['type']) : ''; @@ -826,6 +843,7 @@ public function toSchema(): array 'jwtId' => $this->getJwtId(), 'username' => $this->getUsername(), 'documentation' => $this->getDocumentation(), + 'loggingConfig' => $this->getLoggingConfig(), 'headers' => $this->getHeaders(), 'translationConfig' => $this->getTranslationConfig(), 'type' => $this->getType(), @@ -857,6 +875,7 @@ public function export(): ?array 'password' => $this->getPassword(), 'apikey' => $this->getApikey(), 'documentation' => $this->getDocumentation(), + 'loggingConfig' => $this->getLoggingConfig(), 'headers' => $this->getHeaders(), 'translationConfig' => $this->getTranslationConfig(), 'type' => $this->getType(), @@ -1129,14 +1148,14 @@ public function setDocumentation(?string $documentation): self return $this; } - public function getLogging(): ?bool + public function getLoggingConfig(): ?array { - return $this->logging; + return $this->loggingConfig; } - public function setLogging(?bool $logging): self + public function setLoggingConfig(array $loggingConfig): self { - $this->logging = $logging; + $this->loggingConfig = array_merge($loggingConfig); return $this; } diff --git a/api/src/Entity/Log.php b/api/src/Entity/Log.php index ad02842cc..261d9ab7d 100644 --- a/api/src/Entity/Log.php +++ b/api/src/Entity/Log.php @@ -52,6 +52,7 @@ * "type": "exact", * "objectId": "exact", * }) + * @deprecated */ class Log { diff --git a/api/src/Entity/ObjectEntity.php b/api/src/Entity/ObjectEntity.php index c63106b30..fb6eb4e43 100644 --- a/api/src/Entity/ObjectEntity.php +++ b/api/src/Entity/ObjectEntity.php @@ -518,9 +518,11 @@ public function setHasErrors(bool $hasErrors, int $level = 1): self $this->hasErrors = $hasErrors; // Do the same for resources above this one if set to true - if ($hasErrors == true && !$this->getSubresourceOf()->isEmpty() && $level < 5) { + if ($hasErrors === true && !$this->getSubresourceOf()->isEmpty() && $level < 5) { foreach ($this->getSubresourceOf() as $resource) { - $resource->getObjectEntity()->setHasErrors($hasErrors, $level + 1); + if ($resource->getObjectEntity() instanceof ObjectEntity) { + $resource->getObjectEntity()->setHasErrors(true, $level + 1); + } } } @@ -1100,7 +1102,7 @@ public function toArray(array $configuration = []): array { // Let's default the config array !isset($configuration['level']) ? $configuration['level'] = 1 : ''; - !isset($configuration['maxdepth']) ? $configuration['maxdepth'] = $this->getEntity()->getMaxDepth() : ''; + !isset($configuration['maxDepth']) ? $configuration['maxDepth'] = $this->getEntity()->getMaxDepth() : ''; !isset($configuration['renderedObjects']) ? $configuration['renderedObjects'] = [] : ''; !isset($configuration['embedded']) ? $configuration['embedded'] = false : ''; !isset($configuration['onlyMetadata']) ? $configuration['onlyMetadata'] = false : ''; @@ -1160,16 +1162,27 @@ public function toArray(array $configuration = []): array $array[$attribute->getName()] = "Attribute {$attribute->getId()->toString()} of type 'object' has not Entity connected through Attribute->object"; } elseif ($valueObject->getValue() == null) { $array[$attribute->getName()] = null; - } elseif (!$attribute->getMultiple() && $configuration['level'] < $configuration['maxdepth']) { + if($valueObject->getStringValue() !== null) { + $array[$attribute->getName()] = $valueObject->getStringValue(); + } elseif($valueObject->getArrayValue() !== []) { + $array[$attribute->getName()] = $valueObject->getArrayValue(); + } + } elseif (!$attribute->getMultiple()) { if (count($valueObject->getObjects()) === 0) { $array[$attribute->getName()][] = null; } else { $object = $valueObject->getObjects()->first(); $currentObjects[] = $object; - // Only add an object if it hasn't bean added yet - if (!in_array($object, $configuration['renderedObjects']) && !$attribute->getObject()->isExcluded()) { + // Only add an object if it hasn't been added yet and max depth hasn't been reached + if (!in_array($object, $configuration['renderedObjects']) + && !$attribute->getObject()->isExcluded() + && $configuration['level'] < $configuration['maxDepth'] + ) { $config = $configuration; $config['renderedObjects'][] = $object; + if ($attribute->getObject()->getMaxDepth() + $config['level'] < $config['maxDepth']) { + $config['maxDepth'] = $attribute->getObject()->getMaxDepth() + $config['level']; + } $config['level'] = $config['level'] + 1; $objectToArray = $object->toArray($config); @@ -1178,9 +1191,11 @@ public function toArray(array $configuration = []): array switch ($attribute->getFormat()) { case 'uuid': $array[$attribute->getName()] = $object->getId()->toString(); + $embedded[$attribute->getName()] = $objectToArray; break; case 'url': $array[$attribute->getName()] = $object->getUri(); + $embedded[$attribute->getName()] = $objectToArray; break; case 'json': $array[$attribute->getName()] = $objectToArray; @@ -1198,49 +1213,93 @@ public function toArray(array $configuration = []): array // If we don't set the full object then we want to set self else { // $array[$attribute->getName()] = $object->getSelf() ?? ('/api' . ($object->getEntity()->getRoute() ?? $object->getEntity()->getName()) . '/' . $object->getId()); - $array[$attribute->getName()] = $object->getSelf(); + switch ($attribute->getFormat()) { + case 'uuid': + $array[$attribute->getName()] = $object->getId()->toString(); + break; + case 'url': + $array[$attribute->getName()] = $object->getUri(); + break; + case 'json': + $config = $configuration; + $config['renderedObjects'][] = $object; + if ($attribute->getObject()->getMaxDepth() + $config['level'] < $config['maxDepth']) { + $config['maxDepth'] = $attribute->getObject()->getMaxDepth() + $config['level']; + } + $array[$attribute->getName()] = $object->toArray($config); + break; + case 'iri': + default: + $array[$attribute->getName()] = $object->getSelf(); + break; + } } } - } elseif ($configuration['level'] < $configuration['maxdepth']) { - if (count($valueObject->getObjects()) === 0) { - $array[$attribute->getName()] = []; - } else { - $currentObjects[] = $valueObject->getObjects()->toArray(); - foreach ($valueObject->getObjects() as $object) { - // Only add an object if it hasn't bean added yet - if (!in_array($object, $configuration['renderedObjects']) && !$attribute->getObject()->isExcluded()) { - $config = $configuration; - $config['renderedObjects'] = array_merge($configuration['renderedObjects'], $currentObjects); - $config['level'] = $config['level'] + 1; - $objectToArray = $object->toArray($config); - - // Check if we want an embedded array - if ($configuration['embedded'] && !$attribute->getInclude()) { - switch ($attribute->getFormat()) { - case 'uuid': - $array[$attribute->getName()][] = $object->getId()->toString(); - break; - case 'url': - case 'uri': - $array[$attribute->getName()][] = $object->getUri(); - break; - case 'json': - $array[$attribute->getName()][] = $objectToArray; - break; - case 'iri': - default: - $array[$attribute->getName()][] = $object->getSelf(); - $embedded[$attribute->getName()][] = $objectToArray; - break; - } - continue; + } elseif (count($valueObject->getObjects()) === 0) { + $array[$attribute->getName()] = []; + } else { + $currentObjects[] = $valueObject->getObjects()->toArray(); + foreach ($valueObject->getObjects() as $object) { + // Only add an object if it hasn't been added yet and max depth hans't been reached + if (!in_array($object, $configuration['renderedObjects']) + && !$attribute->getObject()->isExcluded() + && $configuration['level'] < $configuration['maxDepth'] + ) { + $config = $configuration; + $config['renderedObjects'] = array_merge($configuration['renderedObjects'], $currentObjects); + if ($attribute->getObject()->getMaxDepth() + $config['level'] < $config['maxDepth']) { + $config['maxDepth'] = $attribute->getObject()->getMaxDepth() + $config['level']; + } + $config['level'] = $config['level'] + 1; + $objectToArray = $object->toArray($config); + + // Check if we want an embedded array + if ($configuration['embedded'] && !$attribute->getInclude()) { + switch ($attribute->getFormat()) { + case 'uuid': + $array[$attribute->getName()][] = $object->getId()->toString(); + $embedded[$attribute->getName()][] = $objectToArray; + break; + case 'url': + case 'uri': + $array[$attribute->getName()][] = $object->getUri(); + $embedded[$attribute->getName()][] = $objectToArray; + break; + case 'json': + $array[$attribute->getName()][] = $objectToArray; + break; + case 'iri': + default: + $array[$attribute->getName()][] = $object->getSelf(); + $embedded[$attribute->getName()][] = $objectToArray; + break; } - $array[$attribute->getName()][] = $objectToArray; + continue; } - // If we don't set the full object then we want to set self - else { - // $array[$attribute->getName()][] = $object->getSelf() ?? ('/api' . ($object->getEntity()->getRoute() ?? $object->getEntity()->getName()) . '/' . $object->getId()); - $array[$attribute->getName()][] = $object->getSelf(); + $array[$attribute->getName()][] = $objectToArray; + } + // If we don't set the full object then we want to set self + else { + switch ($attribute->getFormat()) { + case 'uuid': + $array[$attribute->getName()][] = $object->getId()->toString(); + break; + case 'url': + case 'uri': + $array[$attribute->getName()][] = $object->getUri(); + break; + case 'json': + $config = $configuration; + $config['renderedObjects'][] = $object; + if ($attribute->getObject()->getMaxDepth() + $config['level'] < $config['maxDepth']) { + $config['maxDepth'] = $attribute->getObject()->getMaxDepth() + $config['level']; + } + $array[$attribute->getName()][] = $object->toArray($config); + break; + case 'iri': + default: + $array[$attribute->getName()][] = $object->getSelf(); + break; } } } @@ -1271,11 +1330,12 @@ public function getReadableSyncDataArray(): ?array foreach ($this->getSynchronizations() as $synchronization) { $synchronizations[] = [ 'id' => $synchronization->getId()->toString(), - 'gateway' => [ - 'id' => $synchronization->getSource()->getId()->toString(), - 'name' => $synchronization->getSource()->getName(), - 'ref' => $synchronization->getSource()->getReference(), - 'location' => $synchronization->getSource()->getLocation(), + 'source' => [ + 'id' => $synchronization->getSource()->getId()->toString(), + 'ref' => $synchronization->getSource()->getReference(), + 'name' => $synchronization->getSource()->getName(), + 'description' => $synchronization->getSource()->getDescription(), + 'location' => $synchronization->getSource()->getLocation(), ], 'endpoint' => $synchronization->getEndpoint(), 'sourceId' => $synchronization->getSourceId(), @@ -1385,7 +1445,9 @@ public function addSynchronization(Synchronization $synchronization): self { if (!$this->synchronizations->contains($synchronization)) { $this->synchronizations[] = $synchronization; - $synchronization->setObject($this); + if ($synchronization->getObject() !== $this) { + $synchronization->setObject($this); + } } return $this; @@ -1472,10 +1534,16 @@ public function setDateModified(DateTimeInterface $dateModified): self */ public function changeCascade(DateTimeInterface $dateModified): self { - $this->setDateCreated($dateModified); + $this->setDateModified($dateModified); + + $values = $this->subresourceOf->filter( + function(Value $subresource) { + return $subresource->getAttribute()->getCacheSubObjects(); + } + ); // Lets update the date created of parent resources - foreach ($this->subresourceOf as $mainResourceValue) { + foreach ($values as $mainResourceValue) { $mainresource = $mainResourceValue->getObjectEntity(); if ($mainresource->getDateModified() < $this->getDateModified()) { $mainresource->changeCascade($dateModified); diff --git a/api/src/Entity/Organization.php b/api/src/Entity/Organization.php index 00983dd53..cd231688b 100644 --- a/api/src/Entity/Organization.php +++ b/api/src/Entity/Organization.php @@ -21,21 +21,20 @@ use Symfony\Component\Validator\Constraints as Assert; /** - * This entity holds the information about an Organisation. + * This entity holds the information about an Organization. * * @ApiResource( - * normalizationContext={"groups"={"read"}, "enable_max_depth"=true}, - * denormalizationContext={"groups"={"write"}, "enable_max_depth"=true}, + * normalizationContext={"groups"={"read"}, "enable_max_depth"=true}, + * denormalizationContext={"groups"={"write"}, "enable_max_depth"=true}, * itemOperations={ - * "get"={"path"="/admin/organisations/{id}"}, - * "put"={"path"="/admin/organisations/{id}"}, - * "delete"={"path"="/admin/organisations/{id}"} + * "get"={"path"="/admin/organizations/{id}"}, + * "put"={"path"="/admin/organizations/{id}"}, + * "delete"={"path"="/admin/organizations/{id}"} * }, * collectionOperations={ - * "get"={"path"="/admin/organisations"}, - * "post"={"path"="/admin/organisations"} + * "get"={"path"="/admin/organizations"}, + * "post"={"path"="/admin/organizations"} * }) - * ) * * @ORM\HasLifecycleCallbacks * @@ -117,7 +116,7 @@ class Organization * * @MaxDepth(1) * - * @ORM\OneToMany(targetEntity=User::class, mappedBy="organisation", orphanRemoval=true) + * @ORM\OneToMany(targetEntity=User::class, mappedBy="organization", orphanRemoval=true) */ private $users; @@ -321,7 +320,7 @@ public function addUser(User $user): self { if (!$this->users->contains($user)) { $this->users[] = $user; - $user->setOrganisation($this); + $user->setOrganization($this); } return $this; @@ -331,8 +330,8 @@ public function removeUser(User $user): self { if ($this->users->removeElement($user)) { // set the owning side to null (unless already changed) - if ($user->getOrganisation() === $this) { - $user->setOrganisation(null); + if ($user->getOrganization() === $this) { + $user->setOrganization(null); } } diff --git a/api/src/Entity/Synchronization.php b/api/src/Entity/Synchronization.php index b60202f61..678594050 100644 --- a/api/src/Entity/Synchronization.php +++ b/api/src/Entity/Synchronization.php @@ -87,7 +87,7 @@ class Synchronization * * @Groups({"read","write"}) * - * @ORM\ManyToOne(targetEntity=ObjectEntity::class, inversedBy="synchronizations", fetch="EAGER") + * @ORM\ManyToOne(targetEntity=ObjectEntity::class, cascade={"persist"}, inversedBy="synchronizations", fetch="EAGER") */ private ?ObjectEntity $object = null; @@ -154,7 +154,16 @@ class Synchronization private ?string $hash = ''; /** - * @var bool Whether or not the synchronization is blocked + * @var ?string The sha(256) used to check if a Sync should be triggered cause the object has changed + * + * @Groups({"read","write"}) + * + * @ORM\Column(type="string", nullable=true) + */ + private ?string $sha = null; + + /** + * @var bool Whether the synchronization is blocked * * @Groups({"read", "write"}) * @@ -271,9 +280,22 @@ public function getObject(): ?ObjectEntity public function setObject(?ObjectEntity $object): self { - $this->object = $object; + if ($object !== null) { + $this->setEntity($object->getEntity()); + + if ($object->getSynchronizations()->contains($this) === false) { + $object->addSynchronization($this); + } + } + if ($object === null) { + $this->setEntity(null); + + if ($this->object->getSynchronizations()->contains($this) === true) { + $this->object->removeSynchronization($this); + } + } - $this->setEntity($object->getEntity()); + $this->object = $object; return $this; } @@ -348,6 +370,18 @@ public function setHash(?string $hash): self return $this; } + public function getSha(): ?string + { + return $this->sha; + } + + public function setSha(?string $sha): self + { + $this->sha = $sha; + + return $this; + } + public function getSourceLastChanged(): ?\DateTimeInterface { return $this->sourceLastChanged; diff --git a/api/src/Entity/Template.php b/api/src/Entity/Template.php index 470316928..a400d7a1a 100644 --- a/api/src/Entity/Template.php +++ b/api/src/Entity/Template.php @@ -111,7 +111,7 @@ class Template * * @ORM\ManyToOne(targetEntity=Organization::class, inversedBy="templates") * - * @ORM\JoinColumn(nullable=false) + * @ORM\JoinColumn(nullable=true) */ private ?Organization $organization = null; @@ -122,7 +122,21 @@ class Template * * @ORM\Column(type="array") */ - private $supportedSchemas = []; + private array $supportedSchemas = []; + + /** + * @Groups({"read", "write"}) + * + * @ORM\Column(type="string", length=255, nullable=true) + */ + private ?string $reference = null; + + /** + * @Groups({"read", "write"}) + * + * @ORM\Column(type="string", length=255, nullable=true) + */ + private ?string $version = null; /** * @var Datetime|null The moment this resource was created @@ -146,6 +160,76 @@ class Template */ private ?DateTime $dateModified = null; + + /** + * Constructor for creating an Template. + * + * @param array|null $configuration A configuration array used to correctly create a Template. The following keys are supported: + * + */ + public function __construct(?array $configuration = []) + { + if ($configuration) { + $this->fromSchema($configuration); + } + } + + /** + * Uses given $configuration array to set the properties of this Template. + * + * @param array $schema The schema to load. + * + * @return void + */ + public function fromSchema(array $schema) + { + if (key_exists('$id', $schema) === true) { + $this->setReference($schema['$id']); + } + if (key_exists('version', $schema) === true) { + $this->setVersion($schema['version']); + } + + if (key_exists('name', $schema) === true) { + $this->setName($schema['name']); + } + + if (key_exists('description', $schema) === true) { + $this->setDescription($schema['description']); + } + + if (key_exists('content', $schema) === true) { + $this->setContent($schema['content']); + } + + if (key_exists('organization', $schema) === true) { + $this->setOrganization($schema['organization']); + } + + if (key_exists('supportedSchemas', $schema) === true) { + $this->setSupportedSchemas($schema['supportedSchemas']); + } + } + + /** + * Convert this Template to a schema. + * + * @return array Schema array. + */ + public function toSchema(): array + { + return [ + '$id' => $this->getReference(), //@todo dit zou een interne uri verwijzing moeten zijn maar hebben we nog niet + '$schema' => 'https://docs.commongateway.nl/schemas/Template.schema.json', + 'name' => $this->getName(), + 'description' => $this->getDescription(), + 'content' => $this->getContent(), + 'version' => $this->getVersion(), + 'organization' => $this->getOrganization(), + 'supportedSchemas' => $this->getSupportedSchemas(), + ]; + } + public function setId(UuidInterface $id): self { $this->id = $id; @@ -218,6 +302,30 @@ public function setSupportedSchemas(array $supportedSchemas): self return $this; } + public function getReference(): ?string + { + return $this->reference; + } + + public function setReference(?string $reference): self + { + $this->reference = $reference; + + return $this; + } + + public function getVersion(): ?string + { + return $this->version; + } + + public function setVersion(?string $version): self + { + $this->version = $version; + + return $this; + } + public function getDateCreated(): ?\DateTimeInterface { return $this->dateCreated; diff --git a/api/src/Entity/User.php b/api/src/Entity/User.php index 107755d5f..845610de9 100644 --- a/api/src/Entity/User.php +++ b/api/src/Entity/User.php @@ -25,8 +25,8 @@ * This entity holds the information about an User. * * @ApiResource( - * normalizationContext={"groups"={"read"}, "enable_max_depth"=true}, - * denormalizationContext={"groups"={"write"}, "enable_max_depth"=true}, + * normalizationContext={"groups"={"read"}, "enable_max_depth"=true}, + * denormalizationContext={"groups"={"write"}, "enable_max_depth"=true}, * itemOperations={ * "get"={"path"="/admin/users/{id}"}, * "put"={"path"="/admin/users/{id}"}, @@ -36,7 +36,6 @@ * "get"={"path"="/admin/users"}, * "post"={"path"="/admin/users"} * }) - * ) * * @ORM\HasLifecycleCallbacks * @@ -137,7 +136,7 @@ class User implements PasswordAuthenticatedUserInterface * * @ORM\JoinColumn(nullable=false) */ - private ?Organization $organisation = null; + private ?Organization $organization = null; /** * @Groups({"read", "write"}) @@ -239,7 +238,7 @@ public function fromSchema(array $schema): self $this->setPassword('!ChangeMe!'); array_key_exists('locale', $schema) ? $this->setLocale($schema['locale']) : ''; array_key_exists('person', $schema) ? $this->setPerson($schema['person']) : ''; - array_key_exists('organization', $schema) ? $this->setOrganisation($schema['organization']) : ''; + array_key_exists('organization', $schema) ? $this->setOrganization($schema['organization']) : ''; array_key_exists('applications', $schema) ? $this->setApplications($schema['applications']) : ''; // Todo: temporary? make sure we never allow admin scopes to be added or removed with fromSchema @@ -297,7 +296,7 @@ public function toSchema(): array 'locale' => $this->getLocale(), 'person' => $this->getPerson(), 'scopes' => $this->getScopes(), - 'organization' => $this->getOrganisation() ? $this->getOrganisation()->toSchema() : null, + 'organization' => $this->getOrganization() ? $this->getOrganization()->toSchema() : null, 'applications' => $applications, 'securityGroups' => $securityGroups, ]; @@ -392,14 +391,14 @@ public function setEmail(string $email): self return $this; } - public function getOrganisation(): ?Organization + public function getOrganization(): ?Organization { - return $this->organisation; + return $this->organization; } - public function setOrganisation(?Organization $organization): self + public function setOrganization(?Organization $organization): self { - $this->organisation = $organization; + $this->organization = $organization; return $this; } diff --git a/api/src/Entity/Value.php b/api/src/Entity/Value.php index e83e0f249..91154a87d 100644 --- a/api/src/Entity/Value.php +++ b/api/src/Entity/Value.php @@ -659,21 +659,38 @@ public function setValue($value, bool $unsafe = false, ?DateTimeInterface $dateM foreach ($valueArray as $value) { // Catch Array input (for hydrator) if (is_array($value)) { - $object = new ObjectEntity($this->getAttribute()->getObject()); + $object = null; + + // Make sure to not create new objects if we don't have to (_id in testdata)... + if (isset($value['_id'])) { + $objects = $this->objects->filter(function ($item) use ($value) { + return $item->getId() !== null && $item->getId()->toString() === $value['_id']; + }); + + if (count($objects) > 0) { + $object = $objects[0]; + } + } + if ($object instanceof ObjectEntity === false) { + // failsafe to not create duplicate sub objects. In some weird cases $objects[0] doesn't return an ObjectEntity. + if (isset($objects) === true && count($objects) > 0) { + continue; + } + $object = new ObjectEntity($this->getAttribute()->getObject()); + } $object->setOwner($this->getObjectEntity()->getOwner()); $object->setApplication($this->getObjectEntity()->getApplication()); $object->setOrganization($this->getObjectEntity()->getOrganization()); $object->hydrate($value, $unsafe, $dateModified); $value = $object; - $this->hydratedObjects[] = $object; } if (is_string($value)) { $idArray[] = $value; } elseif (!$value) { continue; - } elseif ($value instanceof ObjectEntity) { + } elseif ($value instanceof ObjectEntity && $this->objects->contains($value) === false) { $this->addObject($value); } } diff --git a/api/src/Logger/SessionDataProcessor.php b/api/src/Logger/SessionDataProcessor.php index ae9014c48..e16385ee2 100644 --- a/api/src/Logger/SessionDataProcessor.php +++ b/api/src/Logger/SessionDataProcessor.php @@ -2,6 +2,10 @@ namespace App\Logger; +use App\Event\ActionEvent; +use Doctrine\ORM\EntityManagerInterface; +use Exception; +use Psr\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Session\SessionInterface; @@ -10,30 +14,179 @@ class SessionDataProcessor private SessionInterface $session; private RequestStack $requestStack; - public function __construct(SessionInterface $session, RequestStack $requestStack) + /** + * @var EventDispatcherInterface The event dispatcher. + */ + private EventDispatcherInterface $eventDispatcher; + + /** + * @var EntityManagerInterface The entity manager. + */ + private EntityManagerInterface $entityManager; + + /** + * @param SessionInterface $session + * @param RequestStack $requestStack + * @param EventDispatcherInterface $eventDispatcher + * @param EntityManagerInterface $entityManager + */ + public function __construct( + SessionInterface $session, + RequestStack $requestStack, + EventDispatcherInterface $eventDispatcher, + EntityManagerInterface $entityManager + ) { $this->session = $session; $this->requestStack = $requestStack; + $this->eventDispatcher = $eventDispatcher; + $this->entityManager = $entityManager; } - public function __invoke(array $record): array + /** + * Update the context with data from the session and the request stack. + * + * @param array $record The log record to update the context for. + * + * @return array The updated context. + */ + public function updateContext(array $record): array + { + $context = $record['context']; + + $context['session'] = $this->session->getId(); + $context['process'] = $this->session->has('process') === true ? $this->session->get('process') : ''; + $context['endpoint'] = $this->session->has('endpoint') === true ? $this->session->get('endpoint') : ''; + $context['schema'] = $this->session->has('schema') === true ? $this->session->get('schema') : ''; + $context['object'] = $this->session->has('object') === true ? $this->session->get('object') : ''; + $context['cronjob'] = $this->session->has('cronjob') === true ? $this->session->get('cronjob') : ''; + $context['action'] = $this->session->has('action') === true ? $this->session->get('action') : ''; + $context['mapping'] = $this->session->has('mapping') === true ? $this->session->get('mapping') : ''; + $context['source'] = $this->session->has('source') === true ? $this->session->get('source') : ''; + + // Add more to context if we are dealing with a log containing sourceCall data. + if (isset($context['sourceCall']) === true) { + $context = $this->updateSourceCallContext($context, $record['level_name']); + } + + $context['user'] = $this->session->has('user') === true ? $this->session->get('user') : ''; + $context['organization'] = $this->session->has('organization') === true ? $this->session->get('organization') : ''; + $context['application'] = $this->session->has('application') === true ? $this->session->get('application') : ''; + $context['host'] = $this->requestStack->getMainRequest() !== null ? $this->requestStack->getMainRequest()->getHost() : ''; + $context['ip'] = $this->requestStack->getMainRequest() !== null ? $this->requestStack->getMainRequest()->getClientIp() : ''; + $context['method'] = $this->requestStack->getMainRequest() !== null ? $this->requestStack->getMainRequest()->getMethod() : ''; + + // Add more to context for higher level logs. + if (in_array($record['level_name'], ['ERROR', 'CRITICAL', 'ALERT', 'EMERGENCY']) === true) { + $context = $this->addErrorContext($context, $record['level_name']); + } + + return $context; + } + + /** + * Update the context for Source call logs. + * + * @param array $context The log context we are updating. + * @param string $levelName The level name of the log record we are updating the context for. + * + * @return array The updated context. + */ + private function updateSourceCallContext(array $context, string $levelName): array + { + if (in_array($levelName, ['ERROR', 'CRITICAL', 'ALERT', 'EMERGENCY']) === true) { + $maxStrLength = ($context['sourceCall']['maxCharCountErrorBody'] ?? 2000); + unset($context['sourceCall']['maxCharCountBody']); + } else { + $maxStrLength = ($context['sourceCall']['maxCharCountBody'] ?? 500); + unset($context['sourceCall']['maxCharCountErrorBody']); + } + + if (isset($context['sourceCall']['callQuery']) === true) { + $context['sourceCall']['callQuery'] = json_encode($context['sourceCall']['callQuery']); + if ($context['sourceCall']['callQuery'] === "[]") { + $context['sourceCall']['callQuery'] = ''; + } + } + + if (isset($context['sourceCall']['callBody']) === true && strlen($context['sourceCall']['callBody']) > $maxStrLength) { + $context['sourceCall']['callBody'] = substr($context['sourceCall']['callBody'], 0, $maxStrLength).'...'; + } + + if (isset($context['sourceCall']['responseBody']) === true && strlen($context['sourceCall']['responseBody']) > $maxStrLength) { + $context['sourceCall']['responseBody'] = substr($context['sourceCall']['responseBody'], 0, $maxStrLength).'...'; + } + + return $context; + } + + /** + * Update the context with data from the session and the request stack. For log records with level ERROR or higher. + * See: https://github.com/Seldaek/monolog/blob/main/doc/01-usage.md for all possible log levels. + * + * @param array $context The log context we are updating. + * @param string $levelName The level name of the log record we are updating the context for. + * + * @return array The updated context. + */ + private function addErrorContext(array $context, string $levelName): array + { + $context['pathRaw'] = $this->requestStack->getMainRequest() !== null ? $this->requestStack->getMainRequest()->getPathInfo() : ''; + $context['querystring'] = $this->requestStack->getMainRequest() !== null ? $this->requestStack->getMainRequest()->getQueryString() : ''; + $context['mongoDBFilter'] = $this->session->has('mongoDBFilter') === true ? json_encode($this->session->get('mongoDBFilter')) : ''; + $context['contentType'] = $this->requestStack->getMainRequest() !== null ? $this->requestStack->getMainRequest()->getContentType() : ''; + + // Do not log entire body for normal errors, only critical and higher. + if ($this->requestStack->getMainRequest() !== null && $levelName !== 'ERROR') { + try { + $context['body'] = $this->requestStack->getMainRequest()->toArray(); + } catch (Exception $exception) { + $context['body'] = ''; + } + $context['crude_body'] = $this->requestStack->getMainRequest() ? $this->requestStack->getMainRequest()->getContent() : ''; + } + + return $context; + } + + /** + * Dispatches a log create action. + * + * @param array $record The log record that is created. + * + * @return array The resulting log record after the action. + */ + public function dispatchLogCreateAction(array $record): array { - $record['context']['session'] = $this->session->getId(); - $record['context']['process'] = $this->session->has('process') ? $this->session->get('process') : ''; - $record['context']['endpoint'] = $this->session->has('endpoint') ? $this->session->get('endpoint') : ''; - $record['context']['schema'] = $this->session->has('schema') ? $this->session->get('schema') : ''; - $record['context']['object'] = $this->session->has('object') === true ? $this->session->get('object') : ''; - $record['context']['cronjob'] = $this->session->has('cronjob') ? $this->session->get('cronjob') : ''; - $record['context']['action'] = $this->session->has('cronjob') ? $this->session->get('action') : ''; - $record['context']['mapping'] = $this->session->has('mapping') ? $this->session->get('mapping') : ''; - $record['context']['source'] = $this->session->has('source') ? $this->session->get('source') : ''; - $record['context']['plugin'] = isset($record['data']['plugin']) === true ? $record['data']['plugin'] : ''; - $record['context']['user'] = $this->session->has('user') ? $this->session->get('user') : ''; - $record['context']['organization'] = $this->session->has('organization') ? $this->session->get('organization') : ''; - $record['context']['application'] = $this->session->has('application') ? $this->session->get('application') : ''; - $record['context']['host'] = $this->requestStack->getMainRequest() ? $this->requestStack->getMainRequest()->getHost() : ''; - $record['context']['ip'] = $this->requestStack->getMainRequest() ? $this->requestStack->getMainRequest()->getClientIp() : ''; + if ($this->entityManager->getConnection()->isConnected() === true + && in_array( + $this->entityManager->getConnection()->getDatabase(), + $this->entityManager->getConnection()->getSchemaManager()->listDatabases() + ) === true + && $this->entityManager->getConnection()->getSchemaManager()->tablesExist('action') === true + && in_array($record['level_name'], ['ERROR', 'CRITICAL', 'ALERT', 'EMERGENCY']) === true + ){ + $event = new ActionEvent('commongateway.action.event', $record, 'commongateway.log.create'); + + $this->eventDispatcher->dispatch($event, 'commongateway.action.event'); + + $record = $event->getData(); + } return $record; } + + /** + * Updates the log record with data from the session, request and from actions. + * + * @param array $record The log record. + * + * @return array The updated log record. + */ + public function __invoke(array $record): array + { + $record['context'] = $this->updateContext($record); + + return $this->dispatchLogCreateAction($record); + } } diff --git a/api/src/MessageHandler/ActionMessageHandler.php b/api/src/MessageHandler/ActionMessageHandler.php index 36b4df458..daf1b4f1f 100644 --- a/api/src/MessageHandler/ActionMessageHandler.php +++ b/api/src/MessageHandler/ActionMessageHandler.php @@ -5,7 +5,7 @@ use App\Entity\Action; use App\Message\ActionMessage; use App\Repository\ActionRepository; -use App\Subscriber\ActionSubscriber; +use CommonGateway\CoreBundle\Subscriber\ActionSubscriber; use Doctrine\ORM\EntityManagerInterface; use Exception; use Symfony\Component\Messenger\Handler\MessageHandlerInterface; diff --git a/api/src/Repository/ApplicationRepository.php b/api/src/Repository/ApplicationRepository.php index 15765f22b..53752d1c2 100644 --- a/api/src/Repository/ApplicationRepository.php +++ b/api/src/Repository/ApplicationRepository.php @@ -21,24 +21,21 @@ public function __construct(ManagerRegistry $registry) } /** - * @param string $domain + * Find all applications that have the given $domain in there list of domains. * - * @throws NonUniqueResultException + * @param string $domain A domain to search with. * - * @return Application|null + * @return array|null */ - public function findByDomain(string $domain): ?Application + public function findByDomain(string $domain): ?array { - // TODO: something like this $query = $this->createQueryBuilder('a') - ->andWhere(':domain IN (a.domains)') - ->setParameters(['domain' => $domain]); - -// var_dump($query->getDQL()); + ->andWhere('a.domains LIKE :domain') + ->setParameters(['domain' => "%$domain%"]); return $query ->getQuery() - ->getOneOrNullResult(); + ->getResult(); } // /** diff --git a/api/src/Repository/AuditTrailRepository.php b/api/src/Repository/AuditTrailRepository.php index 83c6615d6..67173b663 100644 --- a/api/src/Repository/AuditTrailRepository.php +++ b/api/src/Repository/AuditTrailRepository.php @@ -19,6 +19,35 @@ public function __construct(ManagerRegistry $registry) parent::__construct($registry, AuditTrail::class); } + /** + * Returns all get item audit trails on the given ObjectEntity by the given user, ordered by creation date of the audit trail. (and only audit trails where response was a 200). + * + * @param string $objectId The id of an ObjectEntity. + * @param string $userId The id of a User. + * + * @return array An array of audit trails found, or empty array. + */ + public function findDateRead(string $objectId, string $userId): array + { + $query = $this->createQueryBuilder('a') + ->where('a.resource = :objectId') + ->andWhere('a.userId = :userId') + ->andWhere('a.result = :responseStatusCode') + ->andWhere('LOWER(a.action) = :method') + ->setParameters([ + 'objectId' => $objectId, + 'userId' => $userId, + 'responseStatusCode' => 200, + 'method' => 'retrieve', + ]) + ->orderBy('a.creationDate', 'DESC') + ->distinct(); + + return $query + ->getQuery() + ->getResult(); + } + // /** // * @return AuditTrail[] Returns an array of AuditTrail objects // */ diff --git a/api/src/Repository/LogRepository.php b/api/src/Repository/LogRepository.php index d3cebce76..b58312b74 100644 --- a/api/src/Repository/LogRepository.php +++ b/api/src/Repository/LogRepository.php @@ -27,6 +27,7 @@ public function __construct(ManagerRegistry $registry) * @param string $userId * * @return array + * @deprecated */ public function findDateRead(string $objectId, string $userId): array { diff --git a/api/src/Repository/ObjectEntityRepository.php b/api/src/Repository/ObjectEntityRepository.php index 6823c370f..9f3b68051 100644 --- a/api/src/Repository/ObjectEntityRepository.php +++ b/api/src/Repository/ObjectEntityRepository.php @@ -35,6 +35,105 @@ public function __construct(ManagerRegistry $registry, SessionInterface $session parent::__construct($registry, ObjectEntity::class); } + /** + * Gets and returns an array with the allowed filters on an Entity (including its subEntities / sub-filters). + * + * @param Entity $Entity The Entity we are currently doing a get collection on. + * @param string $prefix + * @param int $level + * @param bool $embedded + * + * @return array The array with allowed filters. + */ + public function getFilterParameters(Entity $Entity, string $prefix = '', int $level = 1, bool $embedded = false): array + { + $prefix = $embedded && $level === 2 ? "embedded.$prefix" : $prefix; + + //todo: we only check for the allowed keys/attributes to filter on, if this attribute is a dateTime (or date), we should also check if the value is a valid dateTime string? + // NOTE: + // Filter id looks for ObjectEntity id and externalId + // Filter _id looks specifically/only for ObjectEntity id + // Filter _externalId looks specifically/only for ObjectEntity externalId + + // defaults + $filters = [ + $prefix.'id', $prefix.'_id', $prefix.'_externalId', $prefix.'_uri', $prefix.'_self', $prefix.'_organization', + $prefix.'_application', $prefix.'_dateCreated', $prefix.'_dateModified', $prefix.'_mapping', + ]; + + foreach ($Entity->getAttributes() as $attribute) { + if (in_array($attribute->getType(), ['string', 'date', 'datetime', 'integer', 'float', 'number', 'boolean']) && $attribute->getSearchable()) { + $filters[] = $prefix.$attribute->getName(); + } elseif ($attribute->getObject() && $level < 3 && !str_contains($prefix, $attribute->getName().'.')) { + $attribute->getSearchable() && $filters[] = $prefix.$attribute->getName(); + $embeddedString = ''; + if ($embedded && $level > 1) { + $embeddedString = 'embedded.'; + } + $filters = array_merge($filters, $this->getFilterParameters($attribute->getObject(), $prefix.$embeddedString.$attribute->getName().'.', $level + 1, $embedded)); + } + } + + return $filters; + } + + /** + * Gets and returns an array with the allowed sortable attributes on an Entity (including its subEntities). + * + * @param Entity $Entity The Entity we are currently doing a get collection on. + * @param string $prefix + * @param int $level + * @param bool $embedded + * + * @return array The array with allowed attributes to sort by. + */ + public function getOrderParameters(Entity $Entity, string $prefix = '', int $level = 1, bool $embedded = false): array + { + $prefix = $prefix; + if ($embedded && $level === 2) { + $prefix = "embedded.$prefix"; + } + // defaults + $sortable = [$prefix.'_dateCreated', $prefix.'_dateModified']; + + foreach ($Entity->getAttributes() as $attribute) { + if (in_array($attribute->getType(), ['string', 'date', 'datetime', 'integer', 'float', 'number']) && $attribute->getSortable()) { + $sortable[] = $prefix.$attribute->getName(); + } elseif ($attribute->getObject() && $level < 3 && !str_contains($prefix, $attribute->getName().'.')) { + $embeddedString = ''; + if ($embedded && $level > 1) { + $embeddedString = 'embedded.'; + } + $sortable = array_merge($sortable, $this->getOrderParameters($attribute->getObject(), $prefix.$embeddedString.$attribute->getName().'.', $level + 1)); + } + } + + return $sortable; + } + + /** + * Finds object entities on their id or a sourceId of a synchronization this ObjectEntity has. + * + * @param string $identifier + * + * @throws NonUniqueResultException + * + * @return ObjectEntity The found object entity + */ + public function findByAnyId(string $identifier): ?ObjectEntity + { + $query = $this->createQueryBuilder('o') + ->leftJoin('o.synchronizations', 's') + ->where('s.sourceId = :identifier') + ->setParameter('identifier', $identifier); + + if (Uuid::isValid($identifier)) { + $query->orWhere('o.id = :identifier'); + } + + return $query->getQuery()->getOneOrNullResult(); + } + /** * Does the same as findByEntity(), but also returns an integer representing the total amount of results using the input to create a sql statement. $entity is required. * @@ -47,6 +146,7 @@ public function __construct(ManagerRegistry $registry, SessionInterface $session * @throws NoResultException|NonUniqueResultException * * @return array With a key 'objects' containing the actual objects found and a key 'total' with an integer representing the total amount of results found. + * @deprecated */ public function findAndCountByEntity(Entity $entity, array $filters = [], array $order = [], int $offset = 0, int $limit = 25): array { @@ -81,6 +181,7 @@ public function findAndCountByEntity(Entity $entity, array $filters = [], array * @throws Exception * * @return array Returns an array of ObjectEntity objects + * @deprecated */ public function findByEntity(Entity $entity, array $filters = [], array $order = [], int $offset = 0, int $limit = 25, QueryBuilder $query = null): array { @@ -103,6 +204,7 @@ public function findByEntity(Entity $entity, array $filters = [], array $order = * @throws NoResultException|NonUniqueResultException * * @return int Returns an integer, for the total ObjectEntities found with this Entity and with the given filters. + * @deprecated */ public function countByEntity(Entity $entity, array $filters = [], QueryBuilder $query = null): int { @@ -155,11 +257,11 @@ private function createQuery(Entity $entity, array $filters = [], array $order = // Multitenancy, only show objects this user is allowed to see. // Only show objects this user owns or object that have an organization this user is part of or that are inhereted down the line - $organizations = $this->session->get('organizations', []); + $organizations = []; $parentOrganizations = []; // Make sure we only check for parentOrganizations if inherited is true in the (ObjectEntity)->entity->inherited if ($entity->getInherited()) { - $parentOrganizations = $this->session->get('parentOrganizations', []); + $parentOrganizations = []; } // $query->andWhere('o.organization IN (:organizations) OR o.organization IN (:parentOrganizations) OR o.organization = :defaultOrganization OR o.owner = :userId') @@ -872,92 +974,4 @@ private function makeKeySqlFriendly(string $key): string // todo, probably add more special characters to replace... return str_replace('-', 'Dash', $key); } - - /** - * Gets and returns an array with the allowed filters on an Entity (including its subEntities / sub-filters). - * - * @param Entity $Entity The Entity we are currently doing a get collection on. - * @param string $prefix - * @param int $level - * - * @return array The array with allowed filters. - */ - public function getFilterParameters(Entity $Entity, string $prefix = '', int $level = 1, bool $embedded = false): array - { - $prefix = $embedded && $level === 2 ? "embedded.$prefix" : $prefix; - - //todo: we only check for the allowed keys/attributes to filter on, if this attribute is a dateTime (or date), we should also check if the value is a valid dateTime string? - // NOTE: - // Filter id looks for ObjectEntity id and externalId - // Filter _id looks specifically/only for ObjectEntity id - // Filter _externalId looks specifically/only for ObjectEntity externalId - - // defaults - $filters = [ - $prefix.'id', $prefix.'_id', $prefix.'_externalId', $prefix.'_uri', $prefix.'_self', $prefix.'_organization', - $prefix.'_application', $prefix.'_dateCreated', $prefix.'_dateModified', $prefix.'_mapping', - ]; - - foreach ($Entity->getAttributes() as $attribute) { - if (in_array($attribute->getType(), ['string', 'date', 'datetime', 'integer', 'float', 'number', 'boolean']) && $attribute->getSearchable()) { - $filters[] = $prefix.$attribute->getName(); - } elseif ($attribute->getObject() && $level < 3 && !str_contains($prefix, $attribute->getName().'.')) { - $attribute->getSearchable() && $filters[] = $prefix.$attribute->getName(); - $embeddedString = $embedded && $level > 1 ? 'embedded.' : ''; - $filters = array_merge($filters, $this->getFilterParameters($attribute->getObject(), $prefix.$embeddedString.$attribute->getName().'.', $level + 1, $embedded)); - } - } - - return $filters; - } - - /** - * Gets and returns an array with the allowed sortable attributes on an Entity (including its subEntities). - * - * @param Entity $Entity The Entity we are currently doing a get collection on. - * @param string $prefix - * @param int $level - * - * @return array The array with allowed attributes to sort by. - */ - public function getOrderParameters(Entity $Entity, string $prefix = '', int $level = 1, bool $embedded = false): array - { - $prefix = $embedded && $level === 2 ? "embedded.$prefix" : $prefix; - // defaults - $sortable = [$prefix.'_dateCreated', $prefix.'_dateModified']; - - foreach ($Entity->getAttributes() as $attribute) { - if (in_array($attribute->getType(), ['string', 'date', 'datetime', 'integer', 'float', 'number']) && $attribute->getSortable()) { - $sortable[] = $prefix.$attribute->getName(); - } elseif ($attribute->getObject() && $level < 3 && !str_contains($prefix, $attribute->getName().'.')) { - $embeddedString = $embedded && $level > 1 ? 'embedded.' : ''; - $sortable = array_merge($sortable, $this->getOrderParameters($attribute->getObject(), $prefix.$embeddedString.$attribute->getName().'.', $level + 1)); - } - } - - return $sortable; - } - - /** - * Finds object entities on their id or a sourceId of a synchronization this ObjectEntity has. - * - * @param string $identifier - * - * @throws NonUniqueResultException - * - * @return ObjectEntity The found object entity - */ - public function findByAnyId(string $identifier): ?ObjectEntity - { - $query = $this->createQueryBuilder('o') - ->leftJoin('o.synchronizations', 's') - ->where('s.sourceId = :identifier') - ->setParameter('identifier', $identifier); - - if (Uuid::isValid($identifier)) { - $query->orWhere('o.id = :identifier'); - } - - return $query->getQuery()->getOneOrNullResult(); - } } diff --git a/api/src/Security/ApiKeyAuthenticator.php b/api/src/Security/ApiKeyAuthenticator.php index 24917c76b..c7af2224d 100644 --- a/api/src/Security/ApiKeyAuthenticator.php +++ b/api/src/Security/ApiKeyAuthenticator.php @@ -2,6 +2,7 @@ namespace App\Security; +use App\Entity\Application; use App\Entity\User; use App\Service\FunctionService; use Conduction\CommonGroundBundle\Service\AuthenticationService; @@ -56,7 +57,7 @@ public function supports(Request $request): ?bool } /** - * Get all the child organisations for an organisation. + * Get all the child organizations for an organization. * * @param array $organizations * @param string $organization @@ -85,7 +86,7 @@ private function getSubOrganizations(array $organizations, string $organization, } /** - * Get al the parent organizations for an organisation. + * Get al the parent organizations for an organization. * * @param array $organizations * @param string $organization @@ -146,20 +147,27 @@ public function authenticate(Request $request): PassportInterface { $key = $request->headers->get('Authorization'); $application = $this->entityManager->getRepository('App:Application')->findOneBy(['secret' => $key]); - if (!$application) { + if ($application === null) { throw new AuthenticationException('Invalid ApiKey'); } try { $user = $application->getOrganization()->getUsers()[0]; } catch (\Exception $exception) { - throw new AuthenticationException('Invalid User'); + throw new AuthenticationException('An invalid User is configured for this ApiKey'); } - $this->session->set('apiKeyApplication', $application->getId()->toString()); - if (!$user || !($user instanceof User)) { - throw new AuthenticationException('The provided token does not match the user it refers to'); + if ($user instanceof User === false) { + throw new AuthenticationException('An invalid User is configured for this ApiKey'); } + + // Set apiKey Application id in session + $this->session->set('apiKeyApplication', $application->getId()->toString()); + + // Set organization id and user id in session + $this->session->set('user', $user->getId()->toString()); + $this->session->set('organization', $user->getOrganization() !== null ? $user->getOrganization()->getId()->toString() : null); + $roleArray = []; foreach ($user->getSecurityGroups() as $securityGroup) { $roleArray['roles'][] = "Role_{$securityGroup->getName()}"; @@ -175,21 +183,11 @@ public function authenticate(Request $request): PassportInterface } } - $organizations = []; - if ($user->getOrganisation()) { - $organizations[] = $user->getOrganisation(); - } - - $organizations[] = 'localhostOrganization'; - $this->session->set('organizations', $organizations); - // If user has no organization, we default activeOrganization to an organization of a userGroup this user has and else the application organization; - $this->session->set('activeOrganization', $user->getOrganisation()); - $userArray = [ 'id' => $user->getId()->toString(), 'email' => $user->getEmail(), 'locale' => $user->getLocale(), - 'organization' => $user->getOrganisation()->getId()->toString(), + 'organization' => $user->getOrganization()->getId()->toString(), 'roles' => $roleArray['roles'], ]; diff --git a/api/src/Security/OIDCAuthenticator.php b/api/src/Security/OIDCAuthenticator.php index d09b7aeb3..c7a000716 100644 --- a/api/src/Security/OIDCAuthenticator.php +++ b/api/src/Security/OIDCAuthenticator.php @@ -2,9 +2,14 @@ namespace App\Security; +use App\Entity\SecurityGroup; +use App\Entity\User; +use App\Exception\GatewayException; use App\Security\User\AuthenticationUser; +use App\Service\ApplicationService; use App\Service\AuthenticationService; use Doctrine\ORM\EntityManagerInterface; +use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -26,12 +31,50 @@ class OIDCAuthenticator extends AbstractAuthenticator private EntityManagerInterface $entityManager; private ParameterBagInterface $parameterBag; - public function __construct(AuthenticationService $authenticationService, SessionInterface $session, EntityManagerInterface $entityManager, ParameterBagInterface $parameterBag) + /** + * @var LoggerInterface The logger for this service. + */ + private LoggerInterface $logger; + + /** + * @var \CommonGateway\CoreBundle\Service\AuthenticationService The new authenticationService + */ + private \CommonGateway\CoreBundle\Service\AuthenticationService $coreAuthenticationService; + + /** + * @var ApplicationService The application service + */ + private ApplicationService $applicationService; + + + /** + * Constructor + * + * @param AuthenticationService $authenticationService The authentication service + * @param SessionInterface $session The session interface + * @param EntityManagerInterface $entityManager The entity manager + * @param ParameterBagInterface $parameterBag The Parameter Bag + * @param LoggerInterface $callLogger The call logger + * @param \CommonGateway\CoreBundle\Service\AuthenticationService $coreAuthenticationService The new auth service + * @param ApplicationService $applicationService $the application service + */ + public function __construct( + AuthenticationService $authenticationService, + SessionInterface $session, + EntityManagerInterface $entityManager, + ParameterBagInterface $parameterBag, + LoggerInterface $callLogger, + \CommonGateway\CoreBundle\Service\AuthenticationService $coreAuthenticationService, + ApplicationService $applicationService + ) { $this->authenticationService = $authenticationService; $this->session = $session; $this->entityManager = $entityManager; $this->parameterBag = $parameterBag; + $this->logger = $callLogger; + $this->coreAuthenticationService = $coreAuthenticationService; + $this->applicationService = $applicationService; } public function supports(Request $request): ?bool @@ -59,19 +102,64 @@ public function authenticate(Request $request): PassportInterface $accessToken = $this->authenticationService->authenticate($method, $identifier, $code); $result = json_decode(base64_decode(explode('.', $accessToken['access_token'])[1]), true); - // Set default organization in session for multitenancy (see how this is done in other Authenticators, this can be different for each one!) - $defaultOrganization = $this->getDefaultOrganization(); - $organizations = [$defaultOrganization, 'localhostOrganization']; - $parentOrganizations[] = 'localhostOrganization'; - $this->session->set('organizations', $organizations); - $this->session->set('parentOrganizations', $parentOrganizations); - $this->session->set('activeOrganization', $defaultOrganization); - if (isset($accessToken['refresh_token'])) { - $this->session->set('refresh_token', $accessToken['refresh_token']); + $this->logger->notice('Received result from OIDC connector', ['authResult' => $accessToken]); + + // Make sure groups is always an array, even if there are no groups. + if (isset($result['groups']) !== false && (is_array($result['groups']) === false && $result['groups'] !== null)) { + $result['groups'] = [$result['groups']]; + } else if (isset($result['groups']) === false || is_array($result['groups']) === false) { + $result['groups'] = []; + if (isset($result['group']) === true && is_array($result['group']) === true) { + $result['groups'] = $result['group']; + } } +// if (isset($accessToken['refresh_token'])) { +// $this->session->set('refresh_token', $accessToken['refresh_token']); +// $userIdentifier = $result['email']; +// } else { + $doctrineUser = $this->entityManager->getRepository('App:User')->findOneBy(['email' => $result['email']]); + if($doctrineUser instanceof User === false) { + $doctrineUser = new User(); + } + $doctrineUser->setName($result['name'] ?? $result['sub']); + $doctrineUser->setEmail($result['email']); + $doctrineUser->setPassword(''); + $doctrineUser->addApplication($this->applicationService->getApplication()); + $doctrineUser->setOrganization($doctrineUser->getApplications()->first()->getOrganization()); + $this->session->set('organization', $doctrineUser->getApplications()->first()->getOrganization()); + + foreach ($result['groups'] as $group) { + $securityGroup = $this->entityManager->getRepository('App:SecurityGroup')->findOneBy(['name' => $group]); + if ($securityGroup instanceof SecurityGroup === true) { + $doctrineUser->addSecurityGroup($securityGroup); + } + } + + $this->entityManager->persist($doctrineUser); + $this->entityManager->flush(); + + $userIdentifier = $doctrineUser->getId()->toString(); + + if (empty($doctrineUser->getApplications()[0]->getPrivateKey()) === true) { + throw new GatewayException("Can't create a token because application doesn't have a PrivateKey." ?? null, 409, null, [ + 'data' => ['application_id' => $doctrineUser->getApplications()[0]->getId()->toString()], 'path' => '', 'responseType' => Response::HTTP_CONFLICT, + ]); + } + + // TODO: maybe do not just get the first Application here, but get application using ApplicationService->getApplication() and ... + // todo... if this returns an application check if the user is part of this application or one of the organizations of this application? + $token = $this->coreAuthenticationService->createJwtToken($doctrineUser->getApplications()[0]->getPrivateKey(), $this->coreAuthenticationService->serializeUser($doctrineUser, $this->session)); + + $doctrineUser->setJwtToken($token); + $this->session->set('jwtToken', $token); + + $this->entityManager->persist($doctrineUser); + $this->entityManager->flush(); +// } + return new Passport( - new UserBadge($result['email'], function ($userIdentifier) use ($result) { + new UserBadge($userIdentifier, function ($userIdentifier) use ($result) { return new AuthenticationUser( $userIdentifier, $result['email'], @@ -93,24 +181,6 @@ function ($credentials, $user) { ); } - private function getDefaultOrganization(): string - { - // Find application->organization - if ($this->session->get('application')) { - $application = $this->entityManager->getRepository('App:Application')->findOneBy(['id' => $this->session->get('application')]); - if (!empty($application) && $application->getOrganization()) { - return $application->getOrganization(); - } - } - // Else find and return 'the' default organization - $organization = $this->entityManager->getRepository('App:ObjectEntity')->findOneBy(['id' => 'a1c8e0b6-2f78-480d-a9fb-9792142f4761']); - if (!empty($organization) && $organization->getOrganization()) { - return $organization->getOrganization(); - } - - return 'http://api/admin/organizations/a1c8e0b6-2f78-480d-a9fb-9792142f4761'; - } - public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response { return new RedirectResponse($this->session->get('backUrl', $this->parameterBag->get('defaultBackUrl')) ?? $request->headers->get('referer') ?? $request->getSchemeAndHttpHost()); diff --git a/api/src/Security/TokenAuthenticator.php b/api/src/Security/TokenAuthenticator.php index 10b2d63ac..f12845b68 100644 --- a/api/src/Security/TokenAuthenticator.php +++ b/api/src/Security/TokenAuthenticator.php @@ -99,6 +99,7 @@ public function getPublicKey(string $token): string * @param string $token The token provided by the user * * @return array The payload of the token + * @throws GatewayException */ public function validateToken(string $token): array { @@ -139,9 +140,7 @@ private function prefixRoles(array $roles): array * * @param Request $request * - * @throws CacheException * @throws GatewayException - * @throws InvalidArgumentException * * @return PassportInterface */ diff --git a/api/src/Service/ApplicationService.php b/api/src/Service/ApplicationService.php index fe3a68349..6536ff90c 100644 --- a/api/src/Service/ApplicationService.php +++ b/api/src/Service/ApplicationService.php @@ -2,7 +2,9 @@ namespace App\Service; +use App\Entity\Application; use App\Exception\GatewayException; +use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; @@ -28,60 +30,64 @@ public function __construct( } /** - * A function that finds an application or creates one. + * A function that finds an application. * * @throws GatewayException */ - public function getApplication() + public function getApplication(): Application { - if ($application = $this->session->get('application')) { + // If application is already in the session + if ($this->session->has('application')) { $application = $this->entityManager->getRepository('App:Application')->findOneBy(['id' => $this->session->get('application')]); - if (!empty($application)) { + if ($application !== null) { return $application; } - } elseif ($this->session->get('apiKeyApplication')) { - // If an api-key is used for authentication we already know which application is used - return $this->entityManager->getRepository('App:Application')->findOneBy(['id' => $this->session->get('apiKeyApplication')]); } - // get publickey + // If an api-key is used for authentication we already know which application is used + if ($this->session->has('apiKeyApplication')) { + $application = $this->entityManager->getRepository('App:Application')->findOneBy(['id' => $this->session->get('apiKeyApplication')]); + if ($application !== null) { + $this->session->set('application', $application->getId()->toString()); + return $application; + } + } + + // Find application using the publicKey $public = ($this->request->headers->get('public') ?? $this->request->query->get('public')); + if (empty($public) === false) { + $application = $this->entityManager->getRepository('App:Application')->findOneBy(['public' => $public]); + if ($application !== null) { + $this->session->set('application', $application->getId()->toString()); + return $application; + } + } - // get host/domain + // Find application using the host/domain $host = ($this->request->headers->get('host') ?? $this->request->query->get('host')); + if (empty($host) === false) { + $applications = $this->entityManager->getRepository('App:Application')->findByDomain($host); + if (count($applications) > 0) { + $this->session->set('application', $applications[0]->getId()->toString()); - $application = $this->entityManager->getRepository('App:Application')->findOneBy(['public' => $public]) && !empty($application) && $this->session->set('application', $application->getId()->toString()); - - if (!$application) { - // @todo Create and use query in ApplicationRepository - $applications = $this->entityManager->getRepository('App:Application')->findAll(); - foreach ($applications as $app) { - $app->getDomains() !== null && in_array($host, $app->getDomains()) && $application = $app; - if (isset($application)) { - break; - } + return $applications[0]; } } - if (!$application) { - $this->session->set('application', null); - - // Set message - $public && $message = 'No application found with public '.$public; - $host && $message = 'No application found with host '.$host; - !$public && !$host && $message = 'No host or application given'; + // No application was found + $this->session->set('application', null); - // Set data - $public && $data = ['public' => $public]; - $host && $data = ['host' => $host]; - - throw new GatewayException($message ?? null, null, null, [ - 'data' => $data ?? null, 'path' => $public ?? $host ?? 'Header', 'responseType' => Response::HTTP_FORBIDDEN, - ]); - } + // Set message + $public && $message = 'No application found with public '.$public; + $host && $message = 'No application found with host '.$host; + !$public && !$host && $message = 'No host or application given'; - $this->session->set('application', $application->getId()->toString()); + // Set data + $public && $data = ['public' => $public]; + $host && $data = ['host' => $host]; - return $application; + throw new GatewayException($message ?? null, null, null, [ + 'data' => $data ?? null, 'path' => $public ?? $host ?? 'Header', 'responseType' => Response::HTTP_FORBIDDEN, + ]); } } diff --git a/api/src/Service/AuthorizationService.php b/api/src/Service/AuthorizationService.php index 8f9bd00b0..13add636a 100644 --- a/api/src/Service/AuthorizationService.php +++ b/api/src/Service/AuthorizationService.php @@ -231,8 +231,7 @@ public function getScopesForAnonymous(): array $item = $this->cache->getItem('anonymousScopes'); $itemOrg = $this->cache->getItem('anonymousOrg'); if ($item->isHit() && $itemOrg->isHit()) { - $this->session->set('organizations', [$itemOrg->get()]); - $this->session->set('activeOrganization', $itemOrg->get()); + $this->session->set('organization', $itemOrg->get()); return $item->get(); } @@ -244,8 +243,7 @@ public function getScopesForAnonymous(): array foreach ($groups[0]['scopes'] as $scope) { $scopes[] = strtolower($scope['code']); } - $this->session->set('organizations', [$groups[0]['organization']]); - $this->session->set('activeOrganization', $groups[0]['organization']); + $this->session->set('organization', $groups[0]['organization']); $itemOrg->set($groups[0]['organization']); $itemOrg->tag('anonymousOrg'); $this->cache->save($itemOrg); diff --git a/api/src/Service/EavService.php b/api/src/Service/EavService.php index 29a8b63db..c5e68a4a1 100644 --- a/api/src/Service/EavService.php +++ b/api/src/Service/EavService.php @@ -175,7 +175,7 @@ public function getObject(?string $id, string $method, Entity $entity) $object = new ObjectEntity(); $object->setEntity($entity); // if entity->function == 'organization', organization for this ObjectEntity will be changed later in handleMutation - $this->session->get('activeOrganization') ? $object->setOrganization($this->session->get('activeOrganization')) : $object->setOrganization('http://testdata-organization'); + $this->session->get('organization') ? $object->setOrganization($this->session->get('organization')) : $object->setOrganization('http://testdata-organization'); $application = $this->em->getRepository('App:Application')->findOneBy(['id' => $this->session->get('application')]); $object->setApplication(!empty($application) ? $application : null); @@ -401,15 +401,9 @@ public function generateResult(Request $request, Entity $entity, array $requestB } } - if (!$this->session->get('activeOrganization') && $this->session->get('application')) { + if (!$this->session->get('organization') && $this->session->get('application')) { $application = $this->em->getRepository('App:Application')->findOneBy(['id' => $this->session->get('application')]); - $this->session->set('activeOrganization', !empty($application) ? $application->getOrganization() : null); - } - if (!$this->session->get('organizations') && $this->session->get('activeOrganization')) { - $this->session->set('organizations', [$this->session->get('activeOrganization')]); - } - if (!$this->session->get('parentOrganizations')) { - $this->session->set('parentOrganizations', []); + $this->session->set('organization', !empty($application) ? $application->getOrganization() : null); } // Lets create an object @@ -420,19 +414,19 @@ public function generateResult(Request $request, Entity $entity, array $requestB $result = $object; $object = null; } // Lets check if the user is allowed to view/edit this resource. - elseif (!$this->objectEntityService->checkOwner($object)) { - // TODO: do we want to throw a different error if there are nog organizations in the session? (because of logging out for example) - if ($object->getOrganization() && !in_array($object->getOrganization(), $this->session->get('organizations') ?? [])) { - $object = null; // Needed so we return the error and not the object! - $responseType = Response::HTTP_FORBIDDEN; - $result = [ - 'message' => 'You are forbidden to view or edit this resource.', - 'type' => 'Forbidden', - 'path' => $entity->getName(), - 'data' => ['id' => $requestBase['id']], - ]; - } - } +// elseif (!$this->objectEntityService->checkOwner($object)) { +// // TODO: do we want to throw a different error if there are nog organizations in the session? (because of logging out for example) +// if ($object->getOrganization() && !in_array($object->getOrganization(), [])) { +// $object = null; // Needed so we return the error and not the object! +// $responseType = Response::HTTP_FORBIDDEN; +// $result = [ +// 'message' => 'You are forbidden to view or edit this resource.', +// 'type' => 'Forbidden', +// 'path' => $entity->getName(), +// 'data' => ['id' => $requestBase['id']], +// ]; +// } +// } } // Check for scopes, if forbidden to view/edit overwrite result so far to this forbidden error @@ -836,7 +830,7 @@ public function handleCollectionEndpoint(Request $request, array $info): array public function handleMutation(ObjectEntity $object, array $body, $fields, Request $request): array { // Check if session contains an activeOrganization, so we can't do calls without it. So we do not create objects with no organization! - if ($this->parameterBag->get('app_auth') && empty($this->session->get('activeOrganization'))) { + if ($this->parameterBag->get('app_auth') && empty($this->session->get('organization'))) { return [ 'message' => 'An active organization is required in the session, please login to create a new session.', 'type' => 'Forbidden', diff --git a/api/src/Service/EmailService.php b/api/src/Service/EmailService.php index ae5a38d7d..7625caabc 100644 --- a/api/src/Service/EmailService.php +++ b/api/src/Service/EmailService.php @@ -11,7 +11,7 @@ use Twig\Error\RuntimeError; use Twig\Error\SyntaxError; -// todo: move this to an email plugin with the following packages from composer.json: symfony/mailer, symfony/mailgun-mailer & symfony/http-client +// todo: move this to an email plugin with the following packages from composer.json: symfony/mailer, symfony/mailgun-mailer, symfony/sendinblue-mailer & symfony/http-client /** * @Author Wilco Louwerse , Ruben van der Linde , Sarai Misidjan @@ -71,8 +71,11 @@ private function sendEmail(): bool $variables = []; foreach ($this->configuration['variables'] as $key => $variable) { - if (array_key_exists($variable, $this->data['response'])) { + // Response is the default used for creating emails after an /api endpoint has been called and returned a response. + if (isset($this->data['response']) === true && array_key_exists($variable, $this->data['response'])) { $variables[$key] = $this->data['response'][$variable]; + } elseif (array_key_exists($variable, $this->data)) { + $variables[$key] = $this->data[$variable]; } } diff --git a/api/src/Service/FunctionService.php b/api/src/Service/FunctionService.php index fb16364e9..4ac569b10 100644 --- a/api/src/Service/FunctionService.php +++ b/api/src/Service/FunctionService.php @@ -15,6 +15,7 @@ * @license EUPL * * @category Service + * @deprecated */ class FunctionService { diff --git a/api/src/Service/LogService.php b/api/src/Service/LogService.php index a13f368f2..3420aab5e 100644 --- a/api/src/Service/LogService.php +++ b/api/src/Service/LogService.php @@ -18,6 +18,7 @@ * @license EUPL * * @category Service + * @deprecated moved removeUnreads function to coreBundle */ class LogService { @@ -49,6 +50,7 @@ public function __construct( * @param string $type * * @return Log + * @deprecated */ public function saveLog(Request $request, Response $response = null, int $stopWatchNumber = 0, string $content = null, bool $finalSave = null, string $type = 'in'): Log { @@ -173,6 +175,7 @@ public function saveLog(Request $request, Response $response = null, int $stopWa * @param Log $callLog * * @return void + * @deprecated moved to CoreBundle->ReadUnreadService->removeUnreads() */ private function removeUnreads(Log $callLog) { @@ -196,6 +199,10 @@ private function removeUnreads(Log $callLog) } } + /** + * @return Request + * @deprecated + */ public function makeRequest(): Request { return new Request( @@ -208,6 +215,11 @@ public function makeRequest(): Request ); } + /** + * @param int $statusCode + * @return string|null + * @deprecated + */ public function getStatusWithCode(int $statusCode): ?string { $reflectionClass = new ReflectionClass(Response::class); diff --git a/api/src/Service/ObjectEntityService.php b/api/src/Service/ObjectEntityService.php index 438ecb262..bc35f4468 100644 --- a/api/src/Service/ObjectEntityService.php +++ b/api/src/Service/ObjectEntityService.php @@ -45,8 +45,9 @@ * @license EUPL * * @category Service - * @deprecated TODO: This service still contains some logic used by the CoreBundle->RequestService for DateRead! (& CoreBundle->ObjectSyncSubscriber) + * @deprecated TODO: This service still contains some logic used by CoreBundle->ObjectEntitySubscriber (old CoreBundle->ObjectSyncSubscriber) * todo: this service is also used by the UserService for showing data when calling the /me endpoint. + * todo: and this service still contains some old logic for Files and Promises we might still need at some point? */ class ObjectEntityService { @@ -460,12 +461,12 @@ public function checkGetObject(?string $id, string $method, Entity $entity) throw new GatewayException($object['message'], null, null, ['data' => $object['data'], 'path' => $object['path'], 'responseType' => Response::HTTP_BAD_REQUEST]); } // Let's check if the user is allowed to view/edit this resource. - if (!$method == 'POST' && !$this->checkOwner($object)) { - // TODO: do we want to throw a different error if there are no organizations in the session? (because of logging out for example) - if ($object->getOrganization() && !in_array($object->getOrganization(), $this->session->get('organizations') ?? [])) { - throw new GatewayException('You are forbidden to view or edit this resource.', null, null, ['data' => ['id' => $id ?? null], 'path' => $entity->getName(), 'responseType' => Response::HTTP_FORBIDDEN]); - } - } +// if (!$method == 'POST' && !$this->checkOwner($object)) { +// // TODO: do we want to throw a different error if there are no organizations in the session? (because of logging out for example) +// if ($object->getOrganization() && !in_array($object->getOrganization(), [])) { +// throw new GatewayException('You are forbidden to view or edit this resource.', null, null, ['data' => ['id' => $id ?? null], 'path' => $entity->getName(), 'responseType' => Response::HTTP_FORBIDDEN]); +// } +// } if ($object instanceof ObjectEntity && $object->getId() !== null) { $this->session->set('object', $object->getId()->toString()); @@ -874,6 +875,7 @@ public function saveObject(ObjectEntity $objectEntity, array $post): ObjectEntit * @param ObjectEntity $objectEntity * * @return void + * @deprecated moved this function to CoreBundle->ReadUnreadService->setUnread() */ public function setUnread(ObjectEntity $objectEntity) { @@ -1043,7 +1045,7 @@ private function saveSubObject(ObjectEntity $subObject, $object): ObjectEntity $subObject->setOrganization($subObject->getSubresourceOf()->first()->getObjectEntity()->getOrganization()); $subObject->setApplication($subObject->getSubresourceOf()->first()->getObjectEntity()->getApplication()); } else { - $subObject->setOrganization($this->session->get('activeOrganization')); + $subObject->setOrganization($this->session->get('organization')); $application = $this->entityManager->getRepository('App:Application')->findOneBy(['id' => $this->session->get('application')]); $subObject->setApplication(!empty($application) ? $application : null); } @@ -2155,6 +2157,7 @@ function ($error) use ($objectEntity) { * @param string $keyValueSeparator * * @return string + * @deprecated */ public function implodeMultiArray(array $array, string $separator = ', ', string $keyValueSeparator = '='): string { diff --git a/api/src/Service/ResponseService.php b/api/src/Service/ResponseService.php index 10d837650..c088b6d51 100644 --- a/api/src/Service/ResponseService.php +++ b/api/src/Service/ResponseService.php @@ -26,7 +26,7 @@ * @license EUPL * * @category Service - * @deprecated TODO: This service still contains some logic used by the CoreBundle->RequestService for DateRead! + * @deprecated */ class ResponseService { @@ -87,6 +87,7 @@ public function createSelf(ObjectEntity $objectEntity): string * @param ObjectEntity $objectEntity * * @return DateTimeInterface|null + * @deprecated moved this function to CoreBundle->ReadUnreadService->getDateRead() */ private function getDateRead(ObjectEntity $objectEntity): ?DateTimeInterface { @@ -493,7 +494,7 @@ private function handleXCommongatewayMetadata(ObjectEntity $result, ?array $fiel * @param string|null $overwriteKey Default = null, if a string is given this will be used instead of $key, for the key to add to the $metadata array. * * @return void - * @deprecated TODO: This function is still used for dateRead! maybe move it to somewhere else? be careful with deleting this! + * @deprecated moved this function for dateRead only to CoreBundle->ReadUnreadService->addDateRead() */ public function addToMetadata(array &$metadata, string $key, $value, ?string $overwriteKey = null) { diff --git a/api/src/Service/SynchronizationService.php b/api/src/Service/SynchronizationService.php index a428482aa..b70a62ee0 100644 --- a/api/src/Service/SynchronizationService.php +++ b/api/src/Service/SynchronizationService.php @@ -5,7 +5,6 @@ use Adbar\Dot; use App\Entity\Application; use App\Entity\Entity; -use App\Entity\Gateway; use App\Entity\Gateway as Source; use App\Entity\ObjectEntity; use App\Entity\Synchronization; @@ -66,17 +65,8 @@ class SynchronizationService private EventDispatcherInterface $eventDispatcher; private Logger $logger; - /** - * Default user for synchronizations. - * Note that owner => reference is replaces with an uuid of that User object. - * - * @var array|string[] - */ - private array $synchronizationDefault = [ - 'owner' => 'https://docs.commongateway.nl/user/default.user.json', - ]; - private bool $asyncError = false; + private ?string $sha = null; /** * @param CallService $callService @@ -389,7 +379,7 @@ private function deleteSyncAndObject(Synchronization $synchronization): bool * * @param string $configKey The key to use when looking for an uuid of a Source in the Action->Configuration. * - * @return Gateway|null The found source for the configuration + * @return Source|null The found source for the configuration */ private function getSourceFromConfig(string $configKey = 'source'): ?Source { @@ -789,21 +779,6 @@ public function findSyncByObject(ObjectEntity $objectEntity, Source $source, Ent return $synchronization; } - /** - * Sets default owner on objectEntity. - * - * @return ObjectEntity $object - */ - private function setDefaultOwner(ObjectEntity $object): ObjectEntity - { - $defaultUser = $this->entityManager->getRepository('App:User')->findOneBy(['reference' => $this->synchronizationDefault['owner']]); - if ($defaultUser instanceof User === false) { - return $object; - } - - return $object->setOwner($defaultUser->getId()->toString()); - }//end setDefaultOwner() - /** * Adds a new ObjectEntity to a synchronization object. * @@ -811,14 +786,13 @@ private function setDefaultOwner(ObjectEntity $object): ObjectEntity * * @return string The method for populateObject, POST or PUT depending on if we created a new ObjectEntity. */ - private function checkObjectEntity(Synchronization $synchronization): string + public function checkObjectEntity(Synchronization $synchronization): string { if (!$synchronization->getObject()) { $object = new ObjectEntity(); - $object = $this->setDefaultOwner($object); $synchronization->getSourceId() && $object->setExternalId($synchronization->getSourceId()); $object->setEntity($synchronization->getEntity()); - $object = $this->setApplicationAndOrganization($object); + $object = $this->setApplication($object); $object->addSynchronization($synchronization); $this->entityManager->persist($object); if (isset($this->io)) { @@ -944,6 +918,29 @@ public function handleSync(Synchronization $synchronization, array $sourceObject return $synchronization; } + /** + * This function checks if the sha of $synchronization matches the given $sha. + * When $synchronization->getSha() doesn't match with the given $sha, the given $sha will be stored in the SynchronizationService. + * Always call the ->synchronize() function after this, because only then the stored $sha will be used to update $synchronization->setSha(). + * + * @param Synchronization $synchronization The Synchronization to check the sha of. + * @param string $sha The sha to check / compare. + * + * @return bool Returns True if sha matches, and false if it does not match. + */ + public function doesShaMatch(Synchronization $synchronization, string $sha): bool + { + $this->sha = null; + + if ($synchronization->getSha() === $sha) { + return true; + } + + $this->sha = $sha; + + return false; + } + /** * Executes the synchronization between source and gateway. * @@ -969,7 +966,6 @@ public function synchronize(Synchronization $synchronization, array $sourceObjec isset($this->io) && $this->io->text('creating new objectEntity'); $this->logger->info('creating new objectEntity'); $object = new ObjectEntity($synchronization->getEntity()); - $object = $this->setDefaultOwner($object); $object->addSynchronization($synchronization); $this->entityManager->persist($object); $this->entityManager->persist($synchronization); @@ -998,6 +994,9 @@ public function synchronize(Synchronization $synchronization, array $sourceObjec // Counter $counter = $synchronization->getTryCounter() + 1; + if ($counter > 10000) { + $counter = 10000; + } $synchronization->setTryCounter($counter); // Set dont try before, expensional so in minutes 1,8,27,64,125,216,343,512,729,1000 @@ -1013,6 +1012,13 @@ public function synchronize(Synchronization $synchronization, array $sourceObjec $sourceObject = $this->mappingService->mapping($synchronization->getMapping(), $sourceObject); } $synchronization->getObject()->hydrate($sourceObject, $unsafe); + + if ($this->sha !== null) { + $synchronization->setSha($this->sha); + $this->sha = null; + } + + $this->entityManager->persist($synchronization->getObject()); $this->entityManager->persist($synchronization); if ($oldDateModified !== $synchronization->getObject()->getDateModified()->getTimestamp()) { @@ -1074,18 +1080,17 @@ private function translate(array $sourceObject, bool $translateOut = false): arr /** * Sets an application and organization for new ObjectEntities. + * todo: setting organization is done by the new ObjectEntitySubscriber. We should set application through this way as well... * * @param ObjectEntity $objectEntity The ObjectEntity to update * * @return ObjectEntity The updated ObjectEntity */ - public function setApplicationAndOrganization(ObjectEntity $objectEntity): ObjectEntity + public function setApplication(ObjectEntity $objectEntity): ObjectEntity { - // todo move this to ObjectEntityService to prevent duplicate code $application = $this->entityManager->getRepository('App:Application')->findOneBy(['name' => 'main application']); if ($application instanceof Application) { $objectEntity->setApplication($application); - $objectEntity->setOrganization($application->getOrganization()); } elseif ( ($applications = $this->entityManager->getRepository('App:Application')->findAll() && !empty($applications) @@ -1093,7 +1098,6 @@ public function setApplicationAndOrganization(ObjectEntity $objectEntity): Objec && $application instanceof Application ) { $objectEntity->setApplication($application); - $objectEntity->setOrganization($application->getOrganization()); } return $objectEntity; @@ -1469,24 +1473,26 @@ public function aquireObject(string $url, Entity $entity): ?ObjectEntity $parse = \Safe\parse_url($url); $location = $parse['scheme'].'://'.$parse['host']; - // 2.c Try to establich a source for the domain + // 2.c Try to establish a source for the domain $source = $this->entityManager->getRepository('App:Gateway')->findOneBy(['location'=>$location]); // 2.b The source might be on a path e.g. /v1 so if whe cant find a source let try to cycle - foreach (explode('/', $parse['path']) as $pathPart) { - if ($pathPart !== '') { - $location = $location.'/'.$pathPart; - } - $source = $this->entityManager->getRepository('App:Gateway')->findOneBy(['location'=>$location]); - if ($source !== null) { - break; + if ($source instanceof Source === false && isset($parse['path']) === true) { + foreach (explode('/', $parse['path']) as $pathPart) { + if ($pathPart !== '') { + $location = $location.'/'.$pathPart; + } + $source = $this->entityManager->getRepository('App:Gateway')->findOneBy(['location'=>$location]); + if ($source !== null) { + break; + } } } - if ($source instanceof Gateway === false) { + if ($source instanceof Source === false) { return null; } - // 3 If we have a source we can establich an endpoint. + // 3 If we have a source we can establish an endpoint. $endpoint = str_replace($location, '', $url); // 4 Create sync diff --git a/api/src/Subscriber/ActionDoctrineSubscriber.php b/api/src/Subscriber/ActionDoctrineSubscriber.php deleted file mode 100644 index 87fc00c59..000000000 --- a/api/src/Subscriber/ActionDoctrineSubscriber.php +++ /dev/null @@ -1,85 +0,0 @@ -actionService = $actionService; - $this->entityManager = $entityManager; - } - - public static function getSubscribedEvents(): array - { - return [ - KernelEvents::VIEW => ['postLoad', EventPriorities::PRE_SERIALIZE], - ]; - } - - public function postLoad(ViewEvent $event) - { - $this->addActionHandlerConfig($event); - } - - private function addActionHandlerConfig(ViewEvent $event) - { - $route = $event->getRequest()->attributes->get('_route'); - - if ($route == 'api_action_handlers_get_collection' && $class = $event->getRequest()->get('class')) { - // get the actionHandler with the same class from the request - $actionHandlers = $this->actionService->getAllActionHandlers(); - foreach ($actionHandlers as $actionHandler) { - if ($class == $actionHandler->getClass()) { - $event->setControllerResult($actionHandler); - } - } - } elseif ($route == 'api_action_handlers_get_collection') { - // get all actionHandlers with a commongateway.action_handlers tag - $actionHandlers = $this->actionService->getAllActionHandlers(); - $event->setControllerResult($actionHandlers); - } - - if ($route == 'api_actions_get_collection') { - if ($event->getRequest()->query->count() > 0) { - $actions = $this->entityManager->getRepository('App:Action')->findBy($event->getRequest()->query->all()); - } else { - $actions = $this->entityManager->getRepository('App:Action')->findAll(); - } - - $response = []; - foreach ($actions as $action) { - $handler = $this->actionService->getHandlerForAction($action); - $config = $handler->getConfiguration(); - - $action = $action->setActionHandlerConfiguration($config); - $response[] = $action; - } - $event->setControllerResult($response); - } - - if ($route == 'api_actions_get_item') { - $actionId = $event->getRequest()->attributes->get('_route_params') ? $event->getRequest()->attributes->get('_route_params')['id'] : null; //The id of the resource - $action = $this->entityManager->getRepository('App:Action')->find($actionId); - - $handler = $this->actionService->getHandlerForAction($action); - $config = $handler->getConfiguration(); - - $action->setActionHandlerConfiguration($config); - - $event->setControllerResult($action); - } - } -} diff --git a/api/src/Subscriber/ActionSubscriber.php b/api/src/Subscriber/ActionSubscriber.php deleted file mode 100755 index 51ebe2122..000000000 --- a/api/src/Subscriber/ActionSubscriber.php +++ /dev/null @@ -1,353 +0,0 @@ - 'handleEvent', - 'commongateway.handler.post' => 'handleEvent', - 'commongateway.response.pre' => 'handleEvent', - 'commongateway.cronjob.trigger' => 'handleEvent', - 'commongateway.object.create' => 'handleEvent', - 'commongateway.object.read' => 'handleEvent', - 'commongateway.object.update' => 'handleEvent', - 'commongateway.object.delete' => 'handleEvent', - 'commongateway.action.event' => 'handleEvent', - - ]; - } - - public function __construct( - EntityManagerInterface $entityManager, - ContainerInterface $container, - ObjectEntityService $objectEntityService, - SessionInterface $session, - LoggerInterface $actionLogger, - MessageBusInterface $messageBus - ) { - $this->entityManager = $entityManager; - $this->container = $container; - $this->objectEntityService = $objectEntityService; - $this->session = $session; - $this->messageBus = $messageBus; - $this->logger = $actionLogger; - } - - public function runFunction(Action $action, array $data, string $currentThrow): array - { - // Is the action is lockable we need to lock it - if ($action->getIsLockable()) { - $action->setLocked(new DateTime()); - $this->entityManager->persist($action); - $this->entityManager->flush(); - if (isset($this->io)) { - $this->io->text("Locked Action {$action->getName()} at {$action->getLocked()->format('Y-m-d H:i:s')}"); - } - } - - $class = $action->getClass(); - $object = $this->container->get($class); - - // timer starten - $startTimer = microtime(true); - if (isset($this->io)) { - $this->io->text("Run ActionHandlerInterface \"{$action->getClass()}\""); - $this->io->newLine(); - } - - $actionRanGood = true; - - try { - $data = $object->run($data, array_merge($action->getConfiguration(), ['actionConditions' => $action->getConditions()])); - } catch (AsynchronousException $exception) { - //Do not stop the execution when the asynchronousError is thrown, but throw at the end - - // Something went wrong - $actionRanGood = false; - } - // timer stoppen - $stopTimer = microtime(true); - - // Is the action is lockable we need to unlock it - if ($action->getIsLockable()) { - $action->setLocked(null); - if (isset($this->io)) { - $now = new DateTime(); - $this->io->text("Unlocked Action {$action->getName()} at {$now->format('Y-m-d H:i:s')}"); - } - } - - $totalTime = $stopTimer - $startTimer; - - // Let's set some results - $action->setLastRun(new DateTime()); - $action->setLastRunTime($totalTime); - $action->setStatus($actionRanGood); - - $this->entityManager->persist($action); - $this->entityManager->flush(); - - $this->handleActionThrows($action, $data, $currentThrow); - - if (isset($exception)) { - throw $exception; - } - - return $data; - } - - public function handleAction(Action $action, ActionEvent $event): ActionEvent - { - // Lets see if the action prefents concurency - if ($action->getIsLockable()) { - // bijwerken uit de entity manger - $this->entityManager->refresh($action); - - if ($action->getLocked()) { - if (isset($this->io)) { - $this->io->info("Action {$action->getName()} is lockable and locked = {$action->getLocked()->format(DateTimeInterface::ISO8601)}"); - } - - return $event; - } - } - - if (JsonLogic::apply($action->getConditions(), $event->getData()) && $action->getIsEnabled() == true) { - $currentCronJobThrow = $this->handleActionIoStart($action, $event); - - if (!$action->getAsync()) { - try { - $event->setData($this->runFunction($action, $event->getData(), $currentCronJobThrow)); - } catch (AsynchronousException $exception) { - } - } else { - $data = $event->getData(); - unset($data['httpRequest']); - $this->messageBus->dispatch(new ActionMessage($action->getId(), $data, $currentCronJobThrow)); - } - - $this->handleActionIoFinish($action, $currentCronJobThrow); - - // throw events for this Action - } - - return $event; - } - - /** - * Throws Events for the Action if it has any Throws configured. - * - * @param Action $action - * @param ActionEvent $event - * @param bool $currentCronJobThrow - * - * @return void - */ - private function handleActionThrows(Action $action, array $data, bool $currentCronJobThrow) - { - if (isset($this->io)) { - $totalThrows = $action->getThrows() ? count($action->getThrows()) : 0; - $ioMessage = "Found $totalThrows Throw".($totalThrows !== 1 ? 's' : '').' for this Action.'; - $currentCronJobThrow ? $this->io->block($ioMessage) : $this->io->text($ioMessage); - if ($totalThrows !== 0) { - $extraDashesStr = $currentCronJobThrow ? '-' : ''; - $this->io->text("0/$totalThrows -$extraDashesStr Start looping through all Throws of this Action..."); - !$currentCronJobThrow ?: $this->io->newLine(); - } else { - $currentCronJobThrow ?: $this->io->newLine(); - } - } - foreach ($action->getThrows() as $key => $throw) { - // Throw event - $this->objectEntityService->dispatchEvent('commongateway.action.event', $data, $throw); - - if (isset($this->io) && isset($totalThrows) && isset($extraDashesStr)) { - if ($key !== array_key_last($action->getThrows())) { - $keyStr = $key + 1; - $this->io->text("$keyStr/$totalThrows -$extraDashesStr Looping through Throws of this Action \"{$action->getName()}\"..."); - !$currentCronJobThrow ?: $this->io->newLine(); - } - } - } - if (isset($this->io) && isset($totalThrows) && $totalThrows !== 0 && isset($extraDashesStr)) { - $this->io->text("$totalThrows/$totalThrows -$extraDashesStr Finished looping through all Throws of this Action \"{$action->getName()}\""); - $this->io->newLine(); - } - } - - /** - * If we got here through CronjobCommand, write user feedback to $this->io before handling an Action. - * - * @param Action $action - * @param ActionEvent $event - * - * @return bool - */ - private function handleActionIoStart(Action $action, ActionEvent $event): bool - { - $currentCronJobThrow = false; - if (isset($this->io) && - $this->session->get('currentCronJobThrow') && - $this->session->get('currentCronJobThrow') == $event->getType() && - $this->session->get('currentCronJobSubThrow') == $event->getSubType() - ) { - $currentCronJobThrow = true; - $this->io->block("Found an Action with matching conditions: [{$this->objectEntityService->implodeMultiArray($action->getConditions())}]"); - $this->io->definitionList( - 'The conditions of the following Action match with the ActionEvent data', - new TableSeparator(), - ['Id' => $action->getId()->toString()], - ['Name' => $action->getName()], - ['Description' => $action->getDescription()], - ['Listens' => implode(', ', $action->getListens())], - ['Throws' => implode(', ', $action->getThrows())], - ['Class' => $action->getClass()], - ['Priority' => $action->getPriority()], - ['Async' => is_null($action->getAsync()) ? null : ($action->getAsync() ? 'True' : 'False')], - ['IsLockable' => is_null($action->getIsLockable()) ? null : ($action->getIsLockable() ? 'True' : 'False')], - ['LastRun' => $action->getLastRun() ? $action->getLastRun()->format('Y-m-d H:i:s') : null], - ['LastRunTime' => $action->getLastRunTime()], - ['Status' => is_null($action->getStatus()) ? null : ($action->getStatus() ? 'True' : 'False')], - ); - $this->io->block("The configuration of this Action: [{$this->objectEntityService->implodeMultiArray($action->getConfiguration())}]"); - } elseif (isset($this->io)) { - $this->io->text("The conditions of the Action {$action->getName()} match with the 'sub'-ActionEvent data"); - } - - return $currentCronJobThrow; - } - - /** - * If we got here through CronjobCommand, write user feedback to $this->io after handling an Action. - * - * @param Action $action - * @param bool $currentCronJobThrow - * - * @return void - */ - private function handleActionIoFinish(Action $action, bool $currentCronJobThrow) - { - if (isset($this->io) && $currentCronJobThrow) { - $this->io->definitionList( - 'Finished handling the following Action that matched the ActionEvent data', - new TableSeparator(), - ['Id' => $action->getId()->toString()], - ['Name' => $action->getName()], - ['LastRun' => $action->getLastRun() ? $action->getLastRun()->format('Y-m-d H:i:s') : null], - ['LastRunTime' => $action->getLastRunTime()], - ['Status' => is_null($action->getStatus()) ? null : ($action->getStatus() ? 'True' : 'False')], - ); - } elseif (isset($this->io)) { - $this->io->text("Finished handling the Action {$action->getName()} that matched the 'sub'-ActionEvent data"); - } - } - - public function handleEvent(ActionEvent $event): ActionEvent - { - $currentCronJobThrow = $this->handleEventIo($event); - - // Normal behaviour is using the $event->getType(), but if $event->getSubType() is set, use that one instead. - $listeningToThrow = !$event->getSubType() ? $event->getType() : $event->getSubType(); - - $actions = $this->entityManager->getRepository('App:Action')->findByListens($listeningToThrow); - $totalActions = is_countable($actions) ? count($actions) : 0; - - $this->logger->info('Handling actions for event: '.$listeningToThrow.', found '.$totalActions.' listening actions'); - - $ioMessage = "Found $totalActions Action".($totalActions !== 1 ? 's' : '')." listening to \"$listeningToThrow\""; - if (isset($this->io)) { - $currentCronJobThrow ? $this->io->block($ioMessage) : $this->io->text($ioMessage); - if ($totalActions !== 0) { - $extraDashesStr = $currentCronJobThrow ? '--' : ''; - $this->io->text("0/$totalActions --$extraDashesStr Start looping through all Actions listening to \"$listeningToThrow\"..."); - !$currentCronJobThrow ?: $this->io->newLine(); - } else { - $currentCronJobThrow ?: $this->io->newLine(); - } - } - $this->logger->debug($ioMessage); - - foreach ($actions as $key => $action) { - // Handle Action - $this->session->set('action', $action->getId()->toString()); - $this->logger->debug('Handling action : '.$action->getName().'('.$action->getId().')'); - $this->handleAction($action, $event); - - if (isset($this->io) && isset($totalActions) && isset($extraDashesStr)) { - if ($key !== array_key_last($actions)) { - $keyStr = $key + 1; - $this->io->text("$keyStr/$totalActions --$extraDashesStr Looping through all Actions listening to \"$listeningToThrow\"..."); - $this->logger->debug("$keyStr/$totalActions -- Looping through all Actions listening to \"$listeningToThrow\"..."); - !$currentCronJobThrow ?: $this->io->newLine(); - } - } - $this->session->remove('action'); - } - - if (isset($this->io) && isset($totalActions) && $totalActions !== 0 && isset($extraDashesStr)) { - $this->io->text("$totalActions/$totalActions -- Finished looping all Actions listening to \"$listeningToThrow\""); - $this->io->newLine(); - } - $this->logger->info("$totalActions/$totalActions -- Finished looping all Actions listening to \"$listeningToThrow\""); - - return $event; - } - - /** - * If we got here through CronjobCommand, write user feedback to $this->io before handling Actions. - * - * @param ActionEvent $event - * - * @return bool currentCronJobThrow. True if the throw of the current Cronjob matches the type of the ActionEvent. - */ - private function handleEventIo(ActionEvent $event): bool - { - if ($this->session->get('io')) { - $this->io = $this->session->get('io'); - if ($this->session->get('currentCronJobThrow') && - $this->session->get('currentCronJobThrow') == $event->getType() && - $this->session->get('currentCronJobSubThrow') == $event->getSubType()) { - $this->io->section("Handle ActionEvent \"{$event->getType()}\"".($event->getSubType() ? " With SubType: \"{$event->getSubType()}\"" : '')); - $this->logger->info("Handle ActionEvent \"{$event->getType()}\"".($event->getSubType() ? " With SubType: \"{$event->getSubType()}\"" : '')); - - return true; - } else { - $this->io->text("Handle 'sub'-ActionEvent \"{$event->getType()}\"".($event->getSubType() ? " With SubType: \"{$event->getSubType()}\"" : '')); - $this->logger->info("Handle 'sub'-ActionEvent \"{$event->getType()}\"".($event->getSubType() ? " With SubType: \"{$event->getSubType()}\"" : '')); - } - } - - return false; - } -} diff --git a/api/src/Subscriber/AuditTrailSubscriber.php b/api/src/Subscriber/AuditTrailSubscriber.php deleted file mode 100644 index 9de6382a2..000000000 --- a/api/src/Subscriber/AuditTrailSubscriber.php +++ /dev/null @@ -1,250 +0,0 @@ -entityManager = $entityManager; - $this->logger = $valueSubscriberLogger; - $this->security = $security; - $this->parameterBag = $parameterBag; - $this->requestStack = $requestStack; - $this->cacheService = $cacheService; - }//end __construct() - - /** - * Defines the events that the subscriber should subscribe to. - * - * @return array The subscribed events - */ - public function getSubscribedEvents(): array - { - return [ - Events::postUpdate, - Events::postPersist, - Events::preRemove, - // Events::postLoad, - ]; - }//end getSubscribedEvents() - - /** - * Passes the result of prePersist to preUpdate. - * - * @param ObjectEntity $object - * - * @return array - */ - public function createAuditTrail(ObjectEntity $object, array $config): AuditTrail - { - $userId = null; - $user = null; - - if ($this->security->getUser() !== null) { - $userId = $this->security->getUser()->getUserIdentifier(); - $user = $this->entityManager->getRepository('App:User')->find($userId); - } - - $auditTrail = new AuditTrail(); - if ($object->getEntity() !== null - && $object->getEntity()->getCollections()->first() !== false - ) { - $auditTrail->setSource($object->getEntity()->getCollections()->first()->getPrefix()); - } - - $auditTrail->setApplicationId('Anonymous'); - $auditTrail->setApplicationView('Anonymous'); - $auditTrail->setUserId('Anonymous'); - $auditTrail->setUserView('Anonymous'); - - if ($user !== null) { - $auditTrail->setApplicationId($user->getApplications()->first()->getId()->toString()); - $auditTrail->setApplicationView($user->getApplications()->first()->getName()); - $auditTrail->setUserId($userId); - $auditTrail->setUserView($user->getName()); - } - $auditTrail->setAction($config['action']); - $auditTrail->setActionView($config['action']); - $auditTrail->setResult($config['result']); - $auditTrail->setResource($object->getId()->toString()); - $auditTrail->setResourceUrl($object->getUri()); - $auditTrail->setResourceView($object->getName()); - $auditTrail->setCreationDate(new \DateTime('now')); - - $this->entityManager->persist($auditTrail); - - return $auditTrail; - } - - /** - * Adds object resources from identifier. - * - * @param LifecycleEventArgs $args The lifecycle event arguments for this event - */ - public function postLoad(LifecycleEventArgs $args): void - { - $object = $args->getObject(); - if ($object instanceof ObjectEntity === false) { - return; - } elseif ($object->getEntity() === null || $object->getEntity()->getCreateAuditTrails() === false) { - return; - } - - $config = [ - 'action' => 'READ', - 'result' => 200, - ]; - - $auditTrail = $this->createAuditTrail($object, $config); - $this->entityManager->persist($auditTrail); - } - - /** - * Adds object resources from identifier. - * - * @param LifecycleEventArgs $args The lifecycle event arguments for this event - */ - public function postUpdate(LifecycleEventArgs $args): void - { - $object = $args->getObject(); - - if ($object instanceof ObjectEntity === false) { - return; - } elseif ($object->getEntity()->getCreateAuditTrails() === false) { - return; - } - - $config = [ - 'action' => 'UPDATE', - 'result' => 200, - ]; - - if ($this->requestStack->getMainRequest() !== null - && $this->requestStack->getMainRequest()->getMethod() === 'PATCH' - ) { - $config = [ - 'action' => 'PARTIAL_UPDATE', - 'result' => 200, - ]; - } - - $auditTrail = $this->createAuditTrail($object, $config); - - $auditTrail->setAmendments([ - 'new' => $object->toArray(), - 'old' => $this->cacheService->getObject($object->getId()), - ]); - $this->entityManager->persist($auditTrail); - $this->entityManager->flush(); - }//end preUpdate() - - /** - * Passes the result of prePersist to preUpdate. - * - * @param LifecycleEventArgs $args The lifecycle event arguments for this prePersist - */ - public function postPersist(LifecycleEventArgs $args): void - { - $object = $args->getObject(); - if ($object instanceof ObjectEntity === false) { - return; - } elseif ($object->getEntity()->getCreateAuditTrails() === false) { - return; - } - - $config = [ - 'action' => 'CREATE', - 'result' => 201, - ]; - - $auditTrail = $this->createAuditTrail($object, $config); - $auditTrail->setAmendments([ - 'new' => $object->toArray(), - 'old' => null, - ]); - - $this->entityManager->persist($auditTrail); - $this->entityManager->flush(); - }//end prePersist() - - public function preRemove(LifecycleEventArgs $args): void - { - $object = $args->getObject(); - if ($object instanceof ObjectEntity === false) { - return; - } elseif ($object->getEntity()->getCreateAuditTrails() === false) { - return; - } - - $config = [ - 'action' => 'DELETE', - 'result' => 204, - ]; - $auditTrail = $this->createAuditTrail($object, $config); - $auditTrail->setAmendments([ - 'new' => null, - 'old' => $object->toArray(), - ]); - - $this->entityManager->persist($auditTrail); - }//end preRemove() -}//end class diff --git a/api/src/Subscriber/CallIdSubscriber.php b/api/src/Subscriber/CallIdSubscriber.php deleted file mode 100644 index f3e716239..000000000 --- a/api/src/Subscriber/CallIdSubscriber.php +++ /dev/null @@ -1,34 +0,0 @@ -session = $session; - } - - // this method can only return the event names; you cannot define a - // custom method name to execute when each event triggers - public static function getSubscribedEvents() - { - return [ - KernelEvents::REQUEST => ['OnFirstEvent', EventPriorities::PRE_DESERIALIZE], - ]; - } - - public function OnFirstEvent(RequestEvent $event) - { - $this->session->set('callId', Uuid::uuid4()->toString()); - } -} diff --git a/api/src/Subscriber/CollectionEntitySubscriber.php b/api/src/Subscriber/CollectionEntitySubscriber.php deleted file mode 100644 index 8129ef2cb..000000000 --- a/api/src/Subscriber/CollectionEntitySubscriber.php +++ /dev/null @@ -1,82 +0,0 @@ -entityManager = $entityManager; - } - - public static function getSubscribedEvents() - { - return [ - KernelEvents::VIEW => ['request', EventPriorities::PRE_WRITE], - ]; - } - - /** - * @param ViewEvent $event - */ - public function request(ViewEvent $event) - { - $requestContent = json_decode($event->getRequest()->getContent()); - - if ( - $event->getRequest()->attributes->get('_route') !== 'api_collection_entities_put_item' || !isset($requestContent->prefix) - ) { - return; - } - - $oldObject = $event->getRequest()->get('previous_data'); - $collectionEntity = $this->entityManager->getRepository(CollectionEntity::class)->find($oldObject->getId()->toString()); - - // Replace endpoints paths - if (isset($collectionEntity)) { - foreach ($collectionEntity->getEndpoints() as $endpoint) { - $this->setNewPath($endpoint, $requestContent->prefix, $oldObject->getPrefix()); - } - $this->entityManager->flush(); - } - } - - /** - * Sets a new path and pathRegex for given Endpoint. - * - * @param Endpoint $endpoint This is the endpoint which path we update. - * @param string $newPrefix This is the new prefix that will be set. - * @param ?string $oldPrefix This is the old prefix which will be replaced by the new prefix. - */ - private function setNewPath(Endpoint $endpoint, string $newPrefix, ?string $oldPrefix): void - { - if ($oldPrefix && str_contains($endpoint->getPathRegex(), $oldPrefix)) { - // Remove old prefix with new - $endpoint->setPathRegex(str_replace($oldPrefix, $newPrefix, $endpoint->getPathRegex())); - $endpoint->setPath(str_replace($oldPrefix, $newPrefix, $endpoint->getPath())); - } else { - // Set prefix for first time - $newPath = $endpoint->getPath(); - $endpointPathRegex = $endpoint->getPathRegex(); - array_unshift($newPath, $newPrefix); - $endpoint->setPath($newPath); - - str_contains($endpointPathRegex, '^(') ? - $strToReplace = '^(' : - $strToReplace = '^'; - $newPrefix = $strToReplace.$newPrefix.'/'; - $endpoint->setPathRegex(str_replace($strToReplace, $newPrefix, $endpointPathRegex)); - } - $this->entityManager->persist($endpoint); - } -} diff --git a/api/src/Subscriber/DashboardCardDoctrineSubscriber.php b/api/src/Subscriber/DashboardCardDoctrineSubscriber.php deleted file mode 100644 index d80a76f04..000000000 --- a/api/src/Subscriber/DashboardCardDoctrineSubscriber.php +++ /dev/null @@ -1,72 +0,0 @@ -entityManager = $entityManager; - } - - public static function getSubscribedEvents(): array - { - return [ - KernelEvents::VIEW => ['postLoad', EventPriorities::PRE_SERIALIZE], - ]; - } - - public function postLoad(ViewEvent $event) - { - $this->updateDashboardCard($event); - } - - private function addObject(DashboardCard $dashboardCard): ?DashboardCard - { - if (!$entity = $dashboardCard->getEntity()) { - return null; - } - - if (strpos($entity, 'App\\Entity')) { - $entity = 'App:'.$entity; - } - - $object = $this->entityManager->find($entity, $dashboardCard->getEntityId()); - - return $dashboardCard->setObject($object); - } - - private function updateDashboardCard(ViewEvent $event) - { - $route = $event->getRequest()->attributes->get('_route'); - - if ($route == 'api_dashboard_cards_get_collection') { - $dashboardCards = $this->entityManager->getRepository('App:DashboardCard')->findAll(); - - $response = []; - foreach ($dashboardCards as $dashboardCard) { - $dashboardCard = $this->addObject($dashboardCard); - $response[] = $dashboardCard; - } - $event->setControllerResult($response); - } - - if ($route == 'api_dashboard_cards_get_item') { - $objectId = $event->getRequest()->attributes->get('_route_params') ? $event->getRequest()->attributes->get('_route_params')['id'] : null; //The id of the resource - - $dashboardCard = $this->entityManager->getRepository('App:DashboardCard')->find($objectId); - $dashboardCard = $this->addObject($dashboardCard); - $event->setControllerResult($dashboardCard); - } - } -} diff --git a/api/src/Subscriber/DatabaseActivitySubscriber.php b/api/src/Subscriber/DatabaseActivitySubscriber.php deleted file mode 100644 index 538820d6f..000000000 --- a/api/src/Subscriber/DatabaseActivitySubscriber.php +++ /dev/null @@ -1,99 +0,0 @@ -eavService = $eavService; - $this->gatewayService = $gatewayService; - $this->commonGroundService = $commonGroundService; - $this->cache = $cache; - } - - // this method can only return the event names; you cannot define a - // custom method name to execute when each event triggers - public function getSubscribedEvents(): array - { - return [ - Events::postLoad, - Events::postRemove, - ]; - } - - // callback methods must be called exactly like the events they listen to; - // they receive an argument of type LifecycleEventArgs, which gives you access - // to both the entity object of the event and the entity manager itself - public function postLoad(LifecycleEventArgs $args): void - { - /* @todo hotfix */ - // $this->logActivity('load', $args); - } - - public function postRemove(LifecycleEventArgs $args): void - { - /* @todo hotfix */ - // $this->logActivity('remove', $args); - } - - private function logActivity(string $action, LifecycleEventArgs $args): void - { - $objectEntity = $args->getObject(); - - // if this subscriber only applies to certain entity types, - // add some code to check the entity type as early as possible - if (!$objectEntity instanceof ObjectEntity || !$objectEntity->getEntity() || !$objectEntity->getEntity()->getSource() || !$objectEntity->getUri()) { - return; - } - - if ($action == 'load') { - $item = $this->cache->getItem('commonground_'.base64_encode($objectEntity->getUri())); - // lets try to hit the cach - if ($item->isHit()) { - $objectEntity->setExternalResult($item->get()); - } else { - /* @todo figure out how to this promise style */ - $component = $this->gatewayService->sourceToArray($objectEntity->getEntity()->getSource()); - $result = $this->commonGroundService->callService($component, $objectEntity->getUri(), ''); - $result = json_decode($result->getBody()->getContents(), true); - $objectEntity->setExternalResult($result); - $item->set($result); - //$item->expiresAt(new \DateTime('tomorrow')); - $this->cache->save($item); - } - } elseif ($action == 'remove') { - /* @todo we should check is an entity is not ussed elswhere before removing it */ - $component = $this->gatewayService->sourceToArray($objectEntity->getEntity()->getSource()); - /* @todo we need to do some abstraction to log these calls, something like an callWrapper at the eav service */ - $result = $this->commonGroundService->callService($component, $objectEntity->getUri(), '', [], [], false, 'DELETE'); - // Lets see if we need to clear the cache - $item = $this->cache->getItem('commonground_'.base64_encode($objectEntity->getUri())); - if ($item->isHit()) { - $this->cache->delete('commonground_'.base64_encode($objectEntity->getUri())); - } - } else { - /* @todo throe execption */ - } - } -} diff --git a/api/src/Subscriber/EavSyncSubscriber.php b/api/src/Subscriber/EavSyncSubscriber.php deleted file mode 100644 index 54b86bb9c..000000000 --- a/api/src/Subscriber/EavSyncSubscriber.php +++ /dev/null @@ -1,90 +0,0 @@ -entityManager = $entityManager; - $this->synchronizationService = $synchronizationService; - } - - public static function getSubscribedEvents() - { - return [ - KernelEvents::REQUEST => ['eavsync', EventPriorities::PRE_DESERIALIZE], - ]; - } - - public function eavsync(RequestEvent $event): void - { - $route = $event->getRequest()->attributes->get('_route'); - - if ( - $route !== 'api_object_entities_create_sync_collection' - ) { - return; - } - - // Grap the id's - $objectId = $event->getRequest()->attributes->get('id'); - $sourceId = $event->getRequest()->attributes->get('sourceId'); - - // Grap the objects for the ids - $objectEntity = $this->entityManager->getRepository('App:ObjectEntity')->findOneBy(['id'=>$objectId]); - $source = $this->entityManager->getRepository('App:Gateway')->findOneBy(['id'=>$sourceId]); - - $sourceId = $event->getRequest()->query->get('externalId', ''); - $endpoint = $event->getRequest()->query->get('endpoint', null); - $actionId = $event->getRequest()->query->get('action', null); - // Get a sync objcet - - $status = 202; - if (!$synchronization = $this->entityManager->getRepository('App:Synchronization')->findOneBy(['object' => $objectEntity->getId(), 'gateway' => $source])) { - $synchronization = new Synchronization($source); - $synchronization->setObject($objectEntity); - $synchronization->setSourceId($sourceId); - $synchronization->setEndpoint($endpoint); - if ($actionId) { - $action = $this->entityManager->getRepository('App:Action')->findOneBy(['id'=>$actionId]); - $synchronization->setAction($action); - } - - $status = 201; - // Lets do the practical stuff - // (isset($event->getRequest()->query->get('endpoint', false))? '': ''); - } -// - $synchronization = $this->synchronizationService->handleSync($synchronization); - - $this->entityManager->persist($synchronization); - $this->entityManager->flush(); - - $event->setResponse( - new Response( - json_encode([ - 'id' => $synchronization->getId(), - 'sourceLastChanged'=> $synchronization->getSourceLastChanged(), - 'lastChecked' => $synchronization->getLastChecked(), - 'lastSynced' => $synchronization->getLastSynced(), - 'dateCreated' => $synchronization->getDateCreated(), - 'dateModified' => $synchronization->getDateModified(), - ]), - $status, - ) - ); - } -} diff --git a/api/src/Subscriber/EntityDeleteObjectsSubscriber.php b/api/src/Subscriber/EntityDeleteObjectsSubscriber.php deleted file mode 100644 index ed2945fce..000000000 --- a/api/src/Subscriber/EntityDeleteObjectsSubscriber.php +++ /dev/null @@ -1,72 +0,0 @@ -entityManager = $entityManager; - $this->eavService = $eavService; - } - - public static function getSubscribedEvents() - { - return [ - KernelEvents::REQUEST => ['entityDeleteObjects', EventPriorities::PRE_DESERIALIZE], - ]; - } - - /** - * This function returns the schema of an objectEntity or entity. - * - * @param RequestEvent $event The event object - */ - public function entityDeleteObjects(RequestEvent $event) - { - $route = $event->getRequest()->attributes->get('_route'); - - // Only trigger when we want to. - if ( - $route !== 'api_entities_delete_objects_collection' - ) { - return; - } - - // Let's see if we have the proper info on our route. - $entity = $this->entityManager->getRepository('App:Entity')->find($event->getRequest()->attributes->get('id')); - $method = $event->getRequest()->getMethod(); - if ($entity instanceof Entity === false || $method !== 'POST') { - return; - } - - $result = $this->eavService->deleteAllObjects($entity); - - $event->setResponse(new Response(json_encode(['Amount of objects deleted' => $result]), 200, [])); - }//end entityDeleteObjects() -} diff --git a/api/src/Subscriber/EntityToSchemaSubscriber.php b/api/src/Subscriber/EntityToSchemaSubscriber.php deleted file mode 100644 index 9cda7c1ee..000000000 --- a/api/src/Subscriber/EntityToSchemaSubscriber.php +++ /dev/null @@ -1,70 +0,0 @@ -entityManager = $entityManager; - } - - public static function getSubscribedEvents() - { - return [ - KernelEvents::REQUEST => ['toSchema', EventPriorities::PRE_DESERIALIZE], - ]; - } - - /** - * This function returns the schema of an objectEntity or entity. - * - * @param RequestEvent $event The event object - * - * @throws GatewayException - */ - public function toSchema(RequestEvent $event) - { - $request = $event->getRequest(); - - // Let not do anything if a schema is not requested - if ($request->headers->get('Accept') !== 'application/json+schema' && $request->headers->get('Accept') !== 'application/schema+json') { - return; - } - - // @todo Get endpoint with prefix does not work -// $objectType = $request->attributes->get('_route_params') ? $request->attributes->get('_route_params')['_api_resource_class'] : null; //The class of the requested entity - $objectId = $request->attributes->get('_route_params') ? $request->attributes->get('_route_params')['id'] : null; //The id of the resource - - if (!$objectId) { - throw new GatewayException('Cannot give a schema if no entity is given'); - } - - if ($objectEntity = $this->entityManager->getRepository('App:ObjectEntity')->find($objectId)) { - $schema = $objectEntity->getEntity()->toSchema($objectEntity); - if (isset($schema['required']) === false) { - $schema['required'] = []; - } - $event->setResponse(new Response(json_encode($schema), Response::HTTP_OK, ['content-type' => 'application/json+schema'])); - } - - if ($entity = $this->entityManager->getRepository('App:Entity')->find($objectId)) { - $schema = $entity->toSchema(null); - if (isset($schema['required']) === false) { - $schema['required'] = []; - } - $event->setResponse(new Response(json_encode($schema), Response::HTTP_OK, ['content-type' => 'application/json+schema'])); - } - } -} diff --git a/api/src/Subscriber/GatewayItemSubscriber.php b/api/src/Subscriber/GatewayItemSubscriber.php deleted file mode 100644 index 945562224..000000000 --- a/api/src/Subscriber/GatewayItemSubscriber.php +++ /dev/null @@ -1,62 +0,0 @@ -gatewayService = $gatewayService; - } - - /** - * @return array[] - */ - public static function getSubscribedEvents(): array - { - return [ - KernelEvents::REQUEST => ['gateway', EventPriorities::PRE_DESERIALIZE], - ]; - } - - /** - * @param RequestEvent $event - * - * @throws Exception - */ - public function gateway(RequestEvent $event) - { - if (!$event->isMainRequest()) { - return; - } - $route = $event->getRequest()->attributes->get('_route'); - - if ( - $route !== 'api_gateways_gateway_get_item' && - $route !== 'api_gateways_gateway_put_item' && - $route !== 'api_gateways_gateway_delete_item' - ) { - return; - } - - $response = $this->gatewayService->processSource( - $event->getRequest()->attributes->get('name'), - $event->getRequest()->attributes->get('endpoint'), - $event->getRequest()->getMethod(), - $event->getRequest()->getContent(), - $event->getRequest()->query->all(), - $event->getRequest()->headers->all(), - ); - - $event->setResponse($response); - } -} diff --git a/api/src/Subscriber/GatewaySubscriber.php b/api/src/Subscriber/GatewaySubscriber.php deleted file mode 100644 index c36e19b9b..000000000 --- a/api/src/Subscriber/GatewaySubscriber.php +++ /dev/null @@ -1,67 +0,0 @@ -gatewayService = $gatewayService; - } - - public static function getSubscribedEvents() - { - return [ - KernelEvents::VIEW => ['gateway', EventPriorities::PRE_VALIDATE], - ]; - } - - /** - * @param ViewEvent $event - */ - public function gateway(ViewEvent $event) - { - $route = $event->getRequest()->attributes->get('_route'); - - $response = new Response(); - - if ( - $route !== 'api_gateways_gateway_post_collection' - ) { - return; - } - - $response = $this->gatewayService->processSource( - $event->getRequest()->attributes->get('name'), - $event->getRequest()->attributes->get('endpoint'), - $event->getRequest()->getMethod(), - $event->getRequest()->getContent(), - $event->getRequest()->query->all(), - $event->getRequest()->headers->all(), - ); - - // Lets see if we need to render a file - // @todo dit is echt but lellijke code - if (strpos($event->getRequest()->attributes->get('name'), '.') && $renderType = explode('.', $event->getRequest()->attributes->get('name'))) { - $path = $renderType[0]; - $renderType = end($renderType); - } elseif (strpos($event->getRequest()->attributes->get('endpoint'), '.') && $renderType = explode('.', $event->getRequest()->attributes->get('endpoint'))) { - $path = $renderType[0]; - $renderType = end($renderType); - } - if (isset($renderType)) { - $response = $this->gatewayService->retrieveExport($response, $renderType, $path); - } - - $event->setResponse($response); - } -} diff --git a/api/src/Subscriber/ProxySubscriber.php b/api/src/Subscriber/ProxySubscriber.php deleted file mode 100644 index e07647be8..000000000 --- a/api/src/Subscriber/ProxySubscriber.php +++ /dev/null @@ -1,163 +0,0 @@ -entityManager = $entityManager; - $this->callService = $callService; - $this->fileSystemService = $fileSystemService; - $this->requestService = $requestService; - $this->serializer = $serializer; - } - - /** - * Get Subscribed Events. - * - * @return array[] Subscribed Events - */ - public static function getSubscribedEvents() - { - return [ - KernelEvents::REQUEST => ['proxy', EventPriorities::PRE_DESERIALIZE], - ]; - } - - /** - * Handle Proxy. - * - * @param RequestEvent $event The Event - * - * @throws GuzzleException|UrlException - * - * @return void - */ - public function proxy(RequestEvent $event): void - { - $route = $event->getRequest()->attributes->get('_route'); - - if (!in_array($route, self::PROXY_ROUTES)) { - return; - } - - //@Todo rename - $source = $this->entityManager->getRepository('App:Gateway')->find($event->getRequest()->attributes->get('id')); - if (!$source instanceof Source) { - return; - } - - $headers = array_merge_recursive($source->getHeaders(), $event->getRequest()->headers->all()); - $endpoint = $headers['x-endpoint'][0] ?? ''; - if (empty($endpoint) === false && str_starts_with($endpoint, '/') === false && str_ends_with($source->getLocation(), '/') === false) { - $endpoint = '/'.$endpoint; - } - $endpoint = rtrim($endpoint, '/'); - - $method = $headers['x-method'][0] ?? $event->getRequest()->getMethod(); - unset($headers['authorization']); - unset($headers['x-endpoint']); - unset($headers['x-method']); - - $url = \Safe\parse_url($source->getLocation()); - - try { - if ($url['scheme'] === 'http' || $url['scheme'] === 'https') { - $result = $this->callService->call( - $source, - $endpoint, - $method, - [ - 'headers' => $headers, - 'query' => $this->requestService->realRequestQueryAll($event->getRequest()->getQueryString()), - 'body' => $event->getRequest()->getContent(), - ] - ); - } else { - $result = $this->fileSystemService->call($source, $endpoint); - $result = new \GuzzleHttp\Psr7\Response(200, [], $this->serializer->serialize($result, 'json')); - } - } catch (ServerException|ClientException|RequestException $exception) { - $statusCode = $exception->getCode() ?? 500; - if (method_exists(get_class($exception), 'getResponse') === true && $exception->getResponse() !== null) { - $body = $exception->getResponse()->getBody()->getContents(); - $statusCode = $exception->getResponse()->getStatusCode(); - $headers = $exception->getResponse()->getHeaders(); - } - $content = $this->serializer->serialize( - [ - 'Message' => $exception->getMessage(), - 'Body' => $body ?? "Can\'t get a response & body for this type of Exception: ".get_class($exception), - ], - 'json' - ); - - $result = new \GuzzleHttp\Psr7\Response($statusCode, $headers, $content); - - // If error catched dont pass event->getHeaders (causes infinite loop) - $wentWrong = true; - } - $event->setResponse(new Response($result->getBody()->getContents(), $result->getStatusCode(), !isset($wentWrong) ? $result->getHeaders() : [])); - } -} diff --git a/api/src/Subscriber/ResponseSubscriber.php b/api/src/Subscriber/ResponseSubscriber.php deleted file mode 100644 index b710f42c9..000000000 --- a/api/src/Subscriber/ResponseSubscriber.php +++ /dev/null @@ -1,58 +0,0 @@ -entityManager = $entityManager; - $this->session = $session; - }//end __construct() - - /** - * @return array - */ - public static function getSubscribedEvents(): array - { - return [ - KernelEvents::RESPONSE => ['request'], - ]; - }//end getSubscribedEvents() - - /** - * @param ResponseEvent $event The Responce Event - */ - public function request(ResponseEvent $event) - { - $response = $event->getResponse(); - - // Set multiple headers simultaneously - $response->headers->add([ - 'Access-Control-Allow-Credentials' => 'true', - 'Process-ID' => $this->session->get('process'), - ]); - - $response->headers->remove('Access-Control-Allow-Origin'); - }//end request() -}//end class diff --git a/api/src/Subscriber/TranslationTablesSubscriber.php b/api/src/Subscriber/TranslationTablesSubscriber.php deleted file mode 100644 index 468dd464c..000000000 --- a/api/src/Subscriber/TranslationTablesSubscriber.php +++ /dev/null @@ -1,48 +0,0 @@ -entityManager = $entityManager; - } - - public static function getSubscribedEvents() - { - return [ - KernelEvents::VIEW => ['translationTablesNames', EventPriorities::PRE_SERIALIZE], - ]; - } - - /** - * @param ViewEvent $event - */ - public function translationTablesNames(ViewEvent $event) - { - $route = $event->getRequest()->attributes->get('_route'); - $path = $event->getRequest()->getPathInfo(); - - if ($route === 'api_translations_get_table_names_collection' && $path === '/admin/table_names') { - $translationsRepo = $this->entityManager->getRepository('App:Translation'); - - $event->setResponse(new Response(json_encode([ - 'results' => $translationsRepo->getTables(), - ]), 200, ['Content-type' => 'application/json'])); - } - - if ($route !== 'api_translations_get_collection') { - return false; - } - } -} diff --git a/api/src/Subscriber/UserSubscriber.php b/api/src/Subscriber/UserSubscriber.php deleted file mode 100644 index 53bc21ccd..000000000 --- a/api/src/Subscriber/UserSubscriber.php +++ /dev/null @@ -1,80 +0,0 @@ -hasher = $hasher; - }//end __construct() - - /** - * this method can only return the event names; you cannot define a custom method name to execute when each event triggers. - * - * @return array The events to listen to - */ - public function getSubscribedEvents(): array - { - return [ - Events::prePersist, - Events::preUpdate, - ]; - }//end getSubscribedEvents() - - /** - * Callback methods must be called exactly like the events they listen to; - * they receive an argument of type LifecycleEventArgs, - * which gives you access to both the entity object of the event and the entity manager itself. - * - * @param LifecycleEventArgs $args The lifecycleEvents to listen to - */ - public function prePersist(LifecycleEventArgs $args): void - { - $this->hashPassword($args); - }//end prePersist() - - /** - * Callback methods must be called exactly like the events they listen to; - * they receive an argument of type LifecycleEventArgs, - * which gives you access to both the entity object of the event and the entity manager itself. - * - * @param LifecycleEventArgs $args The lifecycleEvents to listen to - */ - public function preUpdate(LifecycleEventArgs $args): void - { - $this->hashPassword($args); - }//end preUpdate() - - /** - * Hashes an unhashed password before writing a user to the database. - * - * @param LifecycleEventArgs $args The arguments containing the user to update - */ - private function hashPassword(LifecycleEventArgs $args): void - { - $object = $args->getObject(); - if ($object instanceof User === true - && password_get_info($object->getPassword())['algoName'] === 'unknown') { - $hash = $this->hasher->hashPassword($object, $object->getPassword()); - $object->setPassword($hash); - } - }//end hashPassword() -}//end class diff --git a/api/src/Subscriber/ValueDatabaseSubscriber.php b/api/src/Subscriber/ValueDatabaseSubscriber.php deleted file mode 100644 index a7a76dc91..000000000 --- a/api/src/Subscriber/ValueDatabaseSubscriber.php +++ /dev/null @@ -1,58 +0,0 @@ -twig = $twig; - $this->entityManager = $entityManager; - $this->param = $param; - } - - // this method can only return the event names; you cannot define a - // custom method name to execute when each event triggers - public function getSubscribedEvents(): array - { - return [ - Events::postPersist, - Events::postUpdate, - ]; - } - - public function postUpdate(LifecycleEventArgs $args) - { - $value = $args->getObject(); - if ($value instanceof Value && $value->getStringValue()) { - if ($value->getObjectEntity()->getUri() === null) { - $value->getObjectEntity()->setUri(rtrim($this->param->get('app_url'), '/').$value->getObjectEntity()->getSelf()); - } - $value->setStringValue($this->twig->createTemplate($value->getStringValue())->render(['object' => $value->getObjectEntity()])); - } - } - - public function postPersist(LifecycleEventArgs $args) - { - $this->postUpdate($args); - } -} diff --git a/api/src/Subscriber/ValueSubscriber.php b/api/src/Subscriber/ValueSubscriber.php deleted file mode 100644 index 74ecf130d..000000000 --- a/api/src/Subscriber/ValueSubscriber.php +++ /dev/null @@ -1,208 +0,0 @@ -entityManager = $entityManager; - $this->logger = $valueSubscriberLogger; - $this->synchronizationService = $synchronizationService; - $this->parameterBag = $parameterBag; - }//end __construct() - - /** - * Defines the events that the subscriber should subscribe to. - * - * @return array The subscribed events - */ - public function getSubscribedEvents(): array - { - return [ - Events::preUpdate, - Events::prePersist, - Events::preRemove, - ]; - }//end getSubscribedEvents() - - /** - * Gets a subobject by uuid. - * - * @param string $uuid The id of the subobject - * @param Value $valueObject The valueObject to add the subobject to - * - * @return ObjectEntity|null The found subobject - */ - public function getSubObjectById(string $uuid, Value $valueObject): ?ObjectEntity - { - $parentObject = $valueObject->getObjectEntity(); - if (!$subObject = $this->entityManager->find(ObjectEntity::class, $uuid)) { - try { - $subObject = $this->entityManager->getRepository(ObjectEntity::class)->findByAnyId($uuid); - } catch (NonUniqueResultException $exception) { - $this->logger->error("Found more than one ObjectEntity with uuid = '$uuid' or with a synchronization with sourceId = '$uuid'"); - - return null; - } - } - - if (!$subObject instanceof ObjectEntity) { - $this->logger->error( - "No subObjectEntity found with uuid ($uuid) or with a synchronization with sourceId = uuid for ParentObject", - [ - 'uuid' => $uuid, - 'ParentObject' => [ - 'id' => $parentObject->getId()->toString(), - 'entity' => $parentObject->getEntity() ? [ - 'id' => $parentObject->getEntity()->getId()->toString(), - 'name' => $parentObject->getEntity()->getName(), - ] : null, - '_self' => $parentObject->getSelf(), - 'name' => $parentObject->getName(), - ], - ] - ); - - return null; - }//end if - - return $subObject; - }//end getSubObjectById() - - /** - * Gets a subobject by url. - * - * @param string $url The url of the subobject - * @param Value $valueObject The value object to add the subobject to - * - * @return ObjectEntity|null The resulting subobject - */ - public function getSubObjectByUrl(string $url, Value $valueObject): ?ObjectEntity - { - // First check if the object is already being synced. - foreach ($this->entityManager->getUnitOfWork()->getScheduledEntityInsertions() as $insertion) { - if ($insertion instanceof Synchronization === true && $insertion->getSourceId() === $url) { - return $insertion->getObject(); - } - } - - // Then check if the url is internal. - $self = str_replace(rtrim($this->parameterBag->get('app_url'), '/'), '', $url); - $objectEntity = $this->entityManager->getRepository('App:ObjectEntity')->findOneBy(['self' => $self]); - if ($objectEntity !== null) { - return $objectEntity; - } - - // Finally, if we really don't have the object, get it from the source. - $synchronization = $this->entityManager->getRepository('App:Synchronization')->findOneBy(['sourceId' => $url]); - if ($synchronization instanceof Synchronization === true) { - return $synchronization->getObject(); - } - - return $this->synchronizationService->aquireObject($url, $valueObject->getAttribute()->getObject()); - }//end getSubObjectByUrl() - - /** - * Finds subobjects by identifiers. - * - * @param string $identifier The identifier to find the object for - * @param Value $valueObject The value object to add objects to - * - * @return ObjectEntity|null The found object - */ - public function findSubobject(string $identifier, Value $valueObject): ?ObjectEntity - { - if (Uuid::isValid($identifier)) { - return $this->getSubObjectById($identifier, $valueObject); - } elseif (filter_var($identifier, FILTER_VALIDATE_URL)) { - return $this->getSubObjectByUrl($identifier, $valueObject); - } - - return null; - }//end findSubObject() - - /** - * Adds object resources from identifier. - * - * @param LifecycleEventArgs $value The lifecycle event arguments for this event - */ - public function preUpdate(LifecycleEventArgs $value): void - { - $valueObject = $value->getObject(); - - if ($valueObject instanceof Value && $valueObject->getAttribute()->getType() == 'object') { - if ($valueObject->getArrayValue()) { - foreach ($valueObject->getArrayValue() as $identifier) { - $subobject = $this->findSubobject($identifier, $valueObject); - if ($subobject !== null) { - $valueObject->addObject($subobject); - } - } - $valueObject->setArrayValue([]); - } elseif ((Uuid::isValid($valueObject->getStringValue()) || filter_var($valueObject->getStringValue(), FILTER_VALIDATE_URL)) && $identifier = $valueObject->getStringValue()) { - foreach ($valueObject->getObjects() as $object) { - $valueObject->removeObject($object); - } - $subobject = $this->findSubobject($identifier, $valueObject); - if ($subobject !== null) { - $valueObject->addObject($subobject); - } - } - $valueObject->getObjectEntity()->setDateModified(new \DateTime()); - } - }//end preUpdate() - - /** - * Passes the result of prePersist to preUpdate. - * - * @param LifecycleEventArgs $args The lifecycle event arguments for this prePersist - */ - public function prePersist(LifecycleEventArgs $args): void - { - $this->preUpdate($args); - }//end prePersist() - - public function preRemove(LifecycleEventArgs $args): void - { - }//end preRemove() -}//end class diff --git a/api/symfony.lock b/api/symfony.lock index 0664d2039..d20a5abee 100644 --- a/api/symfony.lock +++ b/api/symfony.lock @@ -785,6 +785,15 @@ "symfony/security-http": { "version": "v5.3.6" }, + "symfony/sendinblue-mailer": { + "version": "5.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "5.2", + "ref": "ae1cf494ce06b9a4578a8445c610402a1676ee8d" + } + }, "symfony/serializer": { "version": "v5.3.4" }, diff --git a/api/templates/emails/gateway/new-log-e-mail.html.twig b/api/templates/emails/gateway/new-log-e-mail.html.twig new file mode 100644 index 000000000..672102e69 --- /dev/null +++ b/api/templates/emails/gateway/new-log-e-mail.html.twig @@ -0,0 +1,503 @@ +{# todo: move this to an email plugin (see EmailService.php) #} + + + + + + {{ subject }} + + + + + + + + + + + + + + + diff --git a/docker-compose-dex.yml b/docker-compose-dex.yml index 93090cb9c..82b8a58dd 100644 --- a/docker-compose-dex.yml +++ b/docker-compose-dex.yml @@ -8,14 +8,6 @@ x-cache: - ${CONTAINER_REGISTRY_BASE}/${CONTAINER_PROJECT_NAME}-cron services: - # gateway-frontend: - # &gateway-frontend - # image: ghcr.io/conductionnl/commonground-gateway-frontend:latest - # depends_on: - # - php - # ports: - # - "83:80" - cron: image: ${CONTAINER_REGISTRY_BASE}/${CONTAINER_PROJECT_NAME}_cron:${APP_ENV} build: ./dockerized-cron @@ -45,6 +37,7 @@ services: - ./api/var/certs:/var/certs:rw,cached - ./gateway:/srv/api/fixtures:rw,cached environment: + - APP_INIT='true' - CONTAINER_REGISTRY_BASE=${CONTAINER_REGISTRY_BASE} - CONTAINER_PROJECT_NAME=${CONTAINER_PROJECT_NAME} - DATABASE_URL=postgres://api-platform:!ChangeMe!@db/api?serverVersion=10.1 @@ -106,6 +99,7 @@ services: - CRON_RUNNER_ENABLED=${CRON_RUNNER_ENABLED} - CRON_RUNNER_CRONTAB=${CRON_RUNNER_CRONTAB} - CRON_RUNNER_CONCURRENCY_POLICY=${CRON_RUNNER_CONCURRENCY_POLICY} + - LOG_LEVEL=${LOG_LEVEL} ports: - "82:80" cap_drop: @@ -170,13 +164,14 @@ services: - "5432:5432" mongodb: - image: mongo + image: mongo:4.4.14 restart: always environment: MONGO_INITDB_ROOT_USERNAME: api-platform MONGO_INITDB_ROOT_PASSWORD: '!ChangeMe!' ports: - "27017:27017" + dex: image: dexidp/dex:latest ports: @@ -204,12 +199,12 @@ services: ports: - 389:389 - 636:636 - + rabbitmq: - image: rabbitmq:latest + image: rabbitmq:3.12.0 environment: - - RABBITMQ_DEFAULT_USER=${RABBITMQ_USERNAME} - - RABBITMQ_DEFAULT_PASS=${RABBITMQ_PASSWORD} + - RABBITMQ_DEFAULT_USER=${RABBITMQ_USERNAME} + - RABBITMQ_DEFAULT_PASS=${RABBITMQ_PASSWORD} networks: diff --git a/docker-compose.yml b/docker-compose.yml index 25830e5b3..251ffd3ae 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -107,6 +107,7 @@ services: - CRON_RUNNER_ENABLED=${CRON_RUNNER_ENABLED} - CRON_RUNNER_CRONTAB=${CRON_RUNNER_CRONTAB} - CRON_RUNNER_CONCURRENCY_POLICY=${CRON_RUNNER_CONCURRENCY_POLICY} + - LOG_LEVEL=${LOG_LEVEL} ports: - "82:80" cap_drop: @@ -171,7 +172,7 @@ services: - "5432:5432" mongodb: - image: mongo + image: mongo:4.4.14 restart: always environment: MONGO_INITDB_ROOT_USERNAME: api-platform @@ -180,7 +181,7 @@ services: - "27017:27017" rabbitmq: - image: rabbitmq:latest + image: rabbitmq:3.12.0 environment: - RABBITMQ_DEFAULT_USER=${RABBITMQ_USERNAME} - RABBITMQ_DEFAULT_PASS=${RABBITMQ_PASSWORD}