diff --git a/config/config.sample.yml b/config/config.sample.yml index b2d2cb893e..e04956833e 100644 --- a/config/config.sample.yml +++ b/config/config.sample.yml @@ -33,6 +33,9 @@ dev: search-engine: phrasea task-manager: task_manager authentication: + auto-create: + enabled: false + templates: [] captcha: trials-before-failure: 9 providers: @@ -97,6 +100,9 @@ prod: search-engine: phrasea task-manager: task_manager authentication: + auto-create: + enabled: false + templates: [] captcha: trials-before-failure: 9 providers: @@ -161,6 +167,9 @@ test: search-engine: phrasea task-manager: task_manager authentication: + auto-create: + enabled: false + templates: [] captcha: trials-before-failure: 9 providers: diff --git a/lib/Alchemy/Phrasea/Authentication/AccountCreator.php b/lib/Alchemy/Phrasea/Authentication/AccountCreator.php index f9c1c11c68..8ce5b576a0 100644 --- a/lib/Alchemy/Phrasea/Authentication/AccountCreator.php +++ b/lib/Alchemy/Phrasea/Authentication/AccountCreator.php @@ -30,6 +30,16 @@ class AccountCreator $this->templates = $templates; } + /** + * Returns the default templates + * + * @return array + */ + public function getTemplates() + { + return $this->templates; + } + /** * @return Boolean */ diff --git a/lib/Alchemy/Phrasea/Controller/Root/Login.php b/lib/Alchemy/Phrasea/Controller/Root/Login.php index 51353a1ddc..176df1788d 100644 --- a/lib/Alchemy/Phrasea/Controller/Root/Login.php +++ b/lib/Alchemy/Phrasea/Controller/Root/Login.php @@ -15,6 +15,7 @@ use Alchemy\Phrasea\Application as PhraseaApplication; use Alchemy\Phrasea\Authentication\Exception\NotAuthenticatedException; use Alchemy\Phrasea\Authentication\Exception\AuthenticationException; use Alchemy\Phrasea\Authentication\Context; +use Alchemy\Phrasea\Authentication\Provider\ProviderInterface; use Alchemy\Phrasea\Core\Event\LogoutEvent; use Alchemy\Phrasea\Core\Event\PreAuthenticate; use Alchemy\Phrasea\Core\Event\PostAuthenticate; @@ -30,10 +31,10 @@ use Alchemy\Phrasea\Notification\Mail\MailSuccessEmailConfirmationUnregistered; use Alchemy\Phrasea\Authentication\Exception\RequireCaptchaException; use Alchemy\Phrasea\Authentication\Exception\AccountLockedException; use Alchemy\Phrasea\Form\Login\PhraseaAuthenticationForm; -use Alchemy\Phrasea\Form\Login\PhraseaAuthenticationWithMappingForm; use Alchemy\Phrasea\Form\Login\PhraseaForgotPasswordForm; use Alchemy\Phrasea\Form\Login\PhraseaRecoverPasswordForm; use Alchemy\Phrasea\Form\Login\PhraseaRegisterForm; +use Doctrine\ORM\EntityManager; use Entities\UsrAuthProvider; use Silex\Application; use Silex\ControllerProviderInterface; @@ -112,30 +113,6 @@ class Login implements ControllerProviderInterface $app['firewall']->requireNotAuthenticated(); })->bind('login_authentication_provider_callback'); - // Displays a form to add a mapping from an AuthProvider identity to a Phraseanet user that has been found - $controllers->get('/provider/{providerId}/add-mapping/', 'login.controller:authenticationMapping') - ->before(function(Request $request) use ($app) { - $app['firewall']->requireNotAuthenticated(); - })->bind('login_authentication_provider_mapping'); - - // Submit the mapping from an AuthProvider identity to a Phraseanet user that has been found - $controllers->post('/provider/{providerId}/add-mapping/', 'login.controller:authenticationDoMapToAccount') - ->before(function(Request $request) use ($app) { - $app['firewall']->requireNotAuthenticated(); - })->bind('login_authentication_provider_do_mapping'); - - // Displays a form to bind an AuthProvider identity to an account or create one - $controllers->get('/provider/{providerId}/bind-account/', 'login.controller:authenticationBindToAccount') - ->before(function(Request $request) use ($app) { - $app['firewall']->requireNotAuthenticated(); - })->bind('login_authentication_provider_bind'); - - // Submits the form to bind an AuthProvider identity to an account or create one - $controllers->post('/provider/{providerId}/bind-account/', 'login.controller:authenticationDoBindToAccount') - ->before(function(Request $request) use ($app) { - $app['firewall']->requireNotAuthenticated(); - })->bind('login_authentication_provider_do_bind'); - // Logout end point $controllers->get('/logout/', 'login.controller:logout') ->before(function(Request $request) use ($app) { @@ -150,6 +127,9 @@ class Login implements ControllerProviderInterface // Classic registration end point $controllers->match('/register-classic/', 'login.controller:doRegistration') + ->before(function(Request $request) use ($app) { + $app['firewall']->requireNotAuthenticated(); + }) ->bind('login_register_classic'); // Provide a JSON serialization of registration fields configuration @@ -157,12 +137,6 @@ class Login implements ControllerProviderInterface return $app->json($app['registration.fields']); })->bind('login_registration_fields'); -// A TESTER - // Registers with AuthProviders - $controllers->get('/register-provider/', function(PhraseaApplication $app, Request $request) { - return $app['twig']->render('login/register-provider.html.twig'); - })->bind('login_register_provider'); - // Unlocks an email address that is currently locked $controllers->get('/register-confirm/', 'login.controller:registerConfirm') ->before(function(Request $request) use ($app) { @@ -197,6 +171,10 @@ class Login implements ControllerProviderInterface public function doRegistration(PhraseaApplication $app, Request $request) { + if (!$app['registration.enabled']) { + $app->abort(404, 'Registration is disabled'); + } + $form = $app->form(new PhraseaRegisterForm( $app, $app['registration.optional-fields'], $app['registration.fields'] )); @@ -205,6 +183,35 @@ class Login implements ControllerProviderInterface $form->bind($request); $data = $form->getData(); + $provider = null; + if ($data['provider-id']) { + try { + $provider = $this->findProvider($app, $data['provider-id']); + } catch (NotFoundHttpException $e) { + $app->addFlash('error', _('You tried to register with an unknown provider')); + + return $app->redirect($app->path('login_register')); + } + + try { + $token = $provider->getToken(); + } catch (NotAuthenticatedException $e) { + $app->addFlash('error', _('You tried to register with an unknown provider')); + + return $app->redirect($app->path('login_register')); + } + + $userAuthProvider = $app['EM'] + ->getRepository('Entities\UsrAuthProvider') + ->findWithProviderAndId($token->getProvider()->getId(), $token->getId()); + + if (null !== $userAuthProvider) { + $this->postAuthProcess($app, $userAuthProvider->getUser($app)); + + return $app->redirect($request->query->get('redirect', $app->path('prod'))); + } + } + try { if ($form->isValid()) { if ($data['password'] !== $data['passwordConfirm']) { @@ -267,6 +274,11 @@ class Login implements ControllerProviderInterface } } + if (null !== $provider) { + $this->attachProviderToUser($app['EM'], $provider, $user); + $app['EM']->flush(); + } + $demandOK = array(); if ($app['phraseanet.registry']->get('GV_autoregister')) { @@ -321,6 +333,17 @@ class Login implements ControllerProviderInterface } catch (FormProcessingException $e) { $app->addFlash('error', $e->getMessage()); } + } elseif (null !== $request->query->get('providerId')) { + $provider = $this->findProvider($app, $request->query->get('providerId')); + $identity = $provider->getIdentity(); + + $form->bind(array_filter(array( + 'email' => $identity->getEmail(), + 'firstname' => $identity->getFirstname(), + 'lastname' => $identity->getLastname(), + 'company' => $identity->getCompany(), + 'provider-id' => $provider->getId(), + ))); } return $app['twig']->render('login/register-classic.html.twig', array( @@ -331,6 +354,22 @@ class Login implements ControllerProviderInterface )); } + private function attachProviderToUser(EntityManager $em, ProviderInterface $provider, \User_Adapter $user) + { + $usrAuthProvider = new UsrAuthProvider(); + $usrAuthProvider->setDistantId($provider->getToken()->getId()); + $usrAuthProvider->setProvider($provider->getId()); + $usrAuthProvider->setUsrId($user->get_id()); + + try { + $provider->logout(); + } catch (RuntimeException $e) { + // log these errors + } + + $em->persist($usrAuthProvider); + } + /** * Send a confirmation mail after register * @@ -553,8 +592,8 @@ class Login implements ControllerProviderInterface } return $app['twig']->render('login/forgot-password.html.twig', array( - 'login' => new \login(), - 'form' => $form->createView(), + 'login' => new \login(), + 'form' => $form->createView(), )); } @@ -567,8 +606,11 @@ class Login implements ControllerProviderInterface */ public function displayRegisterForm(PhraseaApplication $app, Request $request) { + if (!$app['registration.enabled']) { + $app->abort(404, 'Registration is disabled'); + } + if (0 < count($app['authentication.providers'])) { - //neutron a verifier return $app['twig']->render('login/register.html.twig', array( 'login' => new \login(), )); @@ -777,6 +819,7 @@ class Login implements ControllerProviderInterface public function authenticationCallback(PhraseaApplication $app, Request $request, $providerId) { + $login = new \login(); $provider = $this->findProvider($app, $providerId); // triggers what's necessary @@ -789,83 +832,58 @@ class Login implements ControllerProviderInterface return $app->redirect($app->path('homepage')); } - // Let's find a match $userAuthProvider = $app['EM'] ->getRepository('Entities\UsrAuthProvider') - ->findOneBy(array( - 'provider' => $token->getProvider()->getId(), - 'distant_id' => $token->getId(), - )); + ->findWithProviderAndId($token->getProvider()->getId(), $token->getId()); - if ($userAuthProvider) { + if (null !== $userAuthProvider) { $this->postAuthProcess($app, $userAuthProvider->getUser($app)); - $target = $request->query->get('redirect', $app->path('prod')); - return $app->redirect($target); + return $app->redirect($request->query->get('redirect', $app->path('prod'))); } try { - if ($app['authentication.suggestion-finder']->find($token)) { - return $app->redirect($app['url_generator']->generate('login_authentication_provider_mapping', array( - 'providerId' => $providerId, - 'id' => $token->getId(), - ))); - } else { - return $app->redirect($app['url_generator']->generate('login_authentication_provider_bind', array( - 'providerId' => $providerId, - 'id' => $token->getId(), - ))); - } - } catch (NotAuthenticatedException $e) { - $app->addFlash('error', _('Unable to retrieve provider identity')); - - return $app->redirect($app->path('homepage')); - } - } - - public function authenticationMapping(PhraseaApplication $app, Request $request, $providerId) - { - $provider = $this->findProvider($app, $providerId); - - try { - $token = $provider->getToken(); - $suggestion = $app['authentication.suggestion-finder']->find($token); + $user = $app['authentication.suggestion-finder']->find($token); } catch (NotAuthenticatedException $e) { $app->addFlash('error', _('Unable to retrieve provider identity')); return $app->redirect($app->path('homepage')); } - $form = $app->form(new PhraseaAuthenticationWithMappingForm(), array( - 'login' => $suggestion->get_login(), - )); + if (null !== $user) { + $this->attachProviderToUser($app['EM'], $provider, $user); + $app['EM']->flush(); - return $app['twig']->render('login/providers/mapping.html.twig', array( - 'provider' => $provider, - 'recaptcha_display' => $app->isCaptchaRequired(), - 'login' => new \login(), - 'form' => $form->createView(), - 'token' => $token, - 'suggestion' => $suggestion, - )); - } - - public function authenticationBindToAccount(PhraseaApplication $app, Request $request, $providerId) - { - $provider = $this->findProvider($app, $providerId); - - $form = $app->form(new PhraseaAuthenticationForm(), array( - )); - - return $app['twig']->render('login/providers/bind.html.twig', array( - 'login' => new \login(), - 'recaptcha_display' => $app->isCaptchaRequired(), - 'provider' => $provider, - 'form' => $form->createView(), - 'token' => $provider->getToken(), - )); + $this->postAuthProcess($app, $user); + + return $app->redirect($request->query->get('redirect', $app->path('prod'))); + } + + if ($app['authentication.providers.account-creator']->isEnabled()) { + $user = $app['authentication.providers.account-creator']->create($app, $token->getId(), $token->getIdentity()->getEmail(), $token->getTemplates()); + + $this->attachProviderToUser($app['EM'], $provider, $user); + $app['EM']->flush(); + + $this->postAuthProcess($app, $user); + + return $app->redirect($request->query->get('redirect', $app->path('prod'))); + } elseif ($app['registration.enabled']) { + return $app->redirect($app->path('login_register_classic', array('providerId' => $providerId))); + } + + $app->addFlash('error', _('Your identity is not recognized.')); + + return $app->redirect($app->path('homepage')); } + /** + * + * @param PhraseaApplication $app + * @param string $providerId + * @return ProviderInterface + * @throws NotFoundHttpException + */ private function findProvider(PhraseaApplication $app, $providerId) { try { @@ -875,80 +893,6 @@ class Login implements ControllerProviderInterface } } - public function authenticationDoMapToAccount(PhraseaApplication $app, Request $request, $providerId) - { - $provider = $this->findProvider($app, $providerId); - - $form = $app->form(new PhraseaAuthenticationWithMappingForm()); - - $redirector = function (array $params = array()) use ($app, $providerId) { - $params = array_merge($params, array( - 'providerId' => $providerId, - )); - - return $app->redirect($app->path('login_authentication_provider_mapping', $params)); - }; - - try { - $response = $this->doAuthentication($app, $request, $form, $redirector); - } catch (AuthenticationException $e) { - return $e->getResponse(); - } - - $usrAuthProvider = new UsrAuthProvider(); - $usrAuthProvider->setDistantId($provider->getToken()->getId()); - $usrAuthProvider->setProvider($provider->getId()); - $usrAuthProvider->setUsrId($app['authentication']->getUser()->get_id()); - - try { - $provider->logout(); - } catch (RuntimeException $e) { - // log these errors - } - - $app['EM']->persist($usrAuthProvider); - $app['EM']->flush(); - - return $response; - } - - public function authenticationDoBindToAccount(PhraseaApplication $app, Request $request, $providerId) - { - $provider = $this->findProvider($app, $providerId); - - $form = $app->form(new PhraseaAuthenticationForm()); - - $redirector = function (array $params = array()) use ($app, $providerId) { - $params = array_merge($params, array( - 'providerId' => $providerId, - )); - - return $app->redirect($app->path('login_authentication_provider_mapping', $params)); - }; - - try { - $response = $this->doAuthentication($app, $request, $form, $redirector); - } catch (AuthenticationException $e) { - return $e->getResponse(); - } - - $usrAuthProvider = new UsrAuthProvider(); - $usrAuthProvider->setDistantId($provider->getToken()->getId()); - $usrAuthProvider->setProvider($provider->getId()); - $usrAuthProvider->setUsrId($app['authentication']->getUser()->get_id()); - - $app['EM']->persist($usrAuthProvider); - $app['EM']->flush(); - - try { - $provider->logout(); - } catch (RuntimeException $e) { - // log these errors - } - - return $response; - } - private function doAuthentication(PhraseaApplication $app, Request $request, FormInterface $form, $redirector) { if (!is_callable($redirector)) { diff --git a/lib/Alchemy/Phrasea/Form/Login/PhraseaAuthenticationWithMappingForm.php b/lib/Alchemy/Phrasea/Form/Login/PhraseaAuthenticationWithMappingForm.php deleted file mode 100644 index f6624456da..0000000000 --- a/lib/Alchemy/Phrasea/Form/Login/PhraseaAuthenticationWithMappingForm.php +++ /dev/null @@ -1,36 +0,0 @@ -add('login', 'hidden', array( - 'required' => true, - 'disabled' => $options['disabled'], - 'constraints' => array( - new Assert\NotBlank(), - ), - )); - } - - public function getName() - { - return null; - } -} diff --git a/lib/Alchemy/Phrasea/Form/Login/PhraseaRegisterForm.php b/lib/Alchemy/Phrasea/Form/Login/PhraseaRegisterForm.php index 003ea49b47..b54f90083d 100644 --- a/lib/Alchemy/Phrasea/Form/Login/PhraseaRegisterForm.php +++ b/lib/Alchemy/Phrasea/Form/Login/PhraseaRegisterForm.php @@ -71,6 +71,8 @@ class PhraseaRegisterForm extends AbstractType ))), )); + $builder->add('provider-id', 'hidden'); + require_once($this->app['phraseanet.registry']->get('GV_RootPath') . 'lib/classes/deprecated/inscript.api.php'); $baseIds = array(); diff --git a/lib/conf.d/config.yml b/lib/conf.d/config.yml index b2d2cb893e..e04956833e 100644 --- a/lib/conf.d/config.yml +++ b/lib/conf.d/config.yml @@ -33,6 +33,9 @@ dev: search-engine: phrasea task-manager: task_manager authentication: + auto-create: + enabled: false + templates: [] captcha: trials-before-failure: 9 providers: @@ -97,6 +100,9 @@ prod: search-engine: phrasea task-manager: task_manager authentication: + auto-create: + enabled: false + templates: [] captcha: trials-before-failure: 9 providers: @@ -161,6 +167,9 @@ test: search-engine: phrasea task-manager: task_manager authentication: + auto-create: + enabled: false + templates: [] captcha: trials-before-failure: 9 providers: diff --git a/tests/Alchemy/Tests/Phrasea/Authentication/AccountCreatorTest.php b/tests/Alchemy/Tests/Phrasea/Authentication/AccountCreatorTest.php index 392fbf35ef..19f87aea71 100644 --- a/tests/Alchemy/Tests/Phrasea/Authentication/AccountCreatorTest.php +++ b/tests/Alchemy/Tests/Phrasea/Authentication/AccountCreatorTest.php @@ -78,6 +78,9 @@ class AccountCreatorTest extends \PhraseanetPHPUnitAbstract $this->assertInstanceOf('User_Adapter', $user); $user->delete(); + $template1->delete(); + $template2->delete(); + $template3->delete(); } public function testCreateWithAlreadyExistingLogin() diff --git a/tests/Alchemy/Tests/Phrasea/Controller/Root/LoginTest.php b/tests/Alchemy/Tests/Phrasea/Controller/Root/LoginTest.php index fd85f5d1c4..fae6406367 100644 --- a/tests/Alchemy/Tests/Phrasea/Controller/Root/LoginTest.php +++ b/tests/Alchemy/Tests/Phrasea/Controller/Root/LoginTest.php @@ -433,6 +433,59 @@ class LoginTest extends \PhraseanetWebTestCaseAuthenticatedAbstract $this->assertAngularFlashMessage($crawler, $type, 1, $message); } + public function testGetRegisterWithRegisterIdBindDataToForm() + { + self::$DI['app']['authentication']->closeAccount(); + + $provider = $this->getMock('Alchemy\Phrasea\Authentication\Provider\ProviderInterface'); + + self::$DI['app']['authentication.providers'] = $this->getMockBuilder('Alchemy\Phrasea\Authentication\ProvidersCollection') + ->disableOriginalConstructor() + ->getMock(); + + self::$DI['app']['authentication.providers']->expects($this->once()) + ->method('get') + ->with($this->equalTo('provider-test')) + ->will($this->returnValue($provider)); + + $identity = $this->getMockBuilder('Alchemy\Phrasea\Authentication\Provider\Token\Identity') + ->disableOriginalConstructor() + ->getMock(); + + $provider->expects($this->once()) + ->method('getIdentity') + ->will($this->returnValue($identity)); + + $identity->expects($this->once()) + ->method('getEmail') + ->will($this->returnValue('supermail@superprovider.com')); + + $crawler = self::$DI['client']->request('GET', '/login/register-classic/?providerId=provider-test'); + + $this->assertEquals(200, self::$DI['client']->getResponse()->getStatusCode()); + $this->assertEquals(1, $crawler->filterXPath("//input[@value='supermail@superprovider.com' and @name='email']")->count()); + } + + public function provideRegistrationRouteAndMethods() + { + return array( + array('GET', '/login/register/'), + array('GET', '/login/register-classic/'), + array('POST', '/login/register-classic/'), + ); + } + + /** + * @dataProvider provideRegistrationRouteAndMethods + */ + public function testGetPostRegisterWhenRegistrationDisabled($method, $route) + { + self::$DI['app']['registration.enabled'] = false; + self::$DI['app']['authentication']->closeAccount(); + self::$DI['client']->request($method, $route); + $this->assertEquals(404, self::$DI['client']->getResponse()->getStatusCode()); + } + /** * @dataProvider provideInvalidRegistrationData */ @@ -550,14 +603,14 @@ class LoginTest extends \PhraseanetWebTestCaseAuthenticatedAbstract public function provideRegistrationData() { return array( - array(array(//required field missing + array(array( "password" => 'password', "passwordConfirm" => 'password', "email" => $this->generateEmail(), "accept-tou" => '1', "collections" => null, ), array()), - array(array(//extra-field is not required + array(array( "password" => 'password', "passwordConfirm" => 'password', "email" => $this->generateEmail(), @@ -696,6 +749,191 @@ class LoginTest extends \PhraseanetWebTestCaseAuthenticatedAbstract ); } + public function testPostRegisterWithProviderIdAndAlreadyBoundProvider() + { + self::$DI['app']['registration.fields'] = array(); + self::$DI['app']['authentication']->closeAccount(); + + $provider = $this->getMock('Alchemy\Phrasea\Authentication\Provider\ProviderInterface'); + $this->addProvider('provider-test', $provider); + + $entity = $this->getMock('Entities\UsrAuthProvider'); + $entity->expects($this->any()) + ->method('getUser') + ->with(self::$DI['app']) + ->will($this->returnValue(self::$DI['user'])); + + $token = new Token($provider, 42); + $this->addUsrAuthDoctrineEntitySupport(42, $entity, true); + + $provider->expects($this->once()) + ->method('getToken') + ->will($this->returnValue($token)); + + $parameters = array_merge(array('_token' => 'token'), array( + "password" => 'password', + "passwordConfirm" => 'password', + "email" => $this->generateEmail(), + "accept-tou" => '1', + "collections" => null, + "provider-id" => 'provider-test', + )); + + foreach ($parameters as $key => $parameter) { + if ('collections' === $key && null === $parameter) { + $parameters[$key] = self::$demands; + } + if ('login' === $key && null === $parameter) { + $parameters[$key] = self::$login; + } + if ('email' === $key && null === $parameter) { + $parameters[$key] = self::$email; + } + } + + self::$DI['client']->request('POST', '/login/register-classic/', $parameters); + + $this->assertTrue(self::$DI['client']->getResponse()->isRedirect()); + $this->assertEquals('/prod/', self::$DI['client']->getResponse()->headers->get('location')); + } + + public function testPostRegisterWithUnknownProvider() + { + self::$DI['app']['registration.fields'] = array(); + self::$DI['app']['authentication']->closeAccount(); + + $parameters = array_merge(array('_token' => 'token'), array( + "password" => 'password', + "passwordConfirm" => 'password', + "email" => $this->generateEmail(), + "accept-tou" => '1', + "collections" => null, + "provider-id" => 'provider-test', + )); + + foreach ($parameters as $key => $parameter) { + if ('collections' === $key && null === $parameter) { + $parameters[$key] = self::$demands; + } + if ('login' === $key && null === $parameter) { + $parameters[$key] = self::$login; + } + if ('email' === $key && null === $parameter) { + $parameters[$key] = self::$email; + } + } + + self::$DI['client']->request('POST', '/login/register-classic/', $parameters); + + $this->assertTrue(self::$DI['client']->getResponse()->isRedirect()); + $this->assertFlashMessagePopulated(self::$DI['app'], 'error', 1); + $this->assertEquals('/login/register/', self::$DI['client']->getResponse()->headers->get('location')); + } + + public function testPostRegisterWithProviderNotAuthenticated() + { + self::$DI['app']['registration.fields'] = array(); + self::$DI['app']['authentication']->closeAccount(); + + $provider = $this->getMock('Alchemy\Phrasea\Authentication\Provider\ProviderInterface'); + $this->addProvider('provider-test', $provider); + + $provider->expects($this->any()) + ->method('getToken') + ->will($this->throwException(new NotAuthenticatedException('Not authenticated'))); + + $parameters = array_merge(array('_token' => 'token'), array( + "password" => 'password', + "passwordConfirm" => 'password', + "email" => $this->generateEmail(), + "accept-tou" => '1', + "collections" => null, + "provider-id" => 'provider-test', + )); + + foreach ($parameters as $key => $parameter) { + if ('collections' === $key && null === $parameter) { + $parameters[$key] = self::$demands; + } + if ('login' === $key && null === $parameter) { + $parameters[$key] = self::$login; + } + if ('email' === $key && null === $parameter) { + $parameters[$key] = self::$email; + } + } + + self::$DI['client']->request('POST', '/login/register-classic/', $parameters); + + $this->assertTrue(self::$DI['client']->getResponse()->isRedirect()); + $this->assertFlashMessagePopulated(self::$DI['app'], 'error', 1); + $this->assertEquals('/login/register/', self::$DI['client']->getResponse()->headers->get('location')); + } + + public function testPostRegisterWithProviderId() + { + self::$DI['app']['registration.fields'] = array(); + self::$DI['app']['authentication']->closeAccount(); + + $emails = array( + 'Alchemy\Phrasea\Notification\Mail\MailRequestEmailConfirmation'=>0, + 'Alchemy\Phrasea\Notification\Mail\MailInfoUserRegistered'=>0, + 'Alchemy\Phrasea\Notification\Mail\MailInfoSomebodyAutoregistered'=>0, + ); + + $this->mockNotificationsDeliverer($emails); + + $parameters = array_merge(array('_token' => 'token'), array( + "password" => 'password', + "passwordConfirm" => 'password', + "email" => $this->generateEmail(), + "accept-tou" => '1', + "collections" => null, + "provider-id" => 'provider-test', + )); + + $provider = $this->getMock('Alchemy\Phrasea\Authentication\Provider\ProviderInterface'); + $this->addProvider('provider-test', $provider); + + $token = new Token($provider, 42); + + $provider->expects($this->any()) + ->method('getToken') + ->will($this->returnValue($token)); + + foreach ($parameters as $key => $parameter) { + if ('collections' === $key && null === $parameter) { + $parameters[$key] = self::$demands; + } + if ('login' === $key && null === $parameter) { + $parameters[$key] = self::$login; + } + if ('email' === $key && null === $parameter) { + $parameters[$key] = self::$email; + } + } + + self::$DI['client']->request('POST', '/login/register-classic/', $parameters); + + if (false === $userId = \User_Adapter::get_usr_id_from_email(self::$DI['app'], $parameters['email'])) { + $this->fail('User not created'); + } + + $user = new \User_Adapter((int) $userId, self::$DI['app']); + + $ret = self::$DI['app']['EM']->getRepository('\Entities\UsrAuthProvider') + ->findBy(array('usr_id' => $userId, 'provider' => 'provider-test')); + $this->assertCount(1, $ret); + + $user->delete(); + + $this->assertGreaterThan(0, $emails['Alchemy\Phrasea\Notification\Mail\MailInfoUserRegistered']); + $this->assertEquals(1, $emails['Alchemy\Phrasea\Notification\Mail\MailRequestEmailConfirmation']); + $this->assertTrue(self::$DI['client']->getResponse()->isRedirect()); + $this->assertFlashMessagePopulated(self::$DI['app'], 'info', 1); + $this->assertEquals('/login/', self::$DI['client']->getResponse()->headers->get('location')); + } + /** * @dataProvider provideRegistrationData */ @@ -1103,10 +1341,7 @@ class LoginTest extends \PhraseanetWebTestCaseAuthenticatedAbstract return array( array('GET', '/login/provider/provider-test/authenticate/'), array('GET', '/login/provider/provider-test/callback/'), - array('GET', '/login/provider/provider-test/add-mapping/'), - array('POST', '/login/provider/provider-test/add-mapping/'), - array('GET', '/login/provider/provider-test/bind-account/'), - array('POST', '/login/provider/provider-test/bind-account/'), + array('GET', '/login/register-classic/?providerId=provider-test'), ); } @@ -1173,7 +1408,7 @@ class LoginTest extends \PhraseanetWebTestCaseAuthenticatedAbstract ->method('onCallback'); $entity = $this->getMock('Entities\UsrAuthProvider'); - $entity->expects($this->once()) + $entity->expects($this->any()) ->method('getUser') ->with(self::$DI['app']) ->will($this->returnValue(self::$DI['user'])); @@ -1194,7 +1429,7 @@ class LoginTest extends \PhraseanetWebTestCaseAuthenticatedAbstract $this->assertTrue(self::$DI['app']['authentication']->isAuthenticated()); } - public function testAuthenticateProviderCallbackWithSuggestion() + public function testAuthenticateProviderCallbackWithSuggestionBindProviderToUser() { $provider = $this->getMock('Alchemy\Phrasea\Authentication\Provider\ProviderInterface'); $this->addProvider('provider-test', $provider); @@ -1203,17 +1438,17 @@ class LoginTest extends \PhraseanetWebTestCaseAuthenticatedAbstract ->method('onCallback'); $token = new Token($provider, 42); - $this->addUsrAuthDoctrineEntitySupport(42, null); - $provider->expects($this->once()) + $provider->expects($this->any()) ->method('getToken') ->will($this->returnValue($token)); - $user = $this->getMockBuilder('\User_Adapter') + self::$DI['app']['authentication.suggestion-finder'] = $this->getMockBuilder('Alchemy\Phrasea\Authentication\SuggestionFinder') ->disableOriginalConstructor() ->getMock(); - $this->mockSuggestionFinder(); + $user = self::$DI['user']; + self::$DI['app']['authentication.suggestion-finder']->expects($this->once()) ->method('find') ->with($token) @@ -1223,10 +1458,19 @@ class LoginTest extends \PhraseanetWebTestCaseAuthenticatedAbstract self::$DI['client']->request('GET', '/login/provider/provider-test/callback/'); $this->assertSame(302, self::$DI['client']->getResponse()->getStatusCode()); - $this->assertSame('/login/provider/provider-test/add-mapping/?id=42', self::$DI['client']->getResponse()->headers->get('location')); + + $ret = self::$DI['app']['EM']->getRepository('\Entities\UsrAuthProvider') + ->findBy(array('usr_id' => self::$DI['user']->get_id(), 'provider' => 'provider-test')); + + $this->assertCount(1, $ret); + + $this->assertSame(302, self::$DI['client']->getResponse()->getStatusCode()); + $this->assertSame('/prod/', self::$DI['client']->getResponse()->headers->get('location')); + + $this->assertTrue(self::$DI['app']['authentication']->isAuthenticated()); } - public function testAuthenticateProviderCallbackWithoutSuggestion() + public function testAuthenticateProviderCallbackWithAccountCreatorEnabled() { $provider = $this->getMock('Alchemy\Phrasea\Authentication\Provider\ProviderInterface'); $this->addProvider('provider-test', $provider); @@ -1235,221 +1479,158 @@ class LoginTest extends \PhraseanetWebTestCaseAuthenticatedAbstract ->method('onCallback'); $token = new Token($provider, 42); - $this->addUsrAuthDoctrineEntitySupport(42, null); - $provider->expects($this->once()) + $provider->expects($this->any()) ->method('getToken') ->will($this->returnValue($token)); - $this->mockSuggestionFinder(); + self::$DI['app']['authentication.suggestion-finder'] = $this->getMockBuilder('Alchemy\Phrasea\Authentication\SuggestionFinder') + ->disableOriginalConstructor() + ->getMock(); + self::$DI['app']['authentication.suggestion-finder']->expects($this->once()) ->method('find') ->with($token) ->will($this->returnValue(null)); + $identity = $this->getMockBuilder('Alchemy\Phrasea\Authentication\Provider\Token\Identity') + ->disableOriginalConstructor() + ->getMock(); + + $provider->expects($this->any()) + ->method('getIdentity') + ->will($this->returnValue($identity)); + + $provider->expects($this->once()) + ->method('getTemplates') + ->will($this->returnValue(array())); + + $identity->expects($this->once()) + ->method('getEmail') + ->will($this->returnValue('supermail@superprovider.com')); + + $createdUserId = \User_Adapter::get_usr_id_from_email(self::$DI['app'], 'supermail@superprovider.com'); + + if (false === $createdUserId) { + $random = self::$DI['app']['tokens']->generatePassword(); + $createdUser = \User_Adapter::create(self::$DI['app'], 'temporary-'.$random, $random, 'supermail@superprovider.com', false); + } else { + $createdUser = \User_Adapter::getInstance($createdUserId, self::$DI['app']); + } + + self::$DI['app']['authentication.providers.account-creator'] = $this->getMockBuilder('Alchemy\Phrasea\Authentication\AccountCreator') + ->disableOriginalConstructor() + ->getMock(); + self::$DI['app']['authentication.providers.account-creator']->expects($this->once()) + ->method('create') + ->with(self::$DI['app'], 42, 'supermail@superprovider.com', array()) + ->will($this->returnValue($createdUser)); + self::$DI['app']['authentication.providers.account-creator']->expects($this->once()) + ->method('isEnabled') + ->will($this->returnValue(true)); + self::$DI['app']['authentication']->closeAccount(); self::$DI['client']->request('GET', '/login/provider/provider-test/callback/'); $this->assertSame(302, self::$DI['client']->getResponse()->getStatusCode()); - $this->assertSame('/login/provider/provider-test/bind-account/?id=42', self::$DI['client']->getResponse()->headers->get('location')); - } - public function testGetBindAccountWithSuccess() - { - $provider = $this->getMock('Alchemy\Phrasea\Authentication\Provider\ProviderInterface'); - $this->addProvider('provider-test', $provider); + $ret = self::$DI['app']['EM']->getRepository('\Entities\UsrAuthProvider') + ->findBy(array('usr_id' => $createdUser->get_id(), 'provider' => 'provider-test')); - $token = new Token($provider, 42); - - $provider->expects($this->once()) - ->method('getToken') - ->will($this->returnValue($token)); - - $provider->expects($this->any()) - ->method('getIdentity') - ->will($this->returnValue(new Identity())); - - self::$DI['app']['authentication']->closeAccount(); - self::$DI['client']->request('GET', '/login/provider/provider-test/bind-account/'); - - $this->assertSame(200, self::$DI['client']->getResponse()->getStatusCode()); - } - - public function testPostBindAccountWithSuccess() - { - $provider = $this->getMock('Alchemy\Phrasea\Authentication\Provider\ProviderInterface'); - $this->addProvider('provider-test', $provider); - - $token = new Token($provider, 42); - - $provider->expects($this->once()) - ->method('getToken') - ->will($this->returnValue($token)); - - $password = \random::generatePassword(); - - $login = self::$DI['app']['authentication']->getUser()->get_login(); - self::$DI['app']['authentication']->getUser()->set_password($password); - self::$DI['app']['authentication']->getUser()->set_mail_locked(false); - - self::$DI['app']['authentication']->closeAccount(); - self::$DI['client']->request('POST', '/login/provider/provider-test/bind-account/', array( - '_token' => 'token', - 'login' => $login, - 'password' => $password, - 'remember-me' => '1', - 'redirect' => '/prod/', - )); + $this->assertCount(1, $ret); $this->assertSame(302, self::$DI['client']->getResponse()->getStatusCode()); $this->assertSame('/prod/', self::$DI['client']->getResponse()->headers->get('location')); - $entity = self::$DI['app']['EM']->getRepository('Entities\UsrAuthProvider') - ->findOneBy(array('distant_id' => 42)); + $this->assertTrue(self::$DI['app']['authentication']->isAuthenticated()); - $this->assertNotNull($entity); + $createdUser->delete(); } - public function testPostBindAccountWithFailure() + public function testAuthenticateProviderCallbackWithRegistrationEnabled() { $provider = $this->getMock('Alchemy\Phrasea\Authentication\Provider\ProviderInterface'); $this->addProvider('provider-test', $provider); - $provider->expects($this->never()) - ->method('getToken'); - - $login = self::$DI['app']['authentication']->getUser()->get_login(); - - self::$DI['app']['authentication']->closeAccount(); - self::$DI['client']->request('POST', '/login/provider/provider-test/bind-account/', array( - '_token' => 'token', - 'login' => $login, - 'password' => '', - 'remember-me' => '1', - 'redirect' => '/prod/', - )); - - $this->assertSame(302, self::$DI['client']->getResponse()->getStatusCode()); - $this->assertSame('/login/provider/provider-test/add-mapping/', self::$DI['client']->getResponse()->headers->get('location')); - - $entity = self::$DI['app']['EM']->getRepository('Entities\UsrAuthProvider') - ->findOneBy(array('distant_id' => 42)); - - $this->assertNull($entity); - } - - public function testPostAddMapWithSuccess() - { - $provider = $this->getMock('Alchemy\Phrasea\Authentication\Provider\ProviderInterface'); - $this->addProvider('provider-test', $provider); - - $token = new Token($provider, 42); - $provider->expects($this->once()) - ->method('getToken') - ->will($this->returnValue($token)); - - $password = \random::generatePassword(); - - $login = self::$DI['app']['authentication']->getUser()->get_login(); - self::$DI['app']['authentication']->getUser()->set_password($password); - self::$DI['app']['authentication']->getUser()->set_mail_locked(false); - - self::$DI['app']['authentication']->closeAccount(); - self::$DI['client']->request('POST', '/login/provider/provider-test/add-mapping/', array( - '_token' => 'token', - 'login' => $login, - 'password' => $password, - 'remember-me' => '1', - 'redirect' => '/prod/', - )); - - $this->assertSame(302, self::$DI['client']->getResponse()->getStatusCode()); - $this->assertSame('/prod/', self::$DI['client']->getResponse()->headers->get('location')); - - $entity = self::$DI['app']['EM']->getRepository('Entities\UsrAuthProvider') - ->findOneBy(array('distant_id' => 42)); - - $this->assertNotNull($entity); - } - - public function testPostAddMapWithFailure() - { - $provider = $this->getMock('Alchemy\Phrasea\Authentication\Provider\ProviderInterface'); - $this->addProvider('provider-test', $provider); - - $provider->expects($this->never()) - ->method('getToken'); - - $login = self::$DI['app']['authentication']->getUser()->get_login(); - - self::$DI['app']['authentication']->closeAccount(); - self::$DI['client']->request('POST', '/login/provider/provider-test/add-mapping/', array( - '_token' => 'token', - 'login' => $login, - 'password' => '', - 'remember-me' => '1', - 'redirect' => '/prod/', - )); - - $this->assertSame(302, self::$DI['client']->getResponse()->getStatusCode()); - $this->assertSame('/login/provider/provider-test/add-mapping/', self::$DI['client']->getResponse()->headers->get('location')); - - $this->assertFlashMessagePopulated(self::$DI['app'], 'error', 1); - - $entity = self::$DI['app']['EM']->getRepository('Entities\UsrAuthProvider') - ->findOneBy(array('distant_id' => 42)); - - $this->assertNull($entity); - } - - public function testGetAddMappingWithSuccess() - { - $provider = $this->getMock('Alchemy\Phrasea\Authentication\Provider\ProviderInterface'); - $this->addProvider('provider-test', $provider); + ->method('onCallback'); $token = new Token($provider, 42); - $provider->expects($this->once()) - ->method('getToken') - ->will($this->returnValue($token)); - $provider->expects($this->any()) - ->method('getIdentity') - ->will($this->returnValue(new Identity())); + ->method('getToken') + ->will($this->returnValue($token)); - $this->mockSuggestionFinder(); - - $user = $this->getMockBuilder('\User_Adapter') + self::$DI['app']['authentication.suggestion-finder'] = $this->getMockBuilder('Alchemy\Phrasea\Authentication\SuggestionFinder') ->disableOriginalConstructor() ->getMock(); self::$DI['app']['authentication.suggestion-finder']->expects($this->once()) ->method('find') ->with($token) - ->will($this->returnValue($user)); + ->will($this->returnValue(null)); + + self::$DI['app']['authentication.providers.account-creator'] = $this->getMockBuilder('Alchemy\Phrasea\Authentication\AccountCreator') + ->disableOriginalConstructor() + ->getMock(); + self::$DI['app']['authentication.providers.account-creator']->expects($this->never()) + ->method('create'); + self::$DI['app']['authentication.providers.account-creator']->expects($this->once()) + ->method('isEnabled') + ->will($this->returnValue(false)); + + self::$DI['app']['registration.enabled'] = true; self::$DI['app']['authentication']->closeAccount(); - self::$DI['client']->request('GET', '/login/provider/provider-test/add-mapping/'); + self::$DI['client']->request('GET', '/login/provider/provider-test/callback/'); - $this->assertSame(200, self::$DI['client']->getResponse()->getStatusCode()); + $this->assertSame(302, self::$DI['client']->getResponse()->getStatusCode()); + $this->assertSame('/login/register-classic/?providerId=provider-test', self::$DI['client']->getResponse()->headers->get('location')); + + $this->assertFalse(self::$DI['app']['authentication']->isAuthenticated()); } - public function testGetAddMappingFailingAuthentication() + public function testAuthenticateProviderCallbackWithoutRegistrationEnabled() { $provider = $this->getMock('Alchemy\Phrasea\Authentication\Provider\ProviderInterface'); $this->addProvider('provider-test', $provider); $provider->expects($this->once()) + ->method('onCallback'); + + $token = new Token($provider, 42); + + $provider->expects($this->any()) ->method('getToken') - ->will($this->throwException(new NotAuthenticatedException('Not authenticated !'))); + ->will($this->returnValue($token)); + + self::$DI['app']['authentication.suggestion-finder'] = $this->getMockBuilder('Alchemy\Phrasea\Authentication\SuggestionFinder') + ->disableOriginalConstructor() + ->getMock(); + + self::$DI['app']['authentication.suggestion-finder']->expects($this->once()) + ->method('find') + ->with($token) + ->will($this->returnValue(null)); + + self::$DI['app']['authentication.providers.account-creator'] = $this->getMockBuilder('Alchemy\Phrasea\Authentication\AccountCreator') + ->disableOriginalConstructor() + ->getMock(); + self::$DI['app']['authentication.providers.account-creator']->expects($this->never()) + ->method('create'); + self::$DI['app']['authentication.providers.account-creator']->expects($this->once()) + ->method('isEnabled') + ->will($this->returnValue(false)); + + self::$DI['app']['registration.enabled'] = false; self::$DI['app']['authentication']->closeAccount(); - self::$DI['client']->request('GET', '/login/provider/provider-test/add-mapping/'); + self::$DI['client']->request('GET', '/login/provider/provider-test/callback/'); $this->assertSame(302, self::$DI['client']->getResponse()->getStatusCode()); $this->assertSame('/login/', self::$DI['client']->getResponse()->headers->get('location')); + $this->assertFalse(self::$DI['app']['authentication']->isAuthenticated()); $this->assertFlashMessagePopulated(self::$DI['app'], 'error', 1); } @@ -1502,16 +1683,14 @@ class LoginTest extends \PhraseanetWebTestCaseAuthenticatedAbstract private function addUsrAuthDoctrineEntitySupport($id, $out, $participants = false) { - $repo = $this->getMockBuilder('Doctrine\ORM\EntityRepository') + $repo = $this->getMockBuilder('Doctrine\ORM\EntityRepository\UsrAuthProviderRepository') + ->setMethods(array('findWithProviderAndId')) ->disableOriginalConstructor() ->getMock(); - $repo->expects($this->once()) - ->method('findOneBy') - ->with($this->equalTo(array( - 'provider' => 'provider-test', - 'distant_id' => $id - ))) + $repo->expects($this->any()) + ->method('findWithProviderAndId') + ->with('provider-test', $id) ->will($this->returnValue($out)); self::$DI['app']['EM'] = $this->getMockBuilder('Doctrine\ORM\EntityManager') diff --git a/tests/Alchemy/Tests/Phrasea/Form/Login/PhraseaAuthenticationWithMappingFormTest.php b/tests/Alchemy/Tests/Phrasea/Form/Login/PhraseaAuthenticationWithMappingFormTest.php deleted file mode 100644 index c4df17aa69..0000000000 --- a/tests/Alchemy/Tests/Phrasea/Form/Login/PhraseaAuthenticationWithMappingFormTest.php +++ /dev/null @@ -1,14 +0,0 @@ -assertCount(8, self::$DI['app']->form($form)->createView()->vars['form']->children); + $this->assertCount(9, self::$DI['app']->form($form)->createView()->vars['form']->children); } public function testFormDoesNotRegisterNonValidFields() @@ -75,6 +75,6 @@ class PhraseaRegisterFormTest extends FormTestCase $form = new PhraseaRegisterForm(self::$DI['app'], $available, $params, new Camelizer()); - $this->assertCount(7, self::$DI['app']->form($form)->createView()->vars['form']->children); + $this->assertCount(8, self::$DI['app']->form($form)->createView()->vars['form']->children); } } diff --git a/tests/classes/PhraseanetPHPUnitAbstract.php b/tests/classes/PhraseanetPHPUnitAbstract.php index 13f1927413..2894ea7975 100644 --- a/tests/classes/PhraseanetPHPUnitAbstract.php +++ b/tests/classes/PhraseanetPHPUnitAbstract.php @@ -910,6 +910,21 @@ abstract class PhraseanetPHPUnitAbstract extends WebTestCase $expectedMails[get_class($email)]++; })); } + + public function createRandomMock() + { + return $this->getMockBuilder('\random') + ->setMethods(array('generatePassword')) + ->disableOriginalConstructor() + ->getMock(); + } + + public function createAppboxMock() + { + return $this->getMockBuilder('appbox') + ->disableOriginalConstructor() + ->getMock(); + } } class CsrfTestProvider implements CsrfProviderInterface