diff --git a/.env.dist b/.env.dist index f847f27..e029635 100644 --- a/.env.dist +++ b/.env.dist @@ -13,5 +13,6 @@ APP_SECRET=xxx # Format described at http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url # For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db" # Configure your db driver and server_version in config/packages/doctrine.yaml -DATABASE_URL=sqlite:////home/magnetico/.local/share/magneticod/database.sqlite3 +APP_DATABASE_URL=postgres://$PGUSER:$PGPASSWORD@127.0.0.1:5436/test?application_name=magnetico_web +MAGNETICOD_DATABASE_URL=sqlite:///%kernel.project_dir%/tests/database/database.sqlite3 ###< doctrine/doctrine-bundle ### diff --git a/composer.json b/composer.json index ac01ec4..7860ff0 100644 --- a/composer.json +++ b/composer.json @@ -18,13 +18,16 @@ "sensio/framework-extra-bundle": "^5.1", "symfony/console": "^4.1", "symfony/flex": "^1.0", + "symfony/form": "^4.1", "symfony/framework-bundle": "^4.1", "symfony/lts": "^4@dev", "symfony/monolog-bundle": "^3.3", "symfony/orm-pack": "^1.0", + "symfony/security-bundle": "^4.1", "symfony/serializer-pack": "^1.0", "symfony/translation": "^4.1", "symfony/twig-bundle": "^4.1", + "symfony/validator": "^4.1", "symfony/web-server-bundle": "^4.1", "symfony/yaml": "^4.1", "white-october/pagerfanta-bundle": "^1.2" diff --git a/composer.lock b/composer.lock index 58e44f8..d38298a 100644 --- a/composer.lock +++ b/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": "16f2a34d0add203515fe657d6dcde23d", + "content-hash": "0b2aebb419be98d3475cfe76e436f2b0", "packages": [ { "name": "doctrine/annotations", @@ -1657,16 +1657,16 @@ }, { "name": "symfony/cache", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "4986efce97c002e58380e8c0474acbf72eda9339" + "reference": "be95ef3665747e6ff9d883a8adc87085769009f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/4986efce97c002e58380e8c0474acbf72eda9339", - "reference": "4986efce97c002e58380e8c0474acbf72eda9339", + "url": "https://api.github.com/repos/symfony/cache/zipball/be95ef3665747e6ff9d883a8adc87085769009f0", + "reference": "be95ef3665747e6ff9d883a8adc87085769009f0", "shasum": "" }, "require": { @@ -1722,20 +1722,20 @@ "caching", "psr6" ], - "time": "2018-05-16T14:33:22+00:00" + "time": "2018-06-22T08:59:39+00:00" }, { "name": "symfony/config", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "5ceefc256caecc3e25147c4e5b933de71d0020c4" + "reference": "e57e7b573df9d0eaa8c0152768c708ee7ea2b8e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/5ceefc256caecc3e25147c4e5b933de71d0020c4", - "reference": "5ceefc256caecc3e25147c4e5b933de71d0020c4", + "url": "https://api.github.com/repos/symfony/config/zipball/e57e7b573df9d0eaa8c0152768c708ee7ea2b8e5", + "reference": "e57e7b573df9d0eaa8c0152768c708ee7ea2b8e5", "shasum": "" }, "require": { @@ -1785,20 +1785,20 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2018-05-16T14:33:22+00:00" + "time": "2018-06-20T11:15:17+00:00" }, { "name": "symfony/console", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "2d5d973bf9933d46802b01010bd25c800c87c242" + "reference": "70591cda56b4b47c55776ac78e157c4bb6c8b43f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/2d5d973bf9933d46802b01010bd25c800c87c242", - "reference": "2d5d973bf9933d46802b01010bd25c800c87c242", + "url": "https://api.github.com/repos/symfony/console/zipball/70591cda56b4b47c55776ac78e157c4bb6c8b43f", + "reference": "70591cda56b4b47c55776ac78e157c4bb6c8b43f", "shasum": "" }, "require": { @@ -1853,20 +1853,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-05-30T07:26:09+00:00" + "time": "2018-05-31T10:17:53+00:00" }, { "name": "symfony/debug", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "449f8b00b28ab6e6912c3e6b920406143b27193b" + "reference": "dbe0fad88046a755dcf9379f2964c61a02f5ae3d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/449f8b00b28ab6e6912c3e6b920406143b27193b", - "reference": "449f8b00b28ab6e6912c3e6b920406143b27193b", + "url": "https://api.github.com/repos/symfony/debug/zipball/dbe0fad88046a755dcf9379f2964c61a02f5ae3d", + "reference": "dbe0fad88046a755dcf9379f2964c61a02f5ae3d", "shasum": "" }, "require": { @@ -1909,20 +1909,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2018-05-16T14:33:22+00:00" + "time": "2018-06-08T09:39:36+00:00" }, { "name": "symfony/dependency-injection", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "f2a3f0dc640a28b8aedd51b47ad6e6c5cebb3c00" + "reference": "e761828a85d7dfc00b927f94ccbe1851ce0b6535" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f2a3f0dc640a28b8aedd51b47ad6e6c5cebb3c00", - "reference": "f2a3f0dc640a28b8aedd51b47ad6e6c5cebb3c00", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e761828a85d7dfc00b927f94ccbe1851ce0b6535", + "reference": "e761828a85d7dfc00b927f94ccbe1851ce0b6535", "shasum": "" }, "require": { @@ -1930,7 +1930,7 @@ "psr/container": "^1.0" }, "conflict": { - "symfony/config": "<4.1", + "symfony/config": "<4.1.1", "symfony/finder": "<3.4", "symfony/proxy-manager-bridge": "<3.4", "symfony/yaml": "<3.4" @@ -1980,24 +1980,24 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2018-05-25T14:55:38+00:00" + "time": "2018-06-25T11:12:43+00:00" }, { "name": "symfony/doctrine-bridge", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-bridge.git", - "reference": "9d361867451d5397e46bb32056fa43921545676e" + "reference": "a7751cc8d949c16366976633678116f85662b989" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/9d361867451d5397e46bb32056fa43921545676e", - "reference": "9d361867451d5397e46bb32056fa43921545676e", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/a7751cc8d949c16366976633678116f85662b989", + "reference": "a7751cc8d949c16366976633678116f85662b989", "shasum": "" }, "require": { - "doctrine/common": "~2.4", + "doctrine/common": "~2.4@stable", "php": "^7.1.3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0" @@ -2060,11 +2060,11 @@ ], "description": "Symfony Doctrine Bridge", "homepage": "https://symfony.com", - "time": "2018-05-16T14:41:07+00:00" + "time": "2018-06-25T11:31:22+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", @@ -2127,7 +2127,7 @@ }, { "name": "symfony/filesystem", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", @@ -2177,16 +2177,16 @@ }, { "name": "symfony/finder", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "087e2ee0d74464a4c6baac4e90417db7477dc238" + "reference": "84714b8417d19e4ba02ea78a41a975b3efaafddb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/087e2ee0d74464a4c6baac4e90417db7477dc238", - "reference": "087e2ee0d74464a4c6baac4e90417db7477dc238", + "url": "https://api.github.com/repos/symfony/finder/zipball/84714b8417d19e4ba02ea78a41a975b3efaafddb", + "reference": "84714b8417d19e4ba02ea78a41a975b3efaafddb", "shasum": "" }, "require": { @@ -2222,7 +2222,7 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-05-16T14:33:22+00:00" + "time": "2018-06-19T21:38:16+00:00" }, { "name": "symfony/flex", @@ -2271,17 +2271,98 @@ "time": "2018-05-02T19:08:56+00:00" }, { - "name": "symfony/framework-bundle", - "version": "v4.1.0", + "name": "symfony/form", + "version": "v4.1.1", "source": { "type": "git", - "url": "https://github.com/symfony/framework-bundle.git", - "reference": "e93974e78872d22cceebf401ce230363b192268e" + "url": "https://github.com/symfony/form.git", + "reference": "cf9ed8b1a17b34d52c458352cb0c29838ee5f825" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/e93974e78872d22cceebf401ce230363b192268e", - "reference": "e93974e78872d22cceebf401ce230363b192268e", + "url": "https://api.github.com/repos/symfony/form/zipball/cf9ed8b1a17b34d52c458352cb0c29838ee5f825", + "reference": "cf9ed8b1a17b34d52c458352cb0c29838ee5f825", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/intl": "~3.4|~4.0", + "symfony/options-resolver": "~3.4|~4.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0", + "symfony/property-access": "~3.4|~4.0" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/dependency-injection": "<3.4", + "symfony/doctrine-bridge": "<3.4", + "symfony/framework-bundle": "<3.4", + "symfony/http-kernel": "<3.4", + "symfony/twig-bridge": "<3.4.5|<4.0.5,>=4.0" + }, + "require-dev": { + "doctrine/collections": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/console": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/http-foundation": "~3.4|~4.0", + "symfony/http-kernel": "~3.4|~4.0", + "symfony/security-csrf": "~3.4|~4.0", + "symfony/translation": "~3.4|~4.0", + "symfony/validator": "~3.4|~4.0", + "symfony/var-dumper": "~3.4|~4.0" + }, + "suggest": { + "symfony/framework-bundle": "For templating with PHP.", + "symfony/security-csrf": "For protecting forms against CSRF attacks.", + "symfony/twig-bridge": "For templating with Twig.", + "symfony/validator": "For form validation." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Form\\": "" + }, + "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 Form Component", + "homepage": "https://symfony.com", + "time": "2018-06-22T08:59:39+00:00" + }, + { + "name": "symfony/framework-bundle", + "version": "v4.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/framework-bundle.git", + "reference": "a34630e9712b23fb0a20cc12fe937a9ddcaacbe8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/a34630e9712b23fb0a20cc12fe937a9ddcaacbe8", + "reference": "a34630e9712b23fb0a20cc12fe937a9ddcaacbe8", "shasum": "" }, "require": { @@ -2289,7 +2370,7 @@ "php": "^7.1.3", "symfony/cache": "~3.4|~4.0", "symfony/config": "~3.4|~4.0", - "symfony/dependency-injection": "^4.1", + "symfony/dependency-injection": "^4.1.1", "symfony/event-dispatcher": "^4.1", "symfony/filesystem": "~3.4|~4.0", "symfony/finder": "~3.4|~4.0", @@ -2325,7 +2406,7 @@ "symfony/expression-language": "~3.4|~4.0", "symfony/form": "^4.1", "symfony/lock": "~3.4|~4.0", - "symfony/messenger": "^4.1-beta2", + "symfony/messenger": "^4.1", "symfony/polyfill-intl-icu": "~1.0", "symfony/process": "~3.4|~4.0", "symfony/property-info": "~3.4|~4.0", @@ -2383,20 +2464,20 @@ ], "description": "Symfony FrameworkBundle", "homepage": "https://symfony.com", - "time": "2018-05-30T09:26:42+00:00" + "time": "2018-06-20T21:41:56+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "a916c88390fb861ee21f12a92b107d51bb68af99" + "reference": "4f9c7cf962e635b0b26b14500ac046e07dbef7f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/a916c88390fb861ee21f12a92b107d51bb68af99", - "reference": "a916c88390fb861ee21f12a92b107d51bb68af99", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/4f9c7cf962e635b0b26b14500ac046e07dbef7f3", + "reference": "4f9c7cf962e635b0b26b14500ac046e07dbef7f3", "shasum": "" }, "require": { @@ -2437,20 +2518,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2018-05-25T14:55:38+00:00" + "time": "2018-06-19T21:38:16+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "b5ab9d4cdbfd369083744b6b5dfbf454e31e5f90" + "reference": "29c094a1c4f8209b7e033f612cbbd69029e38955" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b5ab9d4cdbfd369083744b6b5dfbf454e31e5f90", - "reference": "b5ab9d4cdbfd369083744b6b5dfbf454e31e5f90", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/29c094a1c4f8209b7e033f612cbbd69029e38955", + "reference": "29c094a1c4f8209b7e033f612cbbd69029e38955", "shasum": "" }, "require": { @@ -2458,13 +2539,13 @@ "psr/log": "~1.0", "symfony/debug": "~3.4|~4.0", "symfony/event-dispatcher": "~4.1", - "symfony/http-foundation": "~4.1", + "symfony/http-foundation": "^4.1.1", "symfony/polyfill-ctype": "~1.8" }, "conflict": { "symfony/config": "<3.4", "symfony/dependency-injection": "<4.1", - "symfony/var-dumper": "<4.1", + "symfony/var-dumper": "<4.1.1", "twig/twig": "<1.34|<2.4,>=2" }, "provide": { @@ -2485,7 +2566,7 @@ "symfony/stopwatch": "~3.4|~4.0", "symfony/templating": "~3.4|~4.0", "symfony/translation": "~3.4|~4.0", - "symfony/var-dumper": "~4.1" + "symfony/var-dumper": "^4.1.1" }, "suggest": { "symfony/browser-kit": "", @@ -2524,11 +2605,11 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2018-05-30T12:52:34+00:00" + "time": "2018-06-25T13:06:45+00:00" }, { "name": "symfony/inflector", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/inflector.git", @@ -2584,6 +2665,81 @@ ], "time": "2018-05-01T23:02:13+00:00" }, + { + "name": "symfony/intl", + "version": "v4.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/intl.git", + "reference": "cb21b901892c0d637f9c2f50860ed2fe29ce4b55" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/intl/zipball/cb21b901892c0d637f9c2f50860ed2fe29ce4b55", + "reference": "cb21b901892c0d637f9c2f50860ed2fe29ce4b55", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-intl-icu": "~1.0" + }, + "require-dev": { + "symfony/filesystem": "~3.4|~4.0" + }, + "suggest": { + "ext-intl": "to use the component with locales other than \"en\"" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Intl\\": "" + }, + "classmap": [ + "Resources/stubs" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Eriksen Costa", + "email": "eriksen.costa@infranology.com.br" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A PHP replacement layer for the C intl extension that includes additional data from the ICU library.", + "homepage": "https://symfony.com", + "keywords": [ + "i18n", + "icu", + "internationalization", + "intl", + "l10n", + "localization" + ], + "time": "2018-06-25T11:12:43+00:00" + }, { "name": "symfony/lts", "version": "dev-master", @@ -2679,16 +2835,16 @@ }, { "name": "symfony/monolog-bridge", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/monolog-bridge.git", - "reference": "c0fe8631641b370f926c9dc54f9cac154a9e503b" + "reference": "0a7cac4e6f2eb72428367893d6d2fc2883b4aeba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/c0fe8631641b370f926c9dc54f9cac154a9e503b", - "reference": "c0fe8631641b370f926c9dc54f9cac154a9e503b", + "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/0a7cac4e6f2eb72428367893d6d2fc2883b4aeba", + "reference": "0a7cac4e6f2eb72428367893d6d2fc2883b4aeba", "shasum": "" }, "require": { @@ -2741,7 +2897,7 @@ ], "description": "Symfony Monolog Bridge", "homepage": "https://symfony.com", - "time": "2018-05-16T14:33:22+00:00" + "time": "2018-05-31T10:17:53+00:00" }, { "name": "symfony/monolog-bundle", @@ -2806,6 +2962,60 @@ ], "time": "2018-06-04T05:55:43+00:00" }, + { + "name": "symfony/options-resolver", + "version": "v4.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "45cdcc8a96ef92b43a50723e6d1f5f83096e8cef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/45cdcc8a96ef92b43a50723e6d1f5f83096e8cef", + "reference": "45cdcc8a96ef92b43a50723e6d1f5f83096e8cef", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "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 OptionsResolver Component", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "time": "2018-05-31T10:17:53+00:00" + }, { "name": "symfony/orm-pack", "version": "v1.0.5", @@ -2834,6 +3044,64 @@ "description": "A pack for the Doctrine ORM", "time": "2017-12-12T01:47:50+00:00" }, + { + "name": "symfony/polyfill-intl-icu", + "version": "v1.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-icu.git", + "reference": "80ee17ae83c10cd513e5144f91a73607a21edb4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/80ee17ae83c10cd513e5144f91a73607a21edb4e", + "reference": "80ee17ae83c10cd513e5144f91a73607a21edb4e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/intl": "~2.3|~3.0|~4.0" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's ICU-related data and classes", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "icu", + "intl", + "polyfill", + "portable", + "shim" + ], + "time": "2018-04-25T14:53:50+00:00" + }, { "name": "symfony/polyfill-mbstring", "version": "v1.8.0", @@ -2895,16 +3163,16 @@ }, { "name": "symfony/process", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "73445bd33b0d337c060eef9652b94df72b6b3434" + "reference": "1d1677391ecf00d1c5b9482d6050c0c27aa3ac3a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/73445bd33b0d337c060eef9652b94df72b6b3434", - "reference": "73445bd33b0d337c060eef9652b94df72b6b3434", + "url": "https://api.github.com/repos/symfony/process/zipball/1d1677391ecf00d1c5b9482d6050c0c27aa3ac3a", + "reference": "1d1677391ecf00d1c5b9482d6050c0c27aa3ac3a", "shasum": "" }, "require": { @@ -2940,11 +3208,11 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-05-30T07:26:09+00:00" + "time": "2018-05-31T10:17:53+00:00" }, { "name": "symfony/property-access", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", @@ -3011,7 +3279,7 @@ }, { "name": "symfony/property-info", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", @@ -3087,16 +3355,16 @@ }, { "name": "symfony/routing", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "180b51c66d10f09e562c9ebc395b39aacb2cf8a2" + "reference": "b38b9797327b26ea2e4146a40e6e2dc9820a6932" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/180b51c66d10f09e562c9ebc395b39aacb2cf8a2", - "reference": "180b51c66d10f09e562c9ebc395b39aacb2cf8a2", + "url": "https://api.github.com/repos/symfony/routing/zipball/b38b9797327b26ea2e4146a40e6e2dc9820a6932", + "reference": "b38b9797327b26ea2e4146a40e6e2dc9820a6932", "shasum": "" }, "require": { @@ -3109,7 +3377,6 @@ }, "require-dev": { "doctrine/annotations": "~1.0", - "doctrine/common": "~2.2", "psr/log": "~1.0", "symfony/config": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0", @@ -3161,20 +3428,177 @@ "uri", "url" ], - "time": "2018-05-30T07:26:09+00:00" + "time": "2018-06-19T21:38:16+00:00" }, { - "name": "symfony/serializer", - "version": "v4.1.0", + "name": "symfony/security", + "version": "v4.1.1", "source": { "type": "git", - "url": "https://github.com/symfony/serializer.git", - "reference": "db427d70438645789ffce6048d61b3992118a33a" + "url": "https://github.com/symfony/security.git", + "reference": "fa46e38ff4dea2d3949630efd33ed73e2ac0850a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/db427d70438645789ffce6048d61b3992118a33a", - "reference": "db427d70438645789ffce6048d61b3992118a33a", + "url": "https://api.github.com/repos/symfony/security/zipball/fa46e38ff4dea2d3949630efd33ed73e2ac0850a", + "reference": "fa46e38ff4dea2d3949630efd33ed73e2ac0850a", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/http-foundation": "~3.4|~4.0", + "symfony/http-kernel": "~3.4|~4.0", + "symfony/property-access": "~3.4|~4.0" + }, + "replace": { + "symfony/security-core": "self.version", + "symfony/security-csrf": "self.version", + "symfony/security-guard": "self.version", + "symfony/security-http": "self.version" + }, + "require-dev": { + "psr/container": "^1.0", + "psr/log": "~1.0", + "symfony/expression-language": "~3.4|~4.0", + "symfony/finder": "~3.4|~4.0", + "symfony/ldap": "~3.4|~4.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/routing": "~3.4|~4.0", + "symfony/validator": "~3.4|~4.0" + }, + "suggest": { + "psr/container-implementation": "To instantiate the Security class", + "symfony/expression-language": "For using the expression voter", + "symfony/form": "", + "symfony/ldap": "For using the LDAP user and authentication providers", + "symfony/routing": "For using the HttpUtils class to create sub-requests, redirect the user, and match URLs", + "symfony/validator": "For using the user password constraint" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Security\\": "" + }, + "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 Security Component", + "homepage": "https://symfony.com", + "time": "2018-06-22T08:59:39+00:00" + }, + { + "name": "symfony/security-bundle", + "version": "v4.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/security-bundle.git", + "reference": "58c0db1915ab9c54c013d9336cace46f9e02cbb2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/security-bundle/zipball/58c0db1915ab9c54c013d9336cace46f9e02cbb2", + "reference": "58c0db1915ab9c54c013d9336cace46f9e02cbb2", + "shasum": "" + }, + "require": { + "ext-xml": "*", + "php": "^7.1.3", + "symfony/dependency-injection": "^3.4.3|^4.0.3", + "symfony/http-kernel": "^4.1", + "symfony/security": "^4.1.1" + }, + "conflict": { + "symfony/console": "<3.4", + "symfony/event-dispatcher": "<3.4", + "symfony/framework-bundle": "<4.1.1", + "symfony/var-dumper": "<3.4" + }, + "require-dev": { + "doctrine/doctrine-bundle": "~1.5", + "symfony/asset": "~3.4|~4.0", + "symfony/browser-kit": "~3.4|~4.0", + "symfony/console": "~3.4|~4.0", + "symfony/css-selector": "~3.4|~4.0", + "symfony/dom-crawler": "~3.4|~4.0", + "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/expression-language": "~3.4|~4.0", + "symfony/form": "~3.4|~4.0", + "symfony/framework-bundle": "~4.1", + "symfony/http-foundation": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0", + "symfony/translation": "~3.4|~4.0", + "symfony/twig-bridge": "~3.4|~4.0", + "symfony/twig-bundle": "~3.4|~4.0", + "symfony/validator": "~3.4|~4.0", + "symfony/var-dumper": "~3.4|~4.0", + "symfony/yaml": "~3.4|~4.0", + "twig/twig": "~1.34|~2.4" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bundle\\SecurityBundle\\": "" + }, + "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 SecurityBundle", + "homepage": "https://symfony.com", + "time": "2018-06-25T11:12:43+00:00" + }, + { + "name": "symfony/serializer", + "version": "v4.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/serializer.git", + "reference": "2ddc6ec084eba809aec92bf723e007bc3a8345c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/serializer/zipball/2ddc6ec084eba809aec92bf723e007bc3a8345c0", + "reference": "2ddc6ec084eba809aec92bf723e007bc3a8345c0", "shasum": "" }, "require": { @@ -3241,7 +3665,7 @@ ], "description": "Symfony Serializer Component", "homepage": "https://symfony.com", - "time": "2018-05-30T07:26:09+00:00" + "time": "2018-06-22T08:59:39+00:00" }, { "name": "symfony/serializer-pack", @@ -3276,16 +3700,16 @@ }, { "name": "symfony/translation", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "16328f5b217cebc8dd4adfe4aeeaa8c377581f5a" + "reference": "b6d8164085ee0b6debcd1b7a131fd6f63bb04854" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/16328f5b217cebc8dd4adfe4aeeaa8c377581f5a", - "reference": "16328f5b217cebc8dd4adfe4aeeaa8c377581f5a", + "url": "https://api.github.com/repos/symfony/translation/zipball/b6d8164085ee0b6debcd1b7a131fd6f63bb04854", + "reference": "b6d8164085ee0b6debcd1b7a131fd6f63bb04854", "shasum": "" }, "require": { @@ -3341,20 +3765,20 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2018-05-30T07:26:09+00:00" + "time": "2018-06-22T08:59:39+00:00" }, { "name": "symfony/twig-bridge", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "b2e4c10df6517d1fdbb053326387f8e1779ad76e" + "reference": "4794c3cac1cd3c0104ec194ecd3ea82019d94c64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/b2e4c10df6517d1fdbb053326387f8e1779ad76e", - "reference": "b2e4c10df6517d1fdbb053326387f8e1779ad76e", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/4794c3cac1cd3c0104ec194ecd3ea82019d94c64", + "reference": "4794c3cac1cd3c0104ec194ecd3ea82019d94c64", "shasum": "" }, "require": { @@ -3431,44 +3855,44 @@ ], "description": "Symfony Twig Bridge", "homepage": "https://symfony.com", - "time": "2018-05-16T14:33:22+00:00" + "time": "2018-06-25T11:12:43+00:00" }, { "name": "symfony/twig-bundle", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "158bf522a8df74b0a322421bbde14a1647197007" + "reference": "04f9b5055ca8ea1cb2abc247149b0b4ae4bac6da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/158bf522a8df74b0a322421bbde14a1647197007", - "reference": "158bf522a8df74b0a322421bbde14a1647197007", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/04f9b5055ca8ea1cb2abc247149b0b4ae4bac6da", + "reference": "04f9b5055ca8ea1cb2abc247149b0b4ae4bac6da", "shasum": "" }, "require": { "php": "^7.1.3", "symfony/config": "~3.4|~4.0", - "symfony/http-foundation": "~3.4|~4.0", - "symfony/http-kernel": "~3.4|~4.0", + "symfony/http-foundation": "~4.1", + "symfony/http-kernel": "~4.1", "symfony/polyfill-ctype": "~1.8", "symfony/twig-bridge": "^3.4.3|^4.0.3", "twig/twig": "~1.34|~2.4" }, "conflict": { - "symfony/dependency-injection": "<3.4", - "symfony/event-dispatcher": "<3.4" + "symfony/dependency-injection": "<4.1", + "symfony/framework-bundle": "<4.1" }, "require-dev": { "doctrine/annotations": "~1.0", "doctrine/cache": "~1.0", "symfony/asset": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", + "symfony/dependency-injection": "~4.1", "symfony/expression-language": "~3.4|~4.0", "symfony/finder": "~3.4|~4.0", "symfony/form": "~3.4|~4.0", - "symfony/framework-bundle": "~3.4|~4.0", + "symfony/framework-bundle": "~4.1", "symfony/routing": "~3.4|~4.0", "symfony/stopwatch": "~3.4|~4.0", "symfony/templating": "~3.4|~4.0", @@ -3505,11 +3929,97 @@ ], "description": "Symfony TwigBundle", "homepage": "https://symfony.com", - "time": "2018-05-16T14:41:07+00:00" + "time": "2018-06-25T11:12:43+00:00" + }, + { + "name": "symfony/validator", + "version": "v4.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/validator.git", + "reference": "f2523bfd8dc5ff648aca55c0f2748674ca4661bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/validator/zipball/f2523bfd8dc5ff648aca55c0f2748674ca4661bb", + "reference": "f2523bfd8dc5ff648aca55c0f2748674ca4661bb", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation": "~3.4|~4.0" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/dependency-injection": "<3.4", + "symfony/http-kernel": "<3.4", + "symfony/intl": "<4.1", + "symfony/yaml": "<3.4" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/cache": "~1.0", + "egulias/email-validator": "^1.2.8|~2.0", + "symfony/cache": "~3.4|~4.0", + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/expression-language": "~3.4|~4.0", + "symfony/http-foundation": "~4.1", + "symfony/http-kernel": "~3.4|~4.0", + "symfony/intl": "~4.1", + "symfony/property-access": "~3.4|~4.0", + "symfony/var-dumper": "~3.4|~4.0", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "doctrine/annotations": "For using the annotation mapping. You will also need doctrine/cache.", + "doctrine/cache": "For using the default cached annotation reader and metadata cache.", + "egulias/email-validator": "Strict (RFC compliant) email validation", + "psr/cache-implementation": "For using the metadata cache.", + "symfony/config": "", + "symfony/expression-language": "For using the Expression validator", + "symfony/http-foundation": "", + "symfony/intl": "", + "symfony/property-access": "For accessing properties within comparison constraints", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Validator\\": "" + }, + "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 Validator Component", + "homepage": "https://symfony.com", + "time": "2018-06-19T21:38:16+00:00" }, { "name": "symfony/web-server-bundle", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/web-server-bundle.git", @@ -3568,7 +4078,7 @@ }, { "name": "symfony/yaml", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", @@ -3911,7 +4421,7 @@ "packages-dev": [ { "name": "symfony/dotenv", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/dotenv.git", @@ -4023,16 +4533,16 @@ }, { "name": "symfony/var-dumper", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "bc88ad53e825ebacc7b190bbd360781fce381c64" + "reference": "b2eebaec085d1f2cafbad7644733d494a3bbbc9b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/bc88ad53e825ebacc7b190bbd360781fce381c64", - "reference": "bc88ad53e825ebacc7b190bbd360781fce381c64", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b2eebaec085d1f2cafbad7644733d494a3bbbc9b", + "reference": "b2eebaec085d1f2cafbad7644733d494a3bbbc9b", "shasum": "" }, "require": { @@ -4094,20 +4604,20 @@ "debug", "dump" ], - "time": "2018-04-29T07:56:09+00:00" + "time": "2018-06-23T12:23:56+00:00" }, { "name": "symfony/web-profiler-bundle", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/web-profiler-bundle.git", - "reference": "8b3914ac388b2c560d4dc162c8136d136245718b" + "reference": "0043c661d7dda6cc07b28345832912e548e204b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/8b3914ac388b2c560d4dc162c8136d136245718b", - "reference": "8b3914ac388b2c560d4dc162c8136d136245718b", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/0043c661d7dda6cc07b28345832912e548e204b6", + "reference": "0043c661d7dda6cc07b28345832912e548e204b6", "shasum": "" }, "require": { @@ -4160,7 +4670,7 @@ ], "description": "Symfony WebProfilerBundle", "homepage": "https://symfony.com", - "time": "2018-05-23T13:33:23+00:00" + "time": "2018-06-12T12:15:08+00:00" } ], "aliases": [], diff --git a/config/bundles.php b/config/bundles.php index 00252c1..d8fd2ba 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -11,4 +11,5 @@ return [ WhiteOctober\PagerfantaBundle\WhiteOctoberPagerfantaBundle::class => ['all' => true], Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], + Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], ]; diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index 39536b6..26d7f45 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -3,21 +3,42 @@ parameters: # This allows you to run cache:warmup even if your # environment variables are not available yet. # You should not need to change this value. - env(DATABASE_URL): '' + env(APP_DATABASE_URL): '' + env(MAGNETICOD_DATABASE_URL): '' doctrine: dbal: - # configure these for your database server - driver: 'pdo_sqlite' - url: '%env(resolve:DATABASE_URL)%' + default_connection: default + connections: + default: + driver: 'pdo_pgsql' + url: '%env(resolve:APP_DATABASE_URL)%' + #server_version: 9.6 + magneticod: + driver: 'pdo_sqlite' + url: '%env(resolve:MAGNETICOD_DATABASE_URL)%' orm: auto_generate_proxy_classes: '%kernel.debug%' - naming_strategy: doctrine.orm.naming_strategy.underscore - auto_mapping: true - mappings: - App: - is_bundle: false - type: annotation - dir: '%kernel.project_dir%/src/Entity' - prefix: 'App\Entity' - alias: App + default_entity_manager: default + entity_managers: + default: + connection: default + naming_strategy: doctrine.orm.naming_strategy.underscore + auto_mapping: true + mappings: + App: + is_bundle: false + type: annotation + dir: '%kernel.project_dir%/src/Entity' + prefix: 'App\Entity' + alias: App + magneticod: + connection: magneticod + mappings: + Magnetico: + is_bundle: false + type: annotation + dir: '%kernel.project_dir%/src/Magnetico/Entity' + prefix: 'App\Magnetico' + alias: Magnetico + diff --git a/config/packages/security.yaml b/config/packages/security.yaml new file mode 100644 index 0000000..c919848 --- /dev/null +++ b/config/packages/security.yaml @@ -0,0 +1,52 @@ +security: + # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers + providers: + default_provider: + entity: + class: App\Entity\User + property: username + manager_name: default + api_token_provider: + id: App\Security\ApiTokenUserProvider + encoders: + App\Entity\User: + algorithm: 'argon2i' + memory_cost: 16384 + time_cost: 2 + threads: 4 + firewalls: + dev: + pattern: ^/(_(profiler|wdt)|css|images|js)/ + security: false + api: + pattern: ^/api/ + anonymous: ~ + stateless: true + simple_preauth: + authenticator: App\Security\ApiTokenAuthenticator + provider: api_token_provider + main: + pattern: ^/ + anonymous: ~ + provider: default_provider + form_login: + login_path: user_login + check_path: user_login + logout: + path: user_logout + target: / + remember_me: + secret: '%kernel.secret%' + lifetime: 604800 + path: / + always_remember_me: true + + + # Easy way to control access for large sections of your site + # Note: Only the *first* access control that matches will be used + access_control: + - { path: ^/api/v1/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY } + - { path: ^/api/, roles: ROLE_USER } + - { path: ^/$, roles: IS_AUTHENTICATED_ANONYMOUSLY } + - { path: /login, roles: IS_AUTHENTICATED_ANONYMOUSLY } + - { path: ^/, roles: ROLE_USER } diff --git a/config/packages/twig.yaml b/config/packages/twig.yaml index 3b315dc..454a794 100644 --- a/config/packages/twig.yaml +++ b/config/packages/twig.yaml @@ -2,3 +2,4 @@ twig: paths: ['%kernel.project_dir%/templates'] debug: '%kernel.debug%' strict_variables: '%kernel.debug%' + form_themes: ['bootstrap_4_layout.html.twig'] diff --git a/config/routes.yaml b/config/routes.yaml index 3c68b70..aa67fd3 100644 --- a/config/routes.yaml +++ b/config/routes.yaml @@ -4,7 +4,7 @@ index: controller: App\Controller\MainController::index torrents_search: - path: /torrents/search + path: /torrents controller: App\Controller\TorrentController::searchTorrent requirements: method: GET @@ -16,7 +16,39 @@ torrents_show: method: GET id: '\d+' +user_register: + path: /register/{inviteCode} + controller: App\Controller\UserController::register + requirements: + method: GET + inviteCode: \w{32} + +user_login: + path: /login + controller: App\Controller\SecurityController::login + +user_logout: + path: /logout + # API +api_v1_login: + path: /api/v1/login + controller: App\Api\V1\Controller\SecurityController::login + defaults: + _format: json + requirements: + method: POST + _format: json + +api_v1_logout: + path: /api/v1/logout + controller: App\Api\V1\Controller\SecurityController::logout + defaults: + _format: json + requirements: + method: GET + _format: json + api_v1_torrents: path: /api/v1/torrents controller: App\Api\V1\Controller\TorrentController::search diff --git a/config/services.yaml b/config/services.yaml index 4e0021b..da562e0 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -17,12 +17,20 @@ services: App\: resource: '../src/*' - exclude: '../src/{Entity,Migrations,Tests,Kernel.php}' + exclude: '../src/{Api/V1/{DTO},Magnetico/{Entity,Migrations},Entity,FormRequest,Migrations,Tests,Kernel.php}' + # Use array in exclude config from Symfony 4.2 + #- '../src/Api/V1/{DTO}' + #- '../src/Magnetico/{Entity,Migrations}' + #- '../src/{Entity,FormRequest,Migrations,Tests,Kernel.php}' App\Controller\: resource: '../src/Controller' tags: ['controller.service_arguments'] + App\Api\V1\Controller\: + resource: '../src/Api/V1/Controller' + tags: ['controller.service_arguments'] + # Fast normalizer for Symfony Serializer get_set_method_normalizer: class: Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer diff --git a/src/Api/V1/Controller/AbstractApiController.php b/src/Api/V1/Controller/AbstractApiController.php new file mode 100644 index 0000000..47f5548 --- /dev/null +++ b/src/Api/V1/Controller/AbstractApiController.php @@ -0,0 +1,19 @@ +json(new ApiResponse($data, $code, $message, $status), $code, [], [ + 'groups' => array_merge(self::DEFAULT_SERIALIZER_GROUPS,$groups), + ]); + } +} \ No newline at end of file diff --git a/src/Api/V1/Controller/SecurityController.php b/src/Api/V1/Controller/SecurityController.php new file mode 100644 index 0000000..58f7e85 --- /dev/null +++ b/src/Api/V1/Controller/SecurityController.php @@ -0,0 +1,71 @@ +request->get('username'); + $password = $request->request->get('password'); + + /** @var User $user */ + if (null === $user = $userRepo->findOneBy(['username' => $username])) { + return $this->createJsonResponse(null, [], JsonResponse::HTTP_UNAUTHORIZED, 'User not found'); + } + + if (!$passwordEncoder->isPasswordValid($user, $password)) { + return $this->createJsonResponse(null, [], JsonResponse::HTTP_UNAUTHORIZED, 'Invalid password'); + } + + $apiToken = new ApiToken($user); + $tokenRepo->add($apiToken); + + try { + $em->flush(); + } catch (\Exception $ex) { + return $this->createJsonResponse(null, [], JsonResponse::HTTP_INTERNAL_SERVER_ERROR, 'Token persisting error'); + } + + return $this->createJsonResponse($apiToken->getKey()); + } + + public function logout(TokenStorageInterface $tokenStorage, ApiTokenRepository $apiTokenRepo, EntityManagerInterface $em): JsonResponse + { + $token = $tokenStorage->getToken(); + + if (!$token instanceof AuthenticatedApiToken) { + return $this->createJsonResponse(null,[],JsonResponse::HTTP_INTERNAL_SERVER_ERROR, 'Invalid session token type retrieved.'); + } + if (null === $apiTokenKey = $token->getTokenKey()) { + return $this->createJsonResponse(null,[],JsonResponse::HTTP_INTERNAL_SERVER_ERROR, 'Can\'t retrieve token key from session.'); + } + + if (null === $apiToken = $apiTokenRepo->findOneBy(['key' => $apiTokenKey])) { + return $this->createJsonResponse(null,[],JsonResponse::HTTP_INTERNAL_SERVER_ERROR, 'API token with such key not found in the database.'); + } + + $em->remove($apiToken); + + try { + $em->flush(); + } catch (\Exception $ex) { + return $this->createJsonResponse(null,[],JsonResponse::HTTP_INTERNAL_SERVER_ERROR, 'API token deauthentication failure.'); + } + + return $this->createJsonResponse(null,[],JsonResponse::HTTP_OK, 'Successfully logged out.'); + } +} \ No newline at end of file diff --git a/src/Api/V1/Controller/TorrentController.php b/src/Api/V1/Controller/TorrentController.php index a255c98..d278bc9 100644 --- a/src/Api/V1/Controller/TorrentController.php +++ b/src/Api/V1/Controller/TorrentController.php @@ -2,22 +2,18 @@ namespace App\Api\V1\Controller; -use App\Api\V1\DTO\ApiResponse; use App\Api\V1\DTO\ListPage; -use App\Entity\Torrent; -use App\Repository\TorrentRepository; +use App\Magnetico\Entity\Torrent; +use App\Magnetico\Repository\TorrentRepository; use Pagerfanta\Adapter\DoctrineORMAdapter; use Pagerfanta\Pagerfanta; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; -use Symfony\Component\HttpFoundation\{Request, Response}; +use Symfony\Component\HttpFoundation\{JsonResponse, Request}; -class TorrentController extends Controller +class TorrentController extends AbstractApiController { - private const DEFAULT_SERIALIZER_GROUPS = ['api_v1']; - private const PER_PAGE = 20; - public function search(Request $request, TorrentRepository $repo): Response + public function search(Request $request, TorrentRepository $repo): JsonResponse { $query = $request->query->get('query', ''); $page = (int) $request->query->get('page', '1'); @@ -29,16 +25,12 @@ class TorrentController extends Controller ->setMaxPerPage(self::PER_PAGE) ; - return $this->json(new ApiResponse(ListPage::createFromPager($pager)),Response::HTTP_OK, [], [ - 'groups' => array_merge(self::DEFAULT_SERIALIZER_GROUPS,['api_v1_search']), - ]); + return $this->createJsonResponse(ListPage::createFromPager($pager), ['api_v1_search']); } - public function show(Torrent $torrent): Response + public function show(Torrent $torrent): JsonResponse { - return $this->json(new ApiResponse($torrent), Response::HTTP_OK, [], [ - 'groups' => array_merge(self::DEFAULT_SERIALIZER_GROUPS,['api_v1_show']), - ]); + return $this->createJsonResponse($torrent, ['api_v1_show'], JsonResponse::HTTP_OK,null, ''); } diff --git a/src/Api/V1/DTO/ApiResponse.php b/src/Api/V1/DTO/ApiResponse.php index b31a6ed..92dcd7f 100644 --- a/src/Api/V1/DTO/ApiResponse.php +++ b/src/Api/V1/DTO/ApiResponse.php @@ -15,28 +15,28 @@ class ApiResponse /** * @var int HTTP response status code * - * @Groups({"api_v1"}) + * @Groups({"api"}) */ private $code; /** * @var string Status text: 'success' (1xx-3xx), 'error' (4xx), 'fail' (5xx) or 'unknown' * - * @Groups({"api_v1"}) + * @Groups({"api"}) */ private $status; /** * @var string|null Used for 'fail' and 'error' * - * @Groups({"api_v1"}) + * @Groups({"api"}) */ private $message; /** * @var string|\object|array|null Response body. In case of 'error' or 'fail' contains cause or exception name. * - * @Groups({"api_v1"}) + * @Groups({"api"}) */ private $data; diff --git a/src/Api/V1/DTO/ListPage.php b/src/Api/V1/DTO/ListPage.php index 6c81cfd..3ddd47b 100644 --- a/src/Api/V1/DTO/ListPage.php +++ b/src/Api/V1/DTO/ListPage.php @@ -10,35 +10,35 @@ class ListPage /** * @var int * - * @Serializer\Groups({"api_v1"}) + * @Serializer\Groups({"api"}) */ private $numberOfPages; /** * @var int * - * @Serializer\Groups({"api_v1"}) + * @Serializer\Groups({"api"}) */ private $currentPage; /** * @var int * - * @Serializer\Groups({"api_v1"}) + * @Serializer\Groups({"api"}) */ private $numberOfResults; /** * @var int * - * @Serializer\Groups({"api_v1"}) + * @Serializer\Groups({"api"}) */ private $maxPerPage; /** * @var \Traversable * - * @Serializer\Groups({"api_v1"}) + * @Serializer\Groups({"api"}) */ protected $items; diff --git a/src/Command/AddUserCommand.php b/src/Command/AddUserCommand.php new file mode 100644 index 0000000..f6361a0 --- /dev/null +++ b/src/Command/AddUserCommand.php @@ -0,0 +1,98 @@ +em = $em; + $this->userManager = $userManager; + $this->userRepo = $userRepo; + $this->inviteRepo = $inviterepo; + } + + protected function configure() + { + $this + ->setName('user:add') + ->addArgument('username', InputArgument::REQUIRED, 'Username') + ->addArgument('email', InputArgument::REQUIRED, 'Email') + ->addArgument('password', InputArgument::OPTIONAL, 'Password', null) + ->addOption('invites', 'i', InputOption::VALUE_OPTIONAL, 'Number of invites for user', 0) + ->addOption('role', 'r', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'Role to add to the user') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $username = $input->getArgument('username'); + $email = $input->getArgument('email'); + $password = $input->getArgument('password'); + $invites = (int) $input->getOption('invites'); + $roles = (array) $input->getOption('role'); + + if (!$password) { + /** @var QuestionHelper $questionHelper */ + $questionHelper = $this->getHelper('question'); + $question = new Question('Enter new user\'s password: '); + $question->setHidden(true); + $question->setHiddenFallback(false); + + $password = $questionHelper->ask($input, $output, $question); + } + + if (!$password) { + $output->writeln('User password cannot be empty.'); + + return 1; + } + + if ($roles) { + $user = $this->userManager->createUser($username, $password, $email, $roles); + } else { + $user = $this->userManager->createUser($username, $password, $email); + } + + + $this->userRepo->add($user); + + if ($invites) { + for ($i = 0; $i < $invites; $i++) { + $invite = new Invite($user); + $this->inviteRepo->add($invite); + } + } + + $this->em->flush(); + + $output->writeln(sprintf('User \'%s\' registered, %d invites added.', $user->getUsername(), $invites)); + + return 0; + } + +} \ No newline at end of file diff --git a/src/Controller/MainController.php b/src/Controller/MainController.php index 1d22536..603c639 100644 --- a/src/Controller/MainController.php +++ b/src/Controller/MainController.php @@ -2,16 +2,29 @@ namespace App\Controller; -use App\Repository\TorrentRepository; +use App\Form\LoginType; use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\Response; class MainController extends Controller { - public function index(TorrentRepository $repo): Response + public function index(): Response { return $this->render('index.html.twig', [ - 'torrentsCount' => $repo->getTorrentsTotalCount(), + 'loginForm' => $this->createLoginForm('')->createView(), ]); } + + private function createLoginForm(string $username): FormInterface + { + $form = $this->createForm(LoginType::class, null, [ + 'action' => $this->generateUrl('user_login'), + ]); + $form->get('_username')->setData($username); + $form->add('submit', SubmitType::class); + + return $form; + } } \ No newline at end of file diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php new file mode 100644 index 0000000..50e6ccf --- /dev/null +++ b/src/Controller/SecurityController.php @@ -0,0 +1,40 @@ +getLastAuthenticationError() ? $authenticationUtils->getLastAuthenticationError()->getMessage() : ''; + $lastUsername = $authenticationUtils->getLastUsername(); + + $form = $this->createLoginForm($lastUsername); + $form->handleRequest($request); + + if ($lastError) { + $form->addError(new FormError($lastError)); + } + + return $this->render('Security/login.html.twig', ['form' => $form->createView()]); + } + + private function createLoginForm(string $username): FormInterface + { + $form = $this->createForm(LoginType::class, null, [ + 'action' => $this->generateUrl('user_login'), + ]); + $form->get('_username')->setData($username); + $form->add('submit', SubmitType::class); + + return $form; + } +} \ No newline at end of file diff --git a/src/Controller/TorrentController.php b/src/Controller/TorrentController.php index 737fbed..a6974bd 100644 --- a/src/Controller/TorrentController.php +++ b/src/Controller/TorrentController.php @@ -2,8 +2,8 @@ namespace App\Controller; -use App\Entity\Torrent; -use App\Repository\TorrentRepository; +use App\Magnetico\Entity\Torrent; +use App\Magnetico\Repository\TorrentRepository; use Pagerfanta\Adapter\DoctrineORMAdapter; use Pagerfanta\Pagerfanta; use Symfony\Bundle\FrameworkBundle\Controller\Controller; diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php new file mode 100644 index 0000000..b4fee2f --- /dev/null +++ b/src/Controller/UserController.php @@ -0,0 +1,65 @@ +inviteCode = $inviteCode; + $form = $this->createRegisterForm($createUserRequest, $inviteCode); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + try { + $user = $userManager->createUserByInviteCode( + $createUserRequest->username, + $createUserRequest->password, + $createUserRequest->email, + $createUserRequest->inviteCode + ); + } catch (InvalidInviteException $ex) { + // @FIXME refactor InvalidInviteException to proper validator + $form->get('inviteCode')->addError(new FormError('Invalid invite code')); + + return $this->render('User/register.html.twig', ['form' => $form->createView()]); + } + + $userRepository->add($user); + $em->flush(); + + return $this->redirectToRoute('index'); + } + + return $this->render('User/register.html.twig', ['form' => $form->createView()]); + } + + private function createRegisterForm(CreateUserRequest $createUserRequest, string $inviteCode): FormInterface + { + $form = $this->createForm(CreateUserRequestType::class, $createUserRequest, [ + 'action' => $this->generateUrl('user_register', ['inviteCode' => $inviteCode]), + ]); + + $form->add('submit', SubmitType::class); + + return $form; + } +} \ No newline at end of file diff --git a/src/Entity/ApiToken.php b/src/Entity/ApiToken.php new file mode 100644 index 0000000..eb58869 --- /dev/null +++ b/src/Entity/ApiToken.php @@ -0,0 +1,60 @@ +user = $user; + $this->key = md5(random_bytes(100)); + $this->createdAt = new \DateTime(); + } + + public function getUser(): User + { + return $this->user; + } + + public function getKey(): string + { + return $this->key; + } + + public function getCreatedAt(): \DateTime + { + return $this->createdAt; + } +} \ No newline at end of file diff --git a/src/Entity/Invite.php b/src/Entity/Invite.php new file mode 100644 index 0000000..14f6870 --- /dev/null +++ b/src/Entity/Invite.php @@ -0,0 +1,84 @@ +user = $forUser; + $this->code = md5(random_bytes(100)); + } + + public function getId(): int + { + return $this->id; + } + + public function getUser(): User + { + return $this->user; + } + + public function getCode(): string + { + return $this->code; + } + + public function getUsedBy(): ?User + { + return $this->usedBy; + } + + public function use(User $user): void + { + if ($this->usedBy) { + throw new \RuntimeException(sprintf( + 'Invite #%d is already used by User#%d and can\'t be used by User#%d', + $this->id, + $this->usedBy->getId(), + $user->getId() + )); + } + + $this->usedBy = $user; + } +} \ No newline at end of file diff --git a/src/Entity/User.php b/src/Entity/User.php new file mode 100644 index 0000000..e3b0b03 --- /dev/null +++ b/src/Entity/User.php @@ -0,0 +1,152 @@ +username = $username; + $this->password = $password; + $this->email = $email; + $this->roles = $roles ?: ['ROLE_USER']; + $this->createdAt = new \DateTime(); + } + + public function getId(): int + { + return $this->id; + } + + public function getUsername() + { + return $this->username; + } + + public function getPassword() + { + return $this->password; + } + + public function updatePassword(string $password): void + { + $this->password = $password; + } + + public function getSalt() + { + // Salt is not needed when using Argon2i + // @see https://symfony.com/doc/current/reference/configuration/security.html#using-the-argon2i-password-encoder + return null; + } + + public function getEmail(): string + { + return $this->email; + } + + public function getRoles(): array + { + return $this->roles; + } + + public function addRole(string $role): void + { + $this->roles[] = $role; + } + + public function getCreatedAt(): \DateTime + { + return $this->createdAt; + } + + public function eraseCredentials() + { + + } + + /** @return Invite[]|ArrayCollection */ + public function getInvites(): \Traversable + { + return $this->invites; + } + + /** @see \Serializable::serialize() */ + public function serialize() + { + return serialize([ + $this->id, + $this->username, + $this->password, + ]); + } + + /** @see \Serializable::unserialize() */ + public function unserialize($serialized) + { + list( + $this->id, + $this->username, + $this->password + ) = unserialize($serialized, ['allowed_classes' => false]); + } +} \ No newline at end of file diff --git a/src/Form/CreateUserRequestType.php b/src/Form/CreateUserRequestType.php new file mode 100644 index 0000000..9f076a2 --- /dev/null +++ b/src/Form/CreateUserRequestType.php @@ -0,0 +1,30 @@ +add('username', TextType::class) + ->add('password', PasswordType::class) + ->add('email', EmailType::class) + ->add('inviteCode', TextType::class) + ; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => CreateUserRequest::class, + ]); + } + +} \ No newline at end of file diff --git a/src/Form/LoginType.php b/src/Form/LoginType.php new file mode 100644 index 0000000..d5bb8ed --- /dev/null +++ b/src/Form/LoginType.php @@ -0,0 +1,26 @@ +add('_username', TextType::class, ['mapped' => false]) + ->add('_password', PasswordType::class, ['mapped' => false]) + ; + } + + public function getBlockPrefix() + { + // Empty prefix for default UsernamePasswordFrormAuthenticationListener + return ''; + } + + +} \ No newline at end of file diff --git a/src/FormRequest/CreateUserRequest.php b/src/FormRequest/CreateUserRequest.php new file mode 100644 index 0000000..6016905 --- /dev/null +++ b/src/FormRequest/CreateUserRequest.php @@ -0,0 +1,42 @@ +select('COUNT(t.id)') ; - return (int) $qb->getQuery()->getSingleScalarResult(); + try { + return (int) $qb->getQuery()->getSingleScalarResult(); + } catch (\Exception $ex) { + return 0; + } } public function createFindLikeQueryBuilder(string $query): QueryBuilder diff --git a/src/Migrations/Version20180626223216.php b/src/Migrations/Version20180626223216.php new file mode 100644 index 0000000..c2f6505 --- /dev/null +++ b/src/Migrations/Version20180626223216.php @@ -0,0 +1,50 @@ +abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('CREATE SCHEMA users'); + $this->addSql('CREATE SEQUENCE users.users_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE users.invites_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE users.users (id INT NOT NULL, username VARCHAR(25) NOT NULL, password TEXT NOT NULL, email VARCHAR(254) NOT NULL, roles JSON NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_338ADFC4F85E0677 ON users.users (username)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_338ADFC4E7927C74 ON users.users (email)'); + $this->addSql('CREATE TABLE users.invites (id INT NOT NULL, user_id INT NOT NULL, used_by_id INT DEFAULT NULL, code VARCHAR(32) NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_93848FA877153098 ON users.invites (code)'); + $this->addSql('CREATE INDEX IDX_93848FA8A76ED395 ON users.invites (user_id)'); + $this->addSql('CREATE INDEX IDX_93848FA84C2B72A8 ON users.invites (used_by_id)'); + $this->addSql('CREATE TABLE users.api_tokens (key VARCHAR(32) NOT NULL, user_id INT NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(key))'); + $this->addSql('CREATE INDEX IDX_C46A74A9A76ED395 ON users.api_tokens (user_id)'); + $this->addSql('ALTER TABLE users.invites ADD CONSTRAINT FK_93848FA8A76ED395 FOREIGN KEY (user_id) REFERENCES users.users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE users.invites ADD CONSTRAINT FK_93848FA84C2B72A8 FOREIGN KEY (used_by_id) REFERENCES users.users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE users.api_tokens ADD CONSTRAINT FK_C46A74A9A76ED395 FOREIGN KEY (user_id) REFERENCES users.users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema) : void + { + // this down() migration is auto-generated, please modify it to your needs + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('ALTER TABLE users.invites DROP CONSTRAINT FK_93848FA8A76ED395'); + $this->addSql('ALTER TABLE users.invites DROP CONSTRAINT FK_93848FA84C2B72A8'); + $this->addSql('ALTER TABLE users.api_tokens DROP CONSTRAINT FK_C46A74A9A76ED395'); + $this->addSql('DROP SEQUENCE users.users_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE users.invites_id_seq CASCADE'); + $this->addSql('DROP TABLE users.users'); + $this->addSql('DROP TABLE users.invites'); + $this->addSql('DROP TABLE users.api_tokens'); + $this->addSql('DROP SCHEMA users'); + } +} diff --git a/src/Repository/ApiTokenRepository.php b/src/Repository/ApiTokenRepository.php new file mode 100644 index 0000000..523be7c --- /dev/null +++ b/src/Repository/ApiTokenRepository.php @@ -0,0 +1,35 @@ +getEntityManager()->persist($token); + } + + public function findUserByTokenKey(string $tokenKey): ?User + { + $qb = $this->getEntityManager()->createQueryBuilder(); + $qb + ->select('u') + ->from(User::class, 'u') + ->innerJoin(ApiToken::class, 'at', Join::WITH, 'at.user = u') + ->where('at.key = :tokenKey') + ->setParameter('tokenKey', $tokenKey) + ; + + return $qb->getQuery()->getOneOrNullResult(); + } +} \ No newline at end of file diff --git a/src/Repository/InviteRepository.php b/src/Repository/InviteRepository.php new file mode 100644 index 0000000..58bf7b0 --- /dev/null +++ b/src/Repository/InviteRepository.php @@ -0,0 +1,20 @@ +getEntityManager()->persist($invite); + } +} \ No newline at end of file diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php new file mode 100644 index 0000000..c9b4c14 --- /dev/null +++ b/src/Repository/UserRepository.php @@ -0,0 +1,20 @@ +getEntityManager()->persist($user); + } +} \ No newline at end of file diff --git a/src/Security/ApiTokenAuthenticator.php b/src/Security/ApiTokenAuthenticator.php new file mode 100644 index 0000000..2d1adb1 --- /dev/null +++ b/src/Security/ApiTokenAuthenticator.php @@ -0,0 +1,88 @@ +serializer = $serializer; + } + + /** Takes request data and creates token which will be ready to auth check */ + public function createToken(Request $request, $providerKey) + { + if (!($tokenKey = $request->headers->get(self::TOKEN_HEADER))) { + // Throwing exception here will break anonymous authentication for login method + //throw new BadCredentialsException(sprintf('\'%s\' is invalid or not defined', self::TOKEN_HEADER)); + return null; + } + + return new PreAuthenticatedToken( + 'anon.', + $tokenKey, + $providerKey + ); + } + + public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey) + { + if (!$userProvider instanceof ApiTokenUserProvider) { + throw new \InvalidArgumentException(sprintf( + 'The user provider for providerKey = \'%s\' must be an instance of %s, %s given.', + $providerKey, + ApiTokenUserProvider::class, + get_class($userProvider) + )); + } + + $apiTokenKey = $token->getCredentials(); + + $user = $userProvider->loadUserByUsername($apiTokenKey); + + if (!$user) { + throw new CustomUserMessageAuthenticationException(sprintf( + 'API token \'%s\' does not exist.', $apiTokenKey + )); + } + + return new AuthenticatedApiToken( + $user, + $apiTokenKey, + $providerKey, + $user->getRoles() + ); + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): JsonResponse + { + // @todo Decouple with App\Api\V1\DTO + $json = $this->serializer->serialize( + new ApiResponse(null, JsonResponse::HTTP_UNAUTHORIZED, $exception->getMessage()), + 'json', + ['groups' => ['api_v1']] + ); + + return new JsonResponse($json, JsonResponse::HTTP_UNAUTHORIZED,[], true); + } + + + public function supportsToken(TokenInterface $token, $providerKey) + { + return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey; + } +} \ No newline at end of file diff --git a/src/Security/ApiTokenUserProvider.php b/src/Security/ApiTokenUserProvider.php new file mode 100644 index 0000000..85f01b4 --- /dev/null +++ b/src/Security/ApiTokenUserProvider.php @@ -0,0 +1,39 @@ +userRepo = $userRepo; + } + + public function loadUserByUsername($username): User + { + if (null === $user = $this->userRepo->findUserByTokenKey($username)) { + throw new UsernameNotFoundException(sprintf('Token \'%s\' is not found.', $username)); + } + + return $user; + } + + public function refreshUser(UserInterface $user) + { + throw new UnsupportedUserException(); + } + + public function supportsClass($class): bool + { + return User::class === $class; + } + +} \ No newline at end of file diff --git a/src/Security/Token/AuthenticatedApiToken.php b/src/Security/Token/AuthenticatedApiToken.php new file mode 100644 index 0000000..f7d1336 --- /dev/null +++ b/src/Security/Token/AuthenticatedApiToken.php @@ -0,0 +1,25 @@ +tokenKey = $credentials; + } + + public function getTokenKey(): ?string + { + return $this->tokenKey; + } +} \ No newline at end of file diff --git a/src/User/Exception/InvalidInviteException.php b/src/User/Exception/InvalidInviteException.php new file mode 100644 index 0000000..be497ae --- /dev/null +++ b/src/User/Exception/InvalidInviteException.php @@ -0,0 +1,8 @@ +userRepo = $userRepo; + $this->inviteRepo = $inviteRepo; + $this->encoderFactory = $encoderFactory; + } + + public function createUser(string $username, string $password, string $email, array $roles = self::DEFAULT_ROLES): User + { + $encodedPassword = $this->encoderFactory->getEncoder(User::class)->encodePassword($password, null); + + $user = new User( + $username, + $encodedPassword, + $email, + $roles + ); + + return $user; + } + + public function createUserByInviteCode(string $username, string $password, string $email, string $inviteCode, array $roles = self::DEFAULT_ROLES): User + { + /** @var Invite $invite */ + if (null === $invite = $this->inviteRepo->findOneBy(['code' => $inviteCode, 'usedBy' => null])) { + throw new InvalidInviteException(); + } + + $user = $this->createUser($username, $password, $email,$roles); + + $invite->use($user); + + return $user; + } +} \ No newline at end of file diff --git a/symfony.lock b/symfony.lock index 128f8c9..66961d2 100644 --- a/symfony.lock +++ b/symfony.lock @@ -146,6 +146,9 @@ "ref": "e921bdbfe20cdefa3b82f379d1cd36df1bc8d115" } }, + "symfony/form": { + "version": "v4.1.0" + }, "symfony/framework-bundle": { "version": "3.3", "recipe": { @@ -164,6 +167,9 @@ "symfony/inflector": { "version": "v4.1.0" }, + "symfony/intl": { + "version": "v4.1.0" + }, "symfony/lts": { "version": "4-dev" }, @@ -179,9 +185,15 @@ "ref": "371d1a2b69984710646b09a1182ef1d4308c904f" } }, + "symfony/options-resolver": { + "version": "v4.1.0" + }, "symfony/orm-pack": { "version": "v1.0.5" }, + "symfony/polyfill-intl-icu": { + "version": "v1.8.0" + }, "symfony/polyfill-mbstring": { "version": "v1.8.0" }, @@ -206,6 +218,18 @@ "ref": "cda8b550123383d25827705d05a42acf6819fe4e" } }, + "symfony/security": { + "version": "v4.1.0" + }, + "symfony/security-bundle": { + "version": "3.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "3.3", + "ref": "f8a63faa0d9521526499c0a8f403c9964ecb0527" + } + }, "symfony/serializer": { "version": "v4.1.0" }, @@ -233,6 +257,9 @@ "ref": "f75ac166398e107796ca94cc57fa1edaa06ec47f" } }, + "symfony/validator": { + "version": "v4.1.0" + }, "symfony/var-dumper": { "version": "v4.1.0" }, diff --git a/templates/Security/login.html.twig b/templates/Security/login.html.twig new file mode 100644 index 0000000..0ec5005 --- /dev/null +++ b/templates/Security/login.html.twig @@ -0,0 +1,7 @@ +{% extends 'base.html.twig' %} + +{% block content %} +
+ {{ form(form) }} +
+{% endblock %} \ No newline at end of file diff --git a/templates/User/register.html.twig b/templates/User/register.html.twig new file mode 100644 index 0000000..50491ff --- /dev/null +++ b/templates/User/register.html.twig @@ -0,0 +1,7 @@ +{% extends 'base.html.twig' %} + +{% block content %} +
+ {{ form(form) }} +
+{% endblock %} \ No newline at end of file diff --git a/templates/base.html.twig b/templates/base.html.twig index 16e1876..9601fd5 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -26,16 +26,25 @@ diff --git a/templates/index.html.twig b/templates/index.html.twig index 24e45fb..9cea746 100644 --- a/templates/index.html.twig +++ b/templates/index.html.twig @@ -2,6 +2,8 @@ {% block content %}
-

Torrents indexed: {{ torrentsCount }}

+ {% if not is_granted('ROLE_USER') %} + Login + {% endif %}
{% endblock %} \ No newline at end of file diff --git a/templates/torrent_list.html.twig b/templates/torrent_list.html.twig index 6973ec5..5133f93 100644 --- a/templates/torrent_list.html.twig +++ b/templates/torrent_list.html.twig @@ -13,7 +13,7 @@ Discovered - {# @var torrent \App\Entity\Torrent #} + {# @var torrent \App\Magnetico\Entity\Torrent #} {% for torrent in torrents %} 🔗 diff --git a/templates/torrent_show.html.twig b/templates/torrent_show.html.twig index 1d8152d..3c49c47 100644 --- a/templates/torrent_show.html.twig +++ b/templates/torrent_show.html.twig @@ -1,7 +1,7 @@ {% extends 'base.html.twig' %} {% block content %} - {# @var torrent \App\Entity\Torrent #} + {# @var torrent \App\Magnetico\Entity\Torrent #} @@ -31,7 +31,7 @@ - {# @var file \App\Entity\File #} + {# @var file \App\Magnetico\Entity\File #} {% for file in torrent.files | sort %} diff --git a/tests/database/database.sqlite3 b/tests/database/database.sqlite3 new file mode 100644 index 0000000..1a54f05 Binary files /dev/null and b/tests/database/database.sqlite3 differ
Name
{{ file.path }}