diff --git a/lib/Alchemy/Phrasea/Application.php b/lib/Alchemy/Phrasea/Application.php index 25fa5da095..a319057df0 100644 --- a/lib/Alchemy/Phrasea/Application.php +++ b/lib/Alchemy/Phrasea/Application.php @@ -76,6 +76,7 @@ use Alchemy\Phrasea\Controller\User\Preferences; use Alchemy\Phrasea\Core\PhraseaExceptionHandler; use Alchemy\Phrasea\Core\Event\Subscriber\LogoutSubscriber; use Alchemy\Phrasea\Core\Event\Subscriber\PhraseaLocaleSubscriber; +use Alchemy\Phrasea\Core\Event\Subscriber\MaintenanceSubscriber; use Alchemy\Phrasea\Core\Provider\AuthenticationManagerServiceProvider; use Alchemy\Phrasea\Core\Provider\BrowserServiceProvider; use Alchemy\Phrasea\Core\Provider\BorderManagerServiceProvider; @@ -369,6 +370,7 @@ class Application extends SilexApplication $dispatcher->addListener(KernelEvents::RESPONSE, array($app, 'disableCookiesIfRequired'), -256); $dispatcher->addSubscriber(new LogoutSubscriber()); $dispatcher->addSubscriber(new PhraseaLocaleSubscriber($app)); + $dispatcher->addSubscriber(new MaintenanceSubscriber($app)); return $dispatcher; }) diff --git a/lib/Alchemy/Phrasea/Controller/Root/Login.php b/lib/Alchemy/Phrasea/Controller/Root/Login.php index 7c21d11a28..9e7b739980 100644 --- a/lib/Alchemy/Phrasea/Controller/Root/Login.php +++ b/lib/Alchemy/Phrasea/Controller/Root/Login.php @@ -57,13 +57,6 @@ class Login implements ControllerProviderInterface if ($request->getPathInfo() == $app->path('homepage')) { return; } - if ($app['phraseanet.registry']->get('GV_maintenance')) { - $app->addFlash('warning', _('login::erreur: maintenance en cours, merci de nous excuser pour la gene occasionee')); - - return $app->redirect($app->path('homepage', array( - 'redirect' => ltrim($request->get('redirect'), '/'), - ))); - } }); // Displays the homepage @@ -697,9 +690,7 @@ class Login implements ControllerProviderInterface $feeds = $public_feeds->get_feeds(); array_unshift($feeds, $public_feeds->get_aggregate()); - $form = $app->form(new PhraseaAuthenticationForm(), null, array( - 'disabled' => $app['phraseanet.registry']->get('GV_maintenance') - )); + $form = $app->form(new PhraseaAuthenticationForm()); return $app['twig']->render('login/index.html.twig', array( 'module_name' => _('Accueil'), diff --git a/lib/Alchemy/Phrasea/Controller/Root/Session.php b/lib/Alchemy/Phrasea/Controller/Root/Session.php index e130c75a9c..fe664017c1 100644 --- a/lib/Alchemy/Phrasea/Controller/Root/Session.php +++ b/lib/Alchemy/Phrasea/Controller/Root/Session.php @@ -112,7 +112,7 @@ class Session implements ControllerProviderInterface } if (in_array($app['session']->get('phraseanet.message'), array('1', null))) { - if ($app['phraseanet.registry']->get('GV_maintenance')) { + if ($app['phraseanet.configuration']['main']['maintenance']) { $ret['message'] .= _('The application is going down for maintenance, please logout.'); } diff --git a/lib/Alchemy/Phrasea/Core/Event/Subscriber/ApiExceptionHandlerSubscriber.php b/lib/Alchemy/Phrasea/Core/Event/Subscriber/ApiExceptionHandlerSubscriber.php index c7374e0b28..0966f3b0b2 100644 --- a/lib/Alchemy/Phrasea/Core/Event/Subscriber/ApiExceptionHandlerSubscriber.php +++ b/lib/Alchemy/Phrasea/Core/Event/Subscriber/ApiExceptionHandlerSubscriber.php @@ -15,9 +15,13 @@ use Alchemy\Phrasea\Application; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; class ApiExceptionHandlerSubscriber implements EventSubscriberInterface { @@ -44,6 +48,8 @@ class ApiExceptionHandlerSubscriber implements EventSubscriberInterface $code = \API_V1_result::ERROR_METHODNOTALLOWED; } elseif ($e instanceof MethodNotAllowedHttpException) { $code = \API_V1_result::ERROR_METHODNOTALLOWED; + } elseif ($e instanceof BadRequestHttpException) { + $code = \API_V1_result::ERROR_BAD_REQUEST; } elseif ($e instanceof \API_V1_exception_badrequest) { $code = \API_V1_result::ERROR_BAD_REQUEST; } elseif ($e instanceof \API_V1_exception_forbidden) { @@ -52,13 +58,23 @@ class ApiExceptionHandlerSubscriber implements EventSubscriberInterface $code = \API_V1_result::ERROR_UNAUTHORIZED; } elseif ($e instanceof \API_V1_exception_internalservererror) { $code = \API_V1_result::ERROR_INTERNALSERVERERROR; + } elseif ($e instanceof AccessDeniedHttpException) { + $code = \API_V1_result::ERROR_FORBIDDEN; + } elseif ($e instanceof UnauthorizedHttpException) { + $code = \API_V1_result::ERROR_UNAUTHORIZED; } elseif ($e instanceof NotFoundHttpException) { $code = \API_V1_result::ERROR_NOTFOUND; + } elseif ($e instanceof HttpExceptionInterface) { + if (503 === $e->getStatusCode()) { + $code = \API_V1_result::ERROR_MAINTENANCE; + } else { + $code = \API_V1_result::ERROR_INTERNALSERVERERROR; + } } else { $code = \API_V1_result::ERROR_INTERNALSERVERERROR; } - if ($e instanceof HttpException) { + if ($e instanceof HttpExceptionInterface) { $headers = $e->getHeaders(); } diff --git a/lib/Alchemy/Phrasea/Core/Event/Subscriber/MaintenanceSubscriber.php b/lib/Alchemy/Phrasea/Core/Event/Subscriber/MaintenanceSubscriber.php new file mode 100644 index 0000000000..7d2aa4c617 --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Event/Subscriber/MaintenanceSubscriber.php @@ -0,0 +1,41 @@ +app = $app; + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array('checkForMaintenance', 0), + ); + } + + public function checkForMaintenance(GetResponseEvent $event) + { + if ($this->app['phraseanet.configuration']['main']['maintenance']) { + $this->app->abort(503, 'Service Temporarily Unavailable', array('Retry-After' => 3600)); + } + } +} diff --git a/lib/classes/API/V1/exception/maintenance.php b/lib/classes/API/V1/exception/maintenance.php new file mode 100644 index 0000000000..7cddf678bd --- /dev/null +++ b/lib/classes/API/V1/exception/maintenance.php @@ -0,0 +1,21 @@ +error_type = $const; $this->error_message = API_V1_exception_internalservererror::get_details(); break; + case self::ERROR_MAINTENANCE: + $this->http_code = 503; + $this->error_type = $const; + $this->error_message = API_V1_exception_maintenance::get_details(); + break; case OAUTH2_ERROR_INVALID_REQUEST: $this->error_type = $const; break; diff --git a/lib/classes/registry.php b/lib/classes/registry.php index 2117fc2550..4497580086 100644 --- a/lib/classes/registry.php +++ b/lib/classes/registry.php @@ -50,7 +50,6 @@ class registry implements registryInterface if ($app['phraseanet.configuration-tester']->isInstalled()) { $this->cache->save('GV_ServerName', $app['phraseanet.configuration']['main']['servername']); $this->cache->save('GV_debug', $app['debug']); - $this->cache->save('GV_maintenance', $app['phraseanet.configuration']['main']['maintenance']); $config = $app['phraseanet.configuration']->getConfig(); @@ -91,7 +90,7 @@ class registry implements registryInterface } foreach ($rs as $row) { - if (in_array($row['key'], array('GV_ServerName', 'GV_sit', 'GV_debug', 'GV_maintenance'))) { + if (in_array($row['key'], array('GV_ServerName', 'GV_sit', 'GV_debug'))) { continue; } diff --git a/templates/web/admin/setup.html.twig b/templates/web/admin/setup.html.twig index f0f902986c..a9a91979da 100644 --- a/templates/web/admin/setup.html.twig +++ b/templates/web/admin/setup.html.twig @@ -20,7 +20,7 @@
- +
diff --git a/tests/Alchemy/Tests/Phrasea/ApplicationTest.php b/tests/Alchemy/Tests/Phrasea/ApplicationTest.php index b7be6549c4..513be88e89 100644 --- a/tests/Alchemy/Tests/Phrasea/ApplicationTest.php +++ b/tests/Alchemy/Tests/Phrasea/ApplicationTest.php @@ -302,6 +302,32 @@ class ApplicationTest extends \PhraseanetPHPUnitAbstract $this->assertEquals('cat.turbocat.com', $app['url_generator']->getContext()->getHost()); } + public function testMaintenanceModeTriggers503s() + { + $app = new Application('test'); + + $app['phraseanet.configuration.config-path'] = __DIR__ . '/Core/Event/Subscriber/Fixtures/configuration-maintenance.yml'; + $app['phraseanet.configuration.config-compiled-path'] = __DIR__ . '/Core/Event/Subscriber/Fixtures/configuration-maintenance.php'; + + if (is_file($app['phraseanet.configuration.config-compiled-path'])) { + unlink($app['phraseanet.configuration.config-compiled-path']); + } + + $app->get('/', function(Application $app, Request $request) { + return 'Hello'; + }); + + $client = new Client($app); + $client->request('GET', '/'); + + $this->assertEquals(503, $client->getResponse()->getStatusCode()); + $this->assertNotEquals('Hello', $client->getResponse()->getContent()); + + if (is_file($app['phraseanet.configuration.config-compiled-path'])) { + unlink($app['phraseanet.configuration.config-compiled-path']); + } + } + private function getAppThatReturnLocale() { $app = new Application('test'); diff --git a/tests/Alchemy/Tests/Phrasea/Controller/Root/LoginTest.php b/tests/Alchemy/Tests/Phrasea/Controller/Root/LoginTest.php index 7428190e21..0e74c446cd 100644 --- a/tests/Alchemy/Tests/Phrasea/Controller/Root/LoginTest.php +++ b/tests/Alchemy/Tests/Phrasea/Controller/Root/LoginTest.php @@ -1287,44 +1287,6 @@ class LoginTest extends \PhraseanetWebTestCaseAuthenticatedAbstract self::$DI['user']->set_mail_locked(false); } - /** - * @covers \Alchemy\Phrasea\Controller\Root\Login::authenticate - */ - public function testAuthenticateUnavailable() - { - self::$DI['app']['authentication']->closeAccount(); - $password = \random::generatePassword(); - self::$DI['app']['phraseanet.registry']->set('GV_maintenance', true , \registry::TYPE_BOOLEAN); - - self::$DI['client'] = new Client(self::$DI['app'], array()); - - self::$DI['client']->request('POST', '/login/authenticate/', array( - 'login' => self::$DI['user']->get_login(), - 'password' => $password, - '_token' => 'token' - )); - self::$DI['app']['phraseanet.registry']->set('GV_maintenance', false, \registry::TYPE_BOOLEAN); - $this->assertTrue(self::$DI['client']->getResponse()->isRedirect()); - $this->assertFlashMessagePopulated(self::$DI['app'], 'warning', 1); - $this->assertFalse(self::$DI['app']['authentication']->isAuthenticated()); - - } - - /** - * @covers \Alchemy\Phrasea\Controller\Root\Login::authenticate - */ - public function testMaintenanceOnLoginDoesNotRedirect() - { - self::$DI['app']['authentication']->closeAccount(); - self::$DI['app']['phraseanet.registry']->set('GV_maintenance', true , \registry::TYPE_BOOLEAN); - - self::$DI['client'] = new Client(self::$DI['app'], array()); - - self::$DI['client']->request('GET', '/login/'); - self::$DI['app']['phraseanet.registry']->set('GV_maintenance', false, \registry::TYPE_BOOLEAN); - $this->assertFalse(self::$DI['client']->getResponse()->isRedirect()); - } - public function testAuthenticateWithProvider() { $provider = $this->getMock('Alchemy\Phrasea\Authentication\Provider\ProviderInterface'); diff --git a/tests/Alchemy/Tests/Phrasea/Core/Event/Subscriber/Fixtures/configuration-maintenance.yml b/tests/Alchemy/Tests/Phrasea/Core/Event/Subscriber/Fixtures/configuration-maintenance.yml new file mode 100644 index 0000000000..30d1b82bf6 --- /dev/null +++ b/tests/Alchemy/Tests/Phrasea/Core/Event/Subscriber/Fixtures/configuration-maintenance.yml @@ -0,0 +1,141 @@ +main: + servername: 'http://local.phrasea/' + maintenance: true + database: + host: sql-host + port: '3306' + user: sql-user + password: sql-password + dbname: ab_phraseanet + driver: pdo_mysql + charset: UTF8 + database-test: + driver: pdo_sqlite + path: /tmp/db.sqlite + charset: UTF8 + api-timers: true + cache: + type: MemcacheCache + options: + host: localhost + port: 11211 + opcodecache: + type: ArrayCache + options: { } + search-engine: + type: Alchemy\Phrasea\SearchEngine\Phrasea\PhraseaEngine + options: { } + task-manager: + options: '' +trusted-proxies: { } +debugger: + allowed-ips: { } +binaries: { } +border-manager: + enabled: true + checkers: + - + type: Checker\Sha256 + enabled: true + - + type: Checker\UUID + enabled: true + - + type: Checker\Colorspace + enabled: false + options: + colorspaces: + - cmyk + - grayscale + - rgb + - + type: Checker\Dimension + enabled: false + options: + width: 80 + height: 160 + - + type: Checker\Extension + enabled: false + options: + extensions: + - jpg + - jpeg + - bmp + - tif + - gif + - png + - pdf + - doc + - odt + - mpg + - mpeg + - mov + - avi + - xls + - flv + - mp3 + - mp2 + - + type: Checker\Filename + enabled: false + options: + sensitive: true + - + type: Checker\MediaType + enabled: false + options: + mediatypes: + - Audio + - Document + - Flash + - Image + - Video +authentication: + auto-create: + enabled: false + templates: { } + captcha: + enabled: true + trials-before-failure: 9 + providers: + facebook: + enabled: false + options: + app-id: '' + secret: '' + twitter: + enabled: false + options: + consumer-key: '' + consumer-secret: '' + google-plus: + enabled: false + options: + client-id: '' + client-secret: '' + github: + enabled: false + options: + client-id: '' + client-secret: '' + viadeo: + enabled: false + options: + client-id: '' + client-secret: '' + linkedin: + enabled: false + options: + client-id: '' + client-secret: '' +registration-fields: + - + name: company + required: true + - + name: firstname + required: true + - + name: geonameid + required: true diff --git a/tests/Alchemy/Tests/Phrasea/Core/Event/Subscriber/MaintenanceSubscriberTest.php b/tests/Alchemy/Tests/Phrasea/Core/Event/Subscriber/MaintenanceSubscriberTest.php new file mode 100644 index 0000000000..107a49127a --- /dev/null +++ b/tests/Alchemy/Tests/Phrasea/Core/Event/Subscriber/MaintenanceSubscriberTest.php @@ -0,0 +1,61 @@ +addSubscriber(new MaintenanceSubscriber($app)); + $app->get('/', function () { + return 'Hello'; + }); + + $client = new Client($app); + $client->request('GET', '/'); + + $this->assertEquals(200, $client->getResponse()->getStatusCode()); + $this->assertEquals('Hello', $client->getResponse()->getContent()); + } + + public function testCheckPositive() + { + $app = new Application(); + + $app['phraseanet.configuration.config-path'] = __DIR__ . '/Fixtures/configuration-maintenance.yml'; + $app['phraseanet.configuration.config-compiled-path'] = __DIR__ . '/Fixtures/configuration-maintenance.php'; + + if (is_file($app['phraseanet.configuration.config-compiled-path'])) { + unlink($app['phraseanet.configuration.config-compiled-path']); + } + + unset($app['exception_handler']); + $app['dispatcher']->addSubscriber(new MaintenanceSubscriber($app)); + $app->get('/', function () { + return 'Hello'; + }); + + $client = new Client($app); + try { + $client->request('GET', '/'); + $this->fail('An exception should have been raised'); + } catch (HttpException $e) { + $this->assertEquals(503, $e->getStatusCode()); + $this->assertEquals(array('Retry-After' => 3600), $e->getHeaders()); + } + } +}