From: Raphaƫl Gertz <git@rapsys.eu>
Date: Sun, 1 Jul 2018 07:27:20 +0000 (+0200)
Subject: First version
X-Git-Tag: 0.0.1
X-Git-Url: https://git.rapsys.eu/userbundle/commitdiff_plain/bab59a4b88a081a7a27a53b4559d74e63b68db92

First version
---

bab59a4b88a081a7a27a53b4559d74e63b68db92
diff --git a/Controller/DefaultController.php b/Controller/DefaultController.php
new file mode 100644
index 0000000..d3c2a3f
--- /dev/null
+++ b/Controller/DefaultController.php
@@ -0,0 +1,376 @@
+<?php
+
+namespace Rapsys\UserBundle\Controller;
+
+use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
+use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
+use Symfony\Component\Form\FormError;
+use Rapsys\UserBundle\Utils\Slugger;
+
+class DefaultController extends Controller {
+	public function loginAction(Request $request, AuthenticationUtils $authenticationUtils) {
+		//Get template
+		$template = $this->container->getParameter(($alias = $this->getAlias()).'.login.template');
+		//Get context
+		$context = $this->container->getParameter($alias.'.login.context');
+
+		//Create the form according to the FormType created previously.
+		//And give the proper parameters
+		$form = $this->createForm('Rapsys\UserBundle\Form\LoginType', null, array(
+			// To set the action use $this->generateUrl('route_identifier')
+			'action' => $this->generateUrl('rapsys_user_login'),
+			'method' => 'POST'
+		));
+
+		//Get the login error if there is one
+		if ($error = $authenticationUtils->getLastAuthenticationError()) {
+			//Get translator
+			$trans = $this->get('translator');
+
+			//Get translated error
+			$error = $trans->trans($error->getMessageKey());
+
+			//Add error message to mail field
+			$form->get('mail')->addError(new FormError($error));
+		}
+
+		//Last username entered by the user
+		if ($lastUsername = $authenticationUtils->getLastUsername()) {
+			$form->get('mail')->setData($lastUsername);
+		}
+
+		//Render view
+		return $this->render($template, $context+array('form' => $form->createView(), 'error' => $error));
+	}
+
+	public function registerAction(Request $request, UserPasswordEncoderInterface $encoder) {
+		//Get mail template
+		$mailTemplate = $this->container->getParameter(($alias = $this->getAlias()).'.register.mail_template');
+		//Get mail context
+		$mailContext = $this->container->getParameter($alias.'.register.mail_context');
+		//Get template
+		$template = $this->container->getParameter($alias.'.register.template');
+		//Get context
+		$context = $this->container->getParameter($alias.'.register.context');
+		//Get home name
+		$homeName = $this->container->getParameter($alias.'.contact.home_name');
+		//Get home args
+		$homeArgs = $this->container->getParameter($alias.'.contact.home_args');
+		//Get contact name
+		$contactName = $this->container->getParameter($alias.'.contact.name');
+		//Get contact mail
+		$contactMail = $this->container->getParameter($alias.'.contact.mail');
+		//TODO: check if doctrine orm replacement is enough with default classes here
+		//Get class user
+		$classUser = $this->container->getParameter($alias.'.class.user');
+		//Get class group
+		$classGroup = $this->container->getParameter($alias.'.class.group');
+		//Get class title
+		$classTitle = $this->container->getParameter($alias.'.class.title');
+
+		//Create the form according to the FormType created previously.
+		//And give the proper parameters
+		$form = $this->createForm('Rapsys\UserBundle\Form\RegisterType', null, array(
+			// To set the action use $this->generateUrl('route_identifier')
+			'class_title' => $classTitle,
+			'action' => $this->generateUrl('rapsys_user_register'),
+			'method' => 'POST'
+		));
+
+		if ($request->isMethod('POST')) {
+			// Refill the fields in case the form is not valid.
+			$form->handleRequest($request);
+
+			if ($form->isValid()) {
+				//Get translator
+				$trans = $this->get('translator');
+
+				//Set data
+				$data = $form->getData();
+
+				//Translate title
+				$mailContext['title'] = $trans->trans($mailContext['title']);
+
+				//Translate title
+				$mailContext['subtitle'] = $trans->trans($mailContext['subtitle'], array('%name%' => $data['forename'].' '.$data['surname'].' ('.$data['pseudonym'].')'));
+
+				//Translate subject
+				$mailContext['subject'] = $trans->trans($mailContext['subject'], array('%title%' => $mailContext['title']));
+
+				//Translate message
+				$mailContext['message'] = $trans->trans($mailContext['message'], array('%title%' => $mailContext['title']));
+
+				//Create message
+				$message = \Swift_Message::newInstance()
+					->setSubject($mailContext['subject'])
+					->setFrom(array($contactMail => $contactName))
+					->setTo(array($data['mail'] => $data['forename'].' '.$data['surname']))
+					->setBody($mailContext['message'])
+					->addPart(
+						$this->renderView(
+							$mailTemplate,
+							$mailContext+array(
+								'home' => $this->get('router')->generate($homeName, $homeArgs, UrlGeneratorInterface::ABSOLUTE_URL)
+							)
+						),
+						'text/html'
+					);
+
+				//Get doctrine
+				$doctrine = $this->getDoctrine();
+
+				//Get manager
+				$manager = $doctrine->getManager();
+
+				//Init reflection
+				$reflection = new \ReflectionClass($classUser);
+
+				//Create new user
+				$user = $reflection->newInstance();
+
+				$user->setMail($data['mail']);
+				$user->setPseudonym($data['pseudonym']);
+				$user->setForename($data['forename']);
+				$user->setSurname($data['surname']);
+				$user->setPassword($encoder->encodePassword($user, $data['password']));
+				$user->setActive(true);
+				$user->setTitle($data['title']);
+				//TODO: see if we can't modify group constructor to set role directly from args
+				//XXX: see vendor/symfony/symfony/src/Symfony/Component/Security/Core/Role/Role.php
+				$user->addGroup($doctrine->getRepository($classGroup)->findOneByRole('ROLE_USER'));
+				$user->setCreated(new \DateTime('now'));
+				$user->setUpdated(new \DateTime('now'));
+
+				//Persist user
+				$manager->persist($user);
+
+				try {
+					//Send to database
+					$manager->flush();
+
+					//Send message
+					if ($this->get('mailer')->send($message)) {
+						//Redirect to cleanup the form
+						return $this->redirectToRoute('rapsys_user_register', array('sent' => 1));
+					}
+				} catch (\Doctrine\DBAL\Exception\UniqueConstraintViolationException $e) {
+					//Add error message mail already exists
+					$form->get('mail')->addError(new FormError($trans->trans('Account already exists: %mail%', array('%mail%' => $data['mail']))));
+				}
+			}
+		}
+
+		//Render view
+		return $this->render($template, $context+array('form' => $form->createView(), 'sent' => $request->query->get('sent', 0)));
+	}
+
+	public function recoverAction(Request $request, Slugger $slugger) {
+		//Get mail template
+		$mailTemplate = $this->container->getParameter(($alias = $this->getAlias()).'.recover.mail_template');
+		//Get mail context
+		$mailContext = $this->container->getParameter($alias.'.recover.mail_context');
+		//Get template
+		$template = $this->container->getParameter($alias.'.recover.template');
+		//Get context
+		$context = $this->container->getParameter($alias.'.recover.context');
+		//Get url name
+		$urlName = $this->container->getParameter($alias.'.recover.url_name');
+		//Get url args
+		$urlArgs = $this->container->getParameter($alias.'.recover.url_args');
+		//Get home name
+		$homeName = $this->container->getParameter($alias.'.contact.home_name');
+		//Get home args
+		$homeArgs = $this->container->getParameter($alias.'.contact.home_args');
+		//Get contact name
+		$contactName = $this->container->getParameter($alias.'.contact.name');
+		//Get contact mail
+		$contactMail = $this->container->getParameter($alias.'.contact.mail');
+		//Get class user
+		$classUser = $this->container->getParameter($alias.'.class.user');
+
+		//Create the form according to the FormType created previously.
+		//And give the proper parameters
+		$form = $this->createForm('Rapsys\UserBundle\Form\RecoverType', null, array(
+			// To set the action use $this->generateUrl('route_identifier')
+			'action' => $this->generateUrl('rapsys_user_recover'),
+			'method' => 'POST'
+		));
+
+		if ($request->isMethod('POST')) {
+			// Refill the fields in case the form is not valid.
+			$form->handleRequest($request);
+
+			if ($form->isValid()) {
+				//Get translator
+				$trans = $this->get('translator');
+
+				//Get doctrine
+				$doctrine = $this->getDoctrine();
+
+				//Set data
+				$data = $form->getData();
+
+				//Translate title
+				$mailContext['title'] = $trans->trans($mailContext['title']);
+
+				//Try to find user
+				if ($user = $doctrine->getRepository($classUser)->findOneByMail($data['mail'])) {
+					//Translate title
+					$mailContext['subtitle'] = $trans->trans($mailContext['subtitle'], array('%name%' => $user->getForename().' '.$user->getSurname().' ('.$user->getPseudonym().')'));
+
+					//Translate subject
+					$mailContext['subject'] = $trans->trans($mailContext['subject'], array('%title%' => $mailContext['title']));
+
+					//Translate message
+					$mailContext['raw'] = $trans->trans($mailContext['raw'], array('%title%' => $mailContext['title'], '%url%' => $this->get('router')->generate($urlName, $urlArgs+array('mail' => $slugger->short($user->getMail()), 'hash' => $slugger->hash($user->getPassword())), UrlGeneratorInterface::ABSOLUTE_URL)));
+
+					//Create message
+					$message = \Swift_Message::newInstance()
+						->setSubject($mailContext['subject'])
+						->setFrom(array($contactMail => $contactName))
+						->setTo(array($user->getMail() => $user->getForename().' '.$user->getSurname()))
+						->setBody(strip_tags($mailContext['raw']))
+						->addPart(
+							$this->renderView(
+								$mailTemplate,
+								$mailContext+array(
+									'home' => $this->get('router')->generate($homeName, $homeArgs, UrlGeneratorInterface::ABSOLUTE_URL)
+								)
+							),
+							'text/html'
+						);
+
+					//Send message
+					if ($this->get('mailer')->send($message)) {
+						//Redirect to cleanup the form
+						return $this->redirectToRoute('rapsys_user_recover', array('sent' => 1));
+					}
+				//Accout not found
+				} else {
+					//Add error message to mail field
+					$form->get('mail')->addError(new FormError($trans->trans('Unable to find account: %mail%', array('%mail%' => $data['mail']))));
+				}
+			}
+		}
+
+		//Render view
+		return $this->render($template, $context+array('form' => $form->createView(), 'sent' => $request->query->get('sent', 0)));
+	}
+
+	public function recoverMailAction(Request $request, UserPasswordEncoderInterface $encoder, Slugger $slugger, $mail, $hash) {
+		//Get mail template
+		$mailTemplate = $this->container->getParameter(($alias = $this->getAlias()).'.recover_mail.mail_template');
+		//Get mail context
+		$mailContext = $this->container->getParameter($alias.'.recover_mail.mail_context');
+		//Get template
+		$template = $this->container->getParameter($alias.'.recover_mail.template');
+		//Get context
+		$context = $this->container->getParameter($alias.'.recover_mail.context');
+		//Get url name
+		$urlName = $this->container->getParameter($alias.'.recover_mail.url_name');
+		//Get url args
+		$urlArgs = $this->container->getParameter($alias.'.recover_mail.url_args');
+		//Get home name
+		$homeName = $this->container->getParameter($alias.'.contact.home_name');
+		//Get home args
+		$homeArgs = $this->container->getParameter($alias.'.contact.home_args');
+		//Get contact name
+		$contactName = $this->container->getParameter($alias.'.contact.name');
+		//Get contact mail
+		$contactMail = $this->container->getParameter($alias.'.contact.mail');
+		//Get class user
+		$classUser = $this->container->getParameter($alias.'.class.user');
+
+		//Create the form according to the FormType created previously.
+		//And give the proper parameters
+		$form = $this->createForm('Rapsys\UserBundle\Form\RecoverMailType', null, array(
+			// To set the action use $this->generateUrl('route_identifier')
+			'action' => $this->generateUrl('rapsys_user_recover_mail', array('mail' => $mail, 'hash' => $hash)),
+			'method' => 'POST'
+		));
+
+		//Get doctrine
+		$doctrine = $this->getDoctrine();
+
+		//Get translator
+		$trans = $this->get('translator');
+
+		//Init not found
+		$notfound = 1;
+
+		//Retrieve user
+		if (($user = $doctrine->getRepository($classUser)->findOneByMail($slugger->unshort($mail))) && $hash == $slugger->hash($user->getPassword())) {
+			//User was found
+			$notfound = 0;
+
+			if ($request->isMethod('POST')) {
+				// Refill the fields in case the form is not valid.
+				$form->handleRequest($request);
+
+				if ($form->isValid()) {
+					//Set data
+					$data = $form->getData();
+
+					//Translate title
+					$mailContext['title'] = $trans->trans($mailContext['title']);
+
+					//Translate title
+					$mailContext['subtitle'] = $trans->trans($mailContext['subtitle'], array('%name%' => $user->getForename().' '.$user->getSurname().' ('.$user->getPseudonym().')'));
+
+					//Translate subject
+					$mailContext['subject'] = $trans->trans($mailContext['subject'], array('%title%' => $mailContext['title']));
+
+					//Set user password
+					$user->setPassword($encoder->encodePassword($user, $data['password']));
+
+					//Translate message
+					$mailContext['raw'] = $trans->trans($mailContext['raw'], array('%title%' => $mailContext['title'], '%url%' => $this->get('router')->generate($urlName, $urlArgs+array('mail' => $slugger->short($user->getMail()), 'hash' => $slugger->hash($user->getPassword())), UrlGeneratorInterface::ABSOLUTE_URL)));
+
+					//Get manager
+					$manager = $doctrine->getManager();
+
+					//Persist user
+					$manager->persist($user);
+
+					//Send to database
+					$manager->flush();
+
+					//Create message
+					$message = \Swift_Message::newInstance()
+						->setSubject($mailContext['subject'])
+						->setFrom(array($contactMail => $contactName))
+						->setTo(array($user->getMail() => $user->getForename().' '.$user->getSurname()))
+						->setBody(strip_tags($mailContext['raw']))
+						->addPart(
+							$this->renderView(
+								$mailTemplate,
+								$mailContext+array(
+									'home' => $this->get('router')->generate($homeName, $homeArgs, UrlGeneratorInterface::ABSOLUTE_URL)
+								)
+							),
+							'text/html'
+						);
+
+					//Send message
+					if ($this->get('mailer')->send($message)) {
+						//Redirect to cleanup the form
+						return $this->redirectToRoute('rapsys_user_recover_mail', array('mail' => $mail, 'hash' => $hash, 'sent' => 1));
+					}
+				}
+			}
+		}
+
+		//Render view
+		return $this->render($template, $context+array('form' => $form->createView(), 'sent' => $request->query->get('sent', 0), 'notfound' => $notfound));
+	}
+
+	/**
+	 * {@inheritdoc}
+	 */
+	public function getAlias() {
+		return 'rapsys_user';
+	}
+}
diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php
new file mode 100644
index 0000000..14066d1
--- /dev/null
+++ b/DependencyInjection/Configuration.php
@@ -0,0 +1,210 @@
+<?php
+
+namespace Rapsys\UserBundle\DependencyInjection;
+
+use Symfony\Component\Config\Definition\Builder\TreeBuilder;
+use Symfony\Component\Config\Definition\ConfigurationInterface;
+
+/**
+ * This is the class that validates and merges configuration from your app/config files.
+ *
+ * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/configuration.html}
+ */
+class Configuration implements ConfigurationInterface {
+	/**
+	 * {@inheritdoc}
+	 */
+	public function getConfigTreeBuilder() {
+		//Set tree builder
+		$treeBuilder = new TreeBuilder();
+
+		//The bundle default values
+		$defaults = [
+	    		'class' => [
+				'group' => 'Rapsys\\UserBundle\\Entity\\Group',
+				'title' => 'Rapsys\\UserBundle\\Entity\\Title',
+				'user' => 'Rapsys\\UserBundle\\Entity\\User'
+			],
+			'contact' => [
+				'name' => 'John Doe',
+				'mail' => 'contact@example.com',
+				'home_name' => 'rapsys_user_homepage',
+				'home_args' => []
+			],
+			'login' => [
+				'template' => '@@RapsysUser/security/login.html.twig',
+				'context' => []
+			],
+			'register' => [
+				'mail_template' => '@@RapsysUser/mail/register.html.twig',
+				'mail_context' => [
+					'title' => 'Title',
+					'subtitle' => 'Hi, %%name%%',
+					'subject' => 'Welcome to %%title%%',
+					'message' => 'Thanks so much for joining us, from now on, you are part of %%title%%.'
+				],
+				'template' => '@@RapsysUser/security/register.html.twig',
+				'context' => []
+			],
+			'recover' => [
+				'mail_template' => '@@RapsysUser/mail/recover.html.twig',
+				'mail_context' => [
+					'title' => 'Title',
+					'subtitle' => 'Hi, %%name%%',
+					'subject' => 'Recover account on %%title%%',
+					'raw' => 'Thanks so much for joining us, to recover your account you can follow this link: <a href="%%url%%">%%url%%</a>'
+				],
+				'url_name' => 'rapsys_user_recover_mail',
+				'url_args' => [],
+				'template' => '@@RapsysUser/security/recover.html.twig',
+				'context' => []
+			],
+			'recover_mail' => [
+				'mail_template' => '@@RapsysUser/mail/recover.html.twig',
+				'mail_context' => [
+					'title' => 'Title',
+					'subtitle' => 'Hi, %%name%%',
+					'subject' => 'Account recovered on %%title%%',
+					'raw' => 'Your account password has been changed, to recover your account you can follow this link: <a href="%%url%%">%%url%%</a>'
+				],
+				'url_name' => 'rapsys_user_recover_mail',
+				'url_args' => [],
+				'template' => '@@RapsysUser/security/recover_mail.html.twig',
+				'context' => []
+			]
+		];
+
+		//Here we define the parameters that are allowed to configure the bundle.
+		//TODO: see https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php for default value and description
+		//TODO: see http://symfony.com/doc/current/components/config/definition.html
+		//TODO: see fosuser DependencyInjection/Configuration.php
+		//XXX: use bin/console config:dump-reference to dump class infos
+
+		//Here we define the parameters that are allowed to configure the bundle.
+		$treeBuilder
+			//Parameters
+			->root('parameters')
+				->addDefaultsIfNotSet()
+				->children()
+					->arrayNode('rapsys_user')
+						->addDefaultsIfNotSet()
+						->children()
+							->arrayNode('class')
+								->isRequired()
+								->addDefaultsIfNotSet()
+								->children()
+									->scalarNode('group')->isRequired()->defaultValue($defaults['class']['group'])->end()
+									->scalarNode('title')->isRequired()->defaultValue($defaults['class']['title'])->end()
+									->scalarNode('user')->isRequired()->defaultValue($defaults['class']['user'])->end()
+								->end()
+							->end()
+							->arrayNode('contact')
+								->isRequired()
+								->addDefaultsIfNotSet()
+								->children()
+									->scalarNode('name')->isRequired()->defaultValue($defaults['contact']['name'])->end()
+									->scalarNode('mail')->isRequired()->defaultValue($defaults['contact']['mail'])->end()
+									->scalarNode('home_name')->isRequired()->defaultValue($defaults['contact']['home_name'])->end()
+									->arrayNode('home_args')
+										->isRequired()
+										->treatNullLike($defaults['contact']['home_args'])
+										->defaultValue($defaults['contact']['home_args'])
+										->scalarPrototype()->end()
+									->end()
+								->end()
+							->end()
+							->arrayNode('login')
+								->isRequired()
+								->addDefaultsIfNotSet()
+								->children()
+									->scalarNode('template')->isRequired()->defaultValue($defaults['login']['template'])->end()
+									->arrayNode('context')
+										->isRequired()
+										->treatNullLike(array())
+										->defaultValue($defaults['login']['context'])
+										->scalarPrototype()->end()
+									->end()
+								->end()
+							->end()
+							->arrayNode('register')
+								->isRequired()
+								->addDefaultsIfNotSet()
+								->children()
+									->scalarNode('mail_template')->isRequired()->defaultValue($defaults['register']['mail_template'])->end()
+									->arrayNode('mail_context')
+										->isRequired()
+										->treatNullLike($defaults['register']['mail_context'])
+										->defaultValue($defaults['register']['mail_context'])
+										->scalarPrototype()->end()
+									->end()
+									->scalarNode('template')->isRequired()->defaultValue($defaults['register']['template'])->end()
+									->arrayNode('context')
+										->isRequired()
+										->treatNullLike($defaults['register']['context'])
+										->defaultValue($defaults['register']['context'])
+										->scalarPrototype()->end()
+									->end()
+								->end()
+							->end()
+							->arrayNode('recover')
+								->isRequired()
+								->addDefaultsIfNotSet()
+								->children()
+									->scalarNode('mail_template')->isRequired()->defaultValue($defaults['recover']['mail_template'])->end()
+									->arrayNode('mail_context')
+										->isRequired()
+										->treatNullLike($defaults['recover']['mail_context'])
+										->defaultValue($defaults['recover']['mail_context'])
+										->scalarPrototype()->end()
+									->end()
+									->scalarNode('url_name')->isRequired()->defaultValue($defaults['recover']['url_name'])->end()
+									->arrayNode('url_args')
+										->isRequired()
+										->treatNullLike($defaults['recover']['url_args'])
+										->defaultValue($defaults['recover']['url_args'])
+										->scalarPrototype()->end()
+									->end()
+									->scalarNode('template')->isRequired()->defaultValue($defaults['recover']['template'])->end()
+									->arrayNode('context')
+										->isRequired()
+										->treatNullLike(array())
+										->defaultValue($defaults['recover']['context'])
+										->scalarPrototype()->end()
+									->end()
+								->end()
+							->end()
+							->arrayNode('recover_mail')
+								->isRequired()
+								->addDefaultsIfNotSet()
+								->children()
+									->scalarNode('mail_template')->isRequired()->defaultValue($defaults['recover']['mail_template'])->end()
+									->arrayNode('mail_context')
+										->isRequired()
+										->treatNullLike($defaults['recover']['mail_context'])
+										->defaultValue($defaults['recover']['mail_context'])
+										->scalarPrototype()->end()
+									->end()
+									->scalarNode('url_name')->isRequired()->defaultValue($defaults['recover']['url_name'])->end()
+									->arrayNode('url_args')
+										->isRequired()
+										->treatNullLike($defaults['recover']['url_args'])
+										->defaultValue($defaults['recover']['url_args'])
+										->scalarPrototype()->end()
+									->end()
+									->scalarNode('template')->isRequired()->defaultValue($defaults['recover']['template'])->end()
+									->arrayNode('context')
+										->isRequired()
+										->treatNullLike(array())
+										->defaultValue($defaults['recover']['context'])
+										->scalarPrototype()->end()
+									->end()
+								->end()
+							->end()
+						->end()
+					->end()
+				->end()
+			->end();
+
+		return $treeBuilder;
+	}
+}
diff --git a/DependencyInjection/RapsysUserExtension.php b/DependencyInjection/RapsysUserExtension.php
new file mode 100644
index 0000000..58c86a6
--- /dev/null
+++ b/DependencyInjection/RapsysUserExtension.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace Rapsys\UserBundle\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\Config\FileLocator;
+use Symfony\Component\HttpKernel\DependencyInjection\Extension;
+use Symfony\Component\DependencyInjection\Loader;
+
+/**
+ * This is the class that loads and manages your bundle configuration.
+ *
+ * @link http://symfony.com/doc/current/cookbook/bundles/extension.html
+ */
+class RapsysUserExtension extends Extension {
+	/**
+	 * {@inheritdoc}
+	 */
+	public function load(array $configs, ContainerBuilder $container) {
+		//Load configuration
+		$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
+		$loader->load('services.yml');
+
+		//Load configuration
+		$configuration = $this->getConfiguration($configs, $container);
+		$config = $this->processConfiguration($configuration, $configs);
+
+		//Set default config in parameter
+		if (!$container->hasParameter($alias = $this->getAlias())) {
+			$container->setParameter($alias, $config[$alias]);
+		} else {
+			$config[$alias] = $container->getParameter($alias);
+		}
+
+		//Transform the two level tree in flat parameters
+		foreach($config[$alias] as $k => $v) {
+			foreach($v as $s => $d) {
+				//Set is as parameters
+				$container->setParameter($alias.'.'.$k.'.'.$s, $d);
+			}
+		}
+	}
+
+	/**
+	 * {@inheritdoc}
+	 */
+	public function getAlias() {
+		return 'rapsys_user';
+	}
+
+	/**
+	 * The function that parses the array to flatten it into a one level depth array
+	 *
+	 * @param $array	The config values array
+	 * @param $path		The current key path
+	 * @param $depth	The maxmium depth
+	 * @param $sep		The separator string
+	 */
+	/*protected function flatten($array, $path, $depth = 10, $sep = '.') {
+		//Init res
+		$res = array();
+
+		//Pass through non hashed or empty array
+		if ($depth && is_array($array) && ($array === [] || array_keys($array) === range(0, count($array) - 1))) {
+			$res[$path] = $array;
+		//Flatten hashed array
+		} elseif ($depth && is_array($array)) {
+			foreach($array as $k => $v) {
+				$sub = $path ? $path.$sep.$k:$k;
+				$res += $this->flatten($v, $sub, $depth - 1, $sep);
+			}
+		//Pass scalar value directly
+		} else {
+			$res[$path] = $array;
+		}
+
+		//Return result
+		return $res;
+	}*/
+}
diff --git a/Entity/Group.php b/Entity/Group.php
new file mode 100644
index 0000000..c7000ea
--- /dev/null
+++ b/Entity/Group.php
@@ -0,0 +1,137 @@
+<?php
+
+// src/Rapsys/UserBundle/Entity/Group.php
+namespace Rapsys\UserBundle\Entity;
+
+class Group extends \Symfony\Component\Security\Core\Role\Role {
+	/**
+	 * @var integer
+	 */
+	protected $id;
+
+	/**
+	 * @var string
+	 */
+	protected $role;
+
+	/**
+	 * @var \DateTime
+	 */
+	protected $created;
+
+	/**
+	 * @var \DateTime
+	 */
+	protected $updated;
+
+	/**
+	 * @var \Doctrine\Common\Collections\Collection
+	 */
+	protected $users;
+
+	/**
+	 * Constructor
+	 * @param string $role The role name
+	 */
+	public function __construct($role) {
+		$this->role = (string) $role;
+		$this->users = new \Doctrine\Common\Collections\ArrayCollection();
+	}
+
+	/**
+	 * Set role
+	 *
+	 * @param string $role
+	 *
+	 * @return User
+	 */
+	public function setRole($role) {
+		$this->role = $role;
+
+		return $this;
+	}
+
+	/**
+	 * Get role
+	 *
+	 * @return string
+	 */
+	public function getRole() {
+		return $this->role;
+	}
+
+	/**
+	 * Set created
+	 *
+	 * @param \DateTime $created
+	 *
+	 * @return User
+	 */
+	public function setCreated($created) {
+		$this->created = $created;
+
+		return $this;
+	}
+
+	/**
+	 * Get created
+	 *
+	 * @return \DateTime
+	 */
+	public function getCreated() {
+		return $this->created;
+	}
+
+	/**
+	 * Set updated
+	 *
+	 * @param \DateTime $updated
+	 *
+	 * @return User
+	 */
+	public function setUpdated($updated) {
+		$this->updated = $updated;
+
+		return $this;
+	}
+
+	/**
+	 * Get updated
+	 *
+	 * @return \DateTime
+	 */
+	public function getUpdated() {
+		return $this->updated;
+	}
+
+	/**
+	 * Add user
+	 *
+	 * @param \Rapsys\UserBundle\Entity\User $user
+	 *
+	 * @return Group
+	 */
+	public function addUser(\Rapsys\UserBundle\Entity\User $user) {
+		$this->users[] = $user;
+
+		return $this;
+	}
+
+	/**
+	 * Remove user
+	 *
+	 * @param \Rapsys\UserBundle\Entity\User $user
+	 */
+	public function removeUser(\Rapsys\UserBundle\Entity\User $user) {
+		$this->users->removeElement($user);
+	}
+
+	/**
+	 * Get users
+	 *
+	 * @return \Doctrine\Common\Collections\Collection
+	 */
+	public function getUsers() {
+		return $this->users;
+	}
+}
diff --git a/Entity/Title.php b/Entity/Title.php
new file mode 100644
index 0000000..5a2b685
--- /dev/null
+++ b/Entity/Title.php
@@ -0,0 +1,173 @@
+<?php
+
+namespace Rapsys\UserBundle\Entity;
+
+/**
+ * Title
+ */
+class Title {
+	/**
+	 * @var integer
+	 */
+	protected $id;
+
+	/**
+	 * @var string
+	 */
+	protected $short;
+
+	/**
+	 * @var string
+	 */
+	protected $title;
+
+	/**
+	 * @var \DateTime
+	 */
+	protected $created;
+
+	/**
+	 * @var \DateTime
+	 */
+	protected $updated;
+
+	/**
+	 * @var \Doctrine\Common\Collections\Collection
+	 */
+	protected $users;
+
+	/**
+	 * Constructor
+	 */
+	public function __construct() {
+		$this->users = new \Doctrine\Common\Collections\ArrayCollection();
+	}
+
+	/**
+	 * Get id
+	 *
+	 * @return integer
+	 */
+	public function getId() {
+		return $this->id;
+	}
+
+	/**
+	 * Set short
+	 *
+	 * @param string $short
+	 *
+	 * @return Title
+	 */
+	public function setShort($short) {
+		$this->short = $short;
+
+		return $this;
+	}
+
+	/**
+	 * Get short
+	 *
+	 * @return string
+	 */
+	public function getShort() {
+		return $this->short;
+	}
+
+	/**
+	 * Set title
+	 *
+	 * @param string $title
+	 *
+	 * @return Title
+	 */
+	public function setTitle($title) {
+		$this->title = $title;
+
+		return $this;
+	}
+
+	/**
+	 * Get title
+	 *
+	 * @return string
+	 */
+	public function getTitle() {
+		return $this->title;
+	}
+
+	/**
+	 * Set created
+	 *
+	 * @param \DateTime $created
+	 *
+	 * @return Title
+	 */
+	public function setCreated($created) {
+		$this->created = $created;
+
+		return $this;
+	}
+
+	/**
+	 * Get created
+	 *
+	 * @return \DateTime
+	 */
+	public function getCreated() {
+		return $this->created;
+	}
+
+	/**
+	 * Set updated
+	 *
+	 * @param \DateTime $updated
+	 *
+	 * @return Title
+	 */
+	public function setUpdated($updated) {
+		$this->updated = $updated;
+
+		return $this;
+	}
+
+	/**
+	 * Get updated
+	 *
+	 * @return \DateTime
+	 */
+	public function getUpdated() {
+		return $this->updated;
+	}
+
+	/**
+	 * Add user
+	 *
+	 * @param \Rapsys\UserBundle\Entity\User $user
+	 *
+	 * @return Title
+	 */
+	public function addUser(\Rapsys\UserBundle\Entity\User $user) {
+		$this->users[] = $user;
+
+		return $this;
+	}
+
+	/**
+	 * Remove user
+	 *
+	 * @param \Rapsys\UserBundle\Entity\User $user
+	 */
+	public function removeUser(\Rapsys\UserBundle\Entity\User $user) {
+		$this->users->removeElement($user);
+	}
+
+	/**
+	 * Get users
+	 *
+	 * @return \Doctrine\Common\Collections\Collection
+	 */
+	public function getUsers() {
+		return $this->users;
+	}
+}
diff --git a/Entity/User.php b/Entity/User.php
new file mode 100644
index 0000000..c6a6cdf
--- /dev/null
+++ b/Entity/User.php
@@ -0,0 +1,355 @@
+<?php
+
+// src/Rapsys/UserBundle/Entity/User.php
+namespace Rapsys\UserBundle\Entity;
+
+class User implements \Symfony\Component\Security\Core\User\AdvancedUserInterface, \Serializable {
+	/**
+	 * @var integer
+	 */
+	protected $id;
+
+	/**
+	 * @var string
+	 */
+	protected $mail;
+
+	/**
+	 * @var string
+	 */
+	protected $pseudonym;
+
+	/**
+	 * @var string
+	 */
+	protected $forename;
+
+	/**
+	 * @var string
+	 */
+	protected $surname;
+
+	/**
+	 * @var string
+	 */
+	protected $password;
+
+	/**
+	 * @var bool
+	 */
+	protected $active;
+
+	/**
+	 * @var \DateTime
+	 */
+	protected $created;
+
+	/**
+	 * @var \DateTime
+	 */
+	protected $updated;
+
+	/**
+	 * @var \Rapsys\UserBundle\Entity\Title
+	 */
+	protected $title;
+
+	/**
+	 * @var \Doctrine\Common\Collections\Collection
+	 */
+	protected $groups;
+
+	/**
+	 * User constructor.
+	 */
+	public function __construct() {
+		$this->active = false;
+		$this->groups = new \Doctrine\Common\Collections\ArrayCollection();
+	}
+
+	/**
+	 * Get id
+	 *
+	 * @return integer
+	 */
+	public function getId() {
+		return $this->id;
+	}
+
+	/**
+	 * Set mail
+	 *
+	 * @param string $mail
+	 *
+	 * @return User
+	 */
+	public function setMail($mail) {
+		$this->mail = $mail;
+
+		return $this;
+	}
+
+	/**
+	 * Get mail
+	 *
+	 * @return string
+	 */
+	public function getMail() {
+		return $this->mail;
+	}
+
+	/**
+	 * Set pseudonym
+	 *
+	 * @param string $pseudonym
+	 *
+	 * @return User
+	 */
+	public function setPseudonym($pseudonym) {
+		$this->pseudonym = $pseudonym;
+
+		return $this;
+	}
+
+	/**
+	 * Get pseudonym
+	 *
+	 * @return string
+	 */
+	public function getPseudonym() {
+		return $this->pseudonym;
+	}
+
+	/**
+	 * Set forename
+	 *
+	 * @param string $forename
+	 *
+	 * @return User
+	 */
+	public function setForename($forename) {
+		$this->forename = $forename;
+
+		return $this;
+	}
+
+	/**
+	 * Get forename
+	 *
+	 * @return string
+	 */
+	public function getForename() {
+		return $this->forename;
+	}
+
+	/**
+	 * Set surname
+	 *
+	 * @param string $surname
+	 *
+	 * @return User
+	 */
+	public function setSurname($surname) {
+		$this->surname = $surname;
+
+		return $this;
+	}
+
+	/**
+	 * Get surname
+	 *
+	 * @return string
+	 */
+	public function getSurname() {
+		return $this->surname;
+	}
+
+	/**
+	 * Set password
+	 *
+	 * @param string $password
+	 *
+	 * @return User
+	 */
+	public function setPassword($password) {
+		$this->password = $password;
+
+		return $this;
+	}
+
+	/**
+	 * Get password
+	 *
+	 * @return string
+	 */
+	public function getPassword() {
+		return $this->password;
+	}
+
+	/**
+	 * Set active
+	 *
+	 * @param bool $active
+	 *
+	 * @return User
+	 */
+	public function setActive($active) {
+		$this->active = $active;
+
+		return $this;
+	}
+
+	/**
+	 * Get active
+	 *
+	 * @return bool
+	 */
+	public function getActive() {
+		return $this->active;
+	}
+
+	/**
+	 * Set created
+	 *
+	 * @param \DateTime $created
+	 *
+	 * @return User
+	 */
+	public function setCreated($created) {
+		$this->created = $created;
+
+		return $this;
+	}
+
+	/**
+	 * Get created
+	 *
+	 * @return \DateTime
+	 */
+	public function getCreated() {
+		return $this->created;
+	}
+
+	/**
+	 * Set updated
+	 *
+	 * @param \DateTime $updated
+	 *
+	 * @return User
+	 */
+	public function setUpdated($updated) {
+		$this->updated = $updated;
+
+		return $this;
+	}
+
+	/**
+	 * Get updated
+	 *
+	 * @return \DateTime
+	 */
+	public function getUpdated() {
+		return $this->updated;
+	}
+
+	/**
+	 * Set title
+	 */
+	public function setTitle($title) {
+		$this->title = $title;
+
+		return $this;
+	}
+
+	/**
+	 * Get title
+	 */
+	public function getTitle() {
+		return $this->title;
+	}
+
+	/**
+	 * Add group
+	 *
+	 * @param \Rapsys\UserBundle\Entity\Group $group
+	 *
+	 * @return User
+	 */
+	public function addGroup(\Rapsys\UserBundle\Entity\Group $group) {
+		$this->groups[] = $group;
+
+		return $this;
+	}
+
+	/**
+	 * Remove group
+	 *
+	 * @param \Rapsys\UserBundle\Entity\Group $group
+	 */
+	public function removeGroup(\Rapsys\UserBundle\Entity\Group $group) {
+		$this->groups->removeElement($group);
+	}
+
+	/**
+	 * Get groups
+	 *
+	 * @return \Doctrine\Common\Collections\Collection
+	 */
+	public function getGroups() {
+		return $this->groups;
+	}
+
+	public function getRoles() {
+		return $this->groups->toArray();
+	}
+
+	public function getSalt() {
+		//No salt required with bcrypt
+		return null;
+	}
+
+	public function getUsername() {
+		return $this->mail;
+	}
+
+	public function eraseCredentials() {
+	}
+
+	public function serialize() {
+		return serialize(array(
+			$this->id,
+			$this->mail,
+			$this->password,
+			$this->active,
+			$this->created,
+			$this->updated
+		));
+	}
+
+	public function unserialize($serialized) {
+		list(
+			$this->id,
+			$this->mail,
+			$this->password,
+			$this->active,
+			$this->created,
+			$this->updated
+		) = unserialize($serialized);
+	}
+
+	public function isAccountNonExpired() {
+		return true;
+	}
+
+	public function isAccountNonLocked() {
+		return true;
+	}
+
+	public function isCredentialsNonExpired() {
+		return true;
+	}
+
+	public function isEnabled() {
+		return $this->active;
+	}
+}
diff --git a/Form/LoginType.php b/Form/LoginType.php
new file mode 100644
index 0000000..f3df87a
--- /dev/null
+++ b/Form/LoginType.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Rapsys\UserBundle\Form;
+
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Component\Form\Extension\Core\Type\PasswordType;
+use Symfony\Component\Form\Extension\Core\Type\EmailType;
+use Symfony\Component\Form\Extension\Core\Type\SubmitType;
+use Symfony\Component\Validator\Constraints\Email;
+use Symfony\Component\Validator\Constraints\NotBlank;
+
+class LoginType extends AbstractType {
+	/**
+	 * {@inheritdoc}
+	 */
+	public function buildForm(FormBuilderInterface $builder, array $options) {
+		return $builder->add('mail', EmailType::class, array('attr' => array('placeholder' => 'Your mail address'), 'constraints' => array(new NotBlank(array('message' => 'Please provide your mail')), new Email(array('message' => 'Your mail doesn\'t seems to be valid')))))
+			->add('password', PasswordType::class, array('attr' => array('placeholder' => 'Your password'), 'constraints' => array(new NotBlank(array("message" => "Please provide your password")))))
+			->add('submit', SubmitType::class, array('label' => 'Send', 'attr' => array('class' => 'submit')));
+	}
+
+	/**
+	 * {@inheritdoc}
+	 */
+	public function configureOptions(OptionsResolver $resolver) {
+		$resolver->setDefaults(['error_bubbling' => true]);
+	}
+
+	/**
+	 * {@inheritdoc}
+	 */
+	public function getName() {
+		return 'rapsys_user_login';
+	}
+}
diff --git a/Form/RecoverMailType.php b/Form/RecoverMailType.php
new file mode 100644
index 0000000..c39c624
--- /dev/null
+++ b/Form/RecoverMailType.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Rapsys\UserBundle\Form;
+
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
+use Symfony\Component\Form\Extension\Core\Type\PasswordType;
+use Symfony\Component\Form\Extension\Core\Type\SubmitType;
+use Symfony\Component\Validator\Constraints\NotBlank;
+
+class RecoverMailType extends AbstractType {
+	/**
+	 * {@inheritdoc}
+	 */
+	public function buildForm(FormBuilderInterface $builder, array $options) {
+		return $builder->add('password', RepeatedType::class, array('type' => PasswordType::class, 'invalid_message' => 'The password and confirmation must match', 'first_options' => array('attr' => array('placeholder' => 'Your password'), 'label' => 'Password'), 'second_options' => array('attr' => array('placeholder' => 'Your password confirmation'), 'label' => 'Confirm password'), 'options' => array('constraints' => array(new NotBlank(array('message' => 'Please provide your password'))))))
+			->add('submit', SubmitType::class, array('label' => 'Send', 'attr' => array('class' => 'submit')));
+	}
+
+	/**
+	 * {@inheritdoc}
+	 */
+	public function configureOptions(OptionsResolver $resolver) {
+		$resolver->setDefaults(['error_bubbling' => true]);
+	}
+
+	/**
+	 * {@inheritdoc}
+	 */
+	public function getName() {
+		return 'rapsys_user_recover_mail';
+	}
+}
diff --git a/Form/RecoverType.php b/Form/RecoverType.php
new file mode 100644
index 0000000..3c14599
--- /dev/null
+++ b/Form/RecoverType.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Rapsys\UserBundle\Form;
+
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Component\Form\Extension\Core\Type\PasswordType;
+use Symfony\Component\Form\Extension\Core\Type\EmailType;
+use Symfony\Component\Form\Extension\Core\Type\SubmitType;
+use Symfony\Component\Validator\Constraints\Email;
+use Symfony\Component\Validator\Constraints\NotBlank;
+
+class RecoverType extends AbstractType {
+	/**
+	 * {@inheritdoc}
+	 */
+	public function buildForm(FormBuilderInterface $builder, array $options) {
+		return $builder->add('mail', EmailType::class, array('attr' => array('placeholder' => 'Your mail address'), 'constraints' => array(new NotBlank(array('message' => 'Please provide your mail')), new Email(array('message' => 'Your mail doesn\'t seems to be valid')))))
+			->add('submit', SubmitType::class, array('label' => 'Send', 'attr' => array('class' => 'submit')));
+	}
+
+	/**
+	 * {@inheritdoc}
+	 */
+	public function configureOptions(OptionsResolver $resolver) {
+		$resolver->setDefaults(['error_bubbling' => true]);
+	}
+
+	/**
+	 * {@inheritdoc}
+	 */
+	public function getName() {
+		return 'rapsys_user_recover';
+	}
+}
diff --git a/Form/RegisterType.php b/Form/RegisterType.php
new file mode 100644
index 0000000..6e68583
--- /dev/null
+++ b/Form/RegisterType.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Rapsys\UserBundle\Form;
+
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Bridge\Doctrine\Form\Type\EntityType;
+use Symfony\Component\Form\Extension\Core\Type\EmailType;
+use Symfony\Component\Form\Extension\Core\Type\PasswordType;
+use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
+use Symfony\Component\Form\Extension\Core\Type\SubmitType;
+use Symfony\Component\Form\Extension\Core\Type\TextType;
+use Symfony\Component\Validator\Constraints\Email;
+use Symfony\Component\Validator\Constraints\NotBlank;
+
+class RegisterType extends AbstractType {
+	/**
+	 * {@inheritdoc}
+	 */
+	public function buildForm(FormBuilderInterface $builder, array $options) {
+		return $builder->add('mail', EmailType::class, array('attr' => array('placeholder' => 'Your mail address'), 'constraints' => array(new NotBlank(array('message' => 'Please provide your mail')), new Email(array('message' => 'Your mail doesn\'t seems to be valid')))))
+			#'RapsysUserBundle:Title'
+			->add('title', EntityType::class, array('class' => $options['class_title'], 'choice_label' => 'title', 'attr' => array('placeholder' => 'Your title'), 'constraints' => array(new NotBlank(array('message' => 'Please provide your title')))))
+			->add('pseudonym', TextType::class, array('attr' => array('placeholder' => 'Your pseudonym'), 'constraints' => array(new NotBlank(array('message' => 'Please provide your pseudonym')))))
+			->add('forename', TextType::class, array('attr' => array('placeholder' => 'Your forename'), 'constraints' => array(new NotBlank(array('message' => 'Please provide your forename')))))
+			->add('surname', TextType::class, array('attr' => array('placeholder' => 'Your surname'), 'constraints' => array(new NotBlank(array('message' => 'Please provide your surname')))))
+			->add('password', RepeatedType::class, array('type' => PasswordType::class, 'invalid_message' => 'The password and confirmation must match', 'first_options' => array('attr' => array('placeholder' => 'Your password'), 'label' => 'Password'), 'second_options' => array('attr' => array('placeholder' => 'Your password confirmation'), 'label' => 'Confirm password'), 'options' => array('constraints' => array(new NotBlank(array('message' => 'Please provide your password'))))))
+			->add('submit', SubmitType::class, array('label' => 'Send', 'attr' => array('class' => 'submit')));
+	}
+
+	/**
+	 * {@inheritdoc}
+	 */
+	public function configureOptions(OptionsResolver $resolver) {
+		$resolver->setDefaults(['error_bubbling' => true]);
+		$resolver->setRequired('class_title');
+		$resolver->setAllowedTypes('class_title', 'string');
+	}
+
+	/**
+	 * {@inheritdoc}
+	 */
+	public function getName() {
+		return 'rapsys_user_register';
+	}
+}
diff --git a/RapsysUserBundle.php b/RapsysUserBundle.php
new file mode 100644
index 0000000..c083bb0
--- /dev/null
+++ b/RapsysUserBundle.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Rapsys\UserBundle;
+
+use Symfony\Component\HttpKernel\Bundle\Bundle;
+
+class RapsysUserBundle extends Bundle {}
diff --git a/Resources/config/doctrine/Group.orm.yml b/Resources/config/doctrine/Group.orm.yml
new file mode 100644
index 0000000..6dfe856
--- /dev/null
+++ b/Resources/config/doctrine/Group.orm.yml
@@ -0,0 +1,21 @@
+Rapsys\UserBundle\Entity\Group:
+    type: mappedSuperclass
+    id:
+        id:
+            type: integer
+            generator: 
+                strategy: AUTO
+            options:
+                unsigned: true
+    fields:
+        role:
+            type: string
+            length: 20
+        created:
+            type: datetime
+        updated:
+            type: datetime
+#    manyToMany:
+#        users:
+#            targetEntity: User
+#            mappedBy: groups
diff --git a/Resources/config/doctrine/Title.orm.yml b/Resources/config/doctrine/Title.orm.yml
new file mode 100644
index 0000000..389e89d
--- /dev/null
+++ b/Resources/config/doctrine/Title.orm.yml
@@ -0,0 +1,24 @@
+Rapsys\UserBundle\Entity\Title:
+    type: mappedSuperclass
+    id:
+        id:
+            type: integer
+            generator: 
+                strategy: AUTO
+            options:
+                unsigned: true
+    fields:
+        short:
+            type: string
+            length: 4
+        title:
+            type: string
+            length: 16
+        created:
+            type: datetime
+        updated:
+            type: datetime
+#    oneToMany:
+#        users:
+#            targetEntity: User
+#            mappedBy: title
diff --git a/Resources/config/doctrine/User.orm.yml b/Resources/config/doctrine/User.orm.yml
new file mode 100644
index 0000000..a6a37f3
--- /dev/null
+++ b/Resources/config/doctrine/User.orm.yml
@@ -0,0 +1,45 @@
+Rapsys\UserBundle\Entity\User:
+    type: mappedSuperclass
+    id:
+        id:
+            type: integer
+            generator: 
+                strategy: AUTO
+            options:
+                unsigned: true
+    fields:
+        mail:
+            type: string
+            unique: true
+            length: 254
+        pseudonym:
+            type: string
+            length: 32
+        forename:
+            type: string
+            length: 32
+        surname:
+            type: string
+            length: 32
+        password:
+            type: string
+            length: 60
+        active:
+            type: boolean
+            options:
+                default: true
+        created:
+            type: datetime
+        updated:
+            type: datetime
+    manyToOne:
+        title:
+            targetEntity: Title
+            inversedBy: users
+    manyToMany:
+        groups:
+            targetEntity: Group
+            inversedBy: users
+#see if usefull: https://stackoverflow.com/questions/34523699/how-to-extend-doctrine-entity-in-another-bundle
+#            joinTable:
+#                name: groups_users
diff --git a/Resources/config/routing.yml b/Resources/config/routing.yml
new file mode 100644
index 0000000..6962599
--- /dev/null
+++ b/Resources/config/routing.yml
@@ -0,0 +1,21 @@
+rapsys_user_login:
+    path:     /login
+    defaults: { _controller: RapsysUserBundle:Default:login }
+
+rapsys_user_recover:
+    path:     /recover
+    defaults: { _controller: RapsysUserBundle:Default:recover }
+
+rapsys_user_recover_mail:
+    path:     /recover/{mail}/{hash}
+    defaults: { _controller: RapsysUserBundle:Default:recoverMail }
+
+rapsys_user_register:
+    path:     /register
+    defaults: { _controller: RapsysUserBundle:Default:register }
+
+rapsys_user_logout:
+    path:     /logout
+
+rapsys_user_homepage:
+    path:     /
diff --git a/Resources/config/services.yml b/Resources/config/services.yml
new file mode 100644
index 0000000..3e9742b
--- /dev/null
+++ b/Resources/config/services.yml
@@ -0,0 +1,8 @@
+services:
+    Rapsys\UserBundle\Controller\DefaultController:
+        tags: [ controller.service_arguments ]
+    Rapsys\UserBundle\Utils\Slugger:
+        arguments: [ "@service_container" ]
+#    rapsys_user.example:
+#        class: Rapsys\UserBundle\Example
+#        arguments: ["@service_id", "plain_value", "%parameter%"]
diff --git a/Utils/Slugger.php b/Utils/Slugger.php
new file mode 100644
index 0000000..f3d8e25
--- /dev/null
+++ b/Utils/Slugger.php
@@ -0,0 +1,84 @@
+<?php
+
+namespace Rapsys\UserBundle\Utils;
+
+class Slugger {
+	//The secret parameter
+	private $secret;
+
+	//The offset reduced from secret
+	private $offset;
+
+	//Retrieve secret and set offset from reduction
+	public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container) {
+		//Set secret
+		$this->secret = $container->getParameter('secret');
+
+		//Init rev array
+		$rev = array_flip(array_merge(range('0', '9'), range('a', 'z'), range('A', 'Z'), range('!', '~')));
+
+		//Set offset
+		$this->offset = array_reduce(str_split($this->secret), function ($res, $a) use ($rev) { return $res += $rev[$a]; }, count($this->secret)) % count($rev);
+	}
+
+	//Short the string
+	public function short($string) {
+		//Return string
+		$ret = '';
+
+		//Alphabet
+		$alpha = array_merge(range('0', '9'), range('a', 'z'), range('A', 'Z'), range('!', '~'));
+
+		//Reverse alphabet
+		$rev = array_flip($alpha);
+
+		//Number characters
+		$count = count($alpha);
+
+		//Iterate on each character
+		foreach(str_split($string) as $c) {
+			if (isset($rev[$c]) && isset($alpha[($rev[$c]+$this->offset)%$count])) {
+				$ret .= $alpha[($rev[$c]+$this->offset)%$count];
+			}
+		}
+
+		//Send result
+		return str_replace(array('+','/'), array('-','_'), base64_encode($ret));
+	}
+
+	//Unshort the string
+	public function unshort($string) {
+		//Return string
+		$ret = '';
+
+		//Alphabet
+		$alpha = array_merge(range('0', '9'), range('a', 'z'), range('A', 'Z'), range('!', '~'));
+
+		//Reverse alphabet
+		$rev = array_flip($alpha);
+
+		//Number characters
+		$count = count($alpha);
+
+		//Iterate on each character
+		foreach(str_split(base64_decode(str_replace(array('-','_'), array('+','/'), $string))) as $c) {
+			if (isset($rev[$c]) && isset($alpha[($rev[$c]-$this->offset+$count)%$count])) {
+				$ret .= $alpha[($rev[$c]-$this->offset+$count)%$count];
+			}
+		}
+
+		//Send result
+		return $ret;
+	}
+
+	//Crypt and base64uri encode string
+	public function hash($string) {
+		return str_replace(array('+','/'), array('-','_'), base64_encode(crypt($string, $this->secret)));
+	}
+
+	//Convert string to safe slug
+	function slug($string) {
+		return preg_replace('/[\/_|+ -]+/', '-', strtolower(trim(preg_replace('/[^a-zA-Z0-9\/_|+ -]/', '', str_replace(array('\'', '"'), ' ', iconv('UTF-8', 'ASCII//TRANSLIT', $string))), '-')));
+	}
+
+}