aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/Wallabag/UserBundle
diff options
context:
space:
mode:
Diffstat (limited to 'src/Wallabag/UserBundle')
-rw-r--r--src/Wallabag/UserBundle/Controller/ManageController.php71
-rw-r--r--src/Wallabag/UserBundle/Entity/User.php64
-rw-r--r--src/Wallabag/UserBundle/EventListener/AuthenticationFailureListener.php40
-rw-r--r--src/Wallabag/UserBundle/EventListener/CreateConfigListener.php3
-rw-r--r--src/Wallabag/UserBundle/Form/SearchUserType.php29
-rw-r--r--src/Wallabag/UserBundle/Repository/UserRepository.php13
-rw-r--r--src/Wallabag/UserBundle/Resources/config/services.yml10
-rw-r--r--src/Wallabag/UserBundle/Resources/views/Manage/index.html.twig77
8 files changed, 247 insertions, 60 deletions
diff --git a/src/Wallabag/UserBundle/Controller/ManageController.php b/src/Wallabag/UserBundle/Controller/ManageController.php
index 92ee2b41..084f2c67 100644
--- a/src/Wallabag/UserBundle/Controller/ManageController.php
+++ b/src/Wallabag/UserBundle/Controller/ManageController.php
@@ -4,12 +4,15 @@ namespace Wallabag\UserBundle\Controller;
4 4
5use FOS\UserBundle\Event\UserEvent; 5use FOS\UserBundle\Event\UserEvent;
6use FOS\UserBundle\FOSUserEvents; 6use FOS\UserBundle\FOSUserEvents;
7use Pagerfanta\Adapter\DoctrineORMAdapter;
8use Pagerfanta\Exception\OutOfRangeCurrentPageException;
9use Pagerfanta\Pagerfanta;
7use Symfony\Component\HttpFoundation\Request; 10use Symfony\Component\HttpFoundation\Request;
8use Symfony\Bundle\FrameworkBundle\Controller\Controller; 11use Symfony\Bundle\FrameworkBundle\Controller\Controller;
9use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; 12use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
10use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; 13use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
11use Wallabag\UserBundle\Entity\User; 14use Wallabag\UserBundle\Entity\User;
12use Wallabag\CoreBundle\Entity\Config; 15use Wallabag\UserBundle\Form\SearchUserType;
13 16
14/** 17/**
15 * User controller. 18 * User controller.
@@ -17,23 +20,6 @@ use Wallabag\CoreBundle\Entity\Config;
17class ManageController extends Controller 20class ManageController extends Controller
18{ 21{
19 /** 22 /**
20 * Lists all User entities.
21 *
22 * @Route("/", name="user_index")
23 * @Method("GET")
24 */
25 public function indexAction()
26 {
27 $em = $this->getDoctrine()->getManager();
28
29 $users = $em->getRepository('WallabagUserBundle:User')->findAll();
30
31 return $this->render('WallabagUserBundle:Manage:index.html.twig', array(
32 'users' => $users,
33 ));
34 }
35
36 /**
37 * Creates a new User entity. 23 * Creates a new User entity.
38 * 24 *
39 * @Route("/new", name="user_new") 25 * @Route("/new", name="user_new")
@@ -47,9 +33,7 @@ class ManageController extends Controller
47 // enable created user by default 33 // enable created user by default
48 $user->setEnabled(true); 34 $user->setEnabled(true);
49 35
50 $form = $this->createForm('Wallabag\UserBundle\Form\NewUserType', $user, [ 36 $form = $this->createForm('Wallabag\UserBundle\Form\NewUserType', $user);
51 'validation_groups' => ['Profile'],
52 ]);
53 $form->handleRequest($request); 37 $form->handleRequest($request);
54 38
55 if ($form->isSubmitted() && $form->isValid()) { 39 if ($form->isSubmitted() && $form->isValid()) {
@@ -146,4 +130,49 @@ class ManageController extends Controller
146 ->getForm() 130 ->getForm()
147 ; 131 ;
148 } 132 }
133
134 /**
135 * @param Request $request
136 * @param int $page
137 *
138 * @Route("/list/{page}", name="user_index", defaults={"page" = 1})
139 *
140 * Default parameter for page is hardcoded (in duplication of the defaults from the Route)
141 * because this controller is also called inside the layout template without any page as argument
142 *
143 * @return \Symfony\Component\HttpFoundation\Response
144 */
145 public function searchFormAction(Request $request, $page = 1)
146 {
147 $em = $this->getDoctrine()->getManager();
148 $qb = $em->getRepository('WallabagUserBundle:User')->createQueryBuilder('u');
149
150 $form = $this->createForm(SearchUserType::class);
151 $form->handleRequest($request);
152
153 if ($form->isSubmitted() && $form->isValid()) {
154 $this->get('logger')->info('searching users');
155
156 $searchTerm = (isset($request->get('search_user')['term']) ? $request->get('search_user')['term'] : '');
157
158 $qb = $em->getRepository('WallabagUserBundle:User')->getQueryBuilderForSearch($searchTerm);
159 }
160
161 $pagerAdapter = new DoctrineORMAdapter($qb->getQuery(), true, false);
162 $pagerFanta = new Pagerfanta($pagerAdapter);
163 $pagerFanta->setMaxPerPage(50);
164
165 try {
166 $pagerFanta->setCurrentPage($page);
167 } catch (OutOfRangeCurrentPageException $e) {
168 if ($page > 1) {
169 return $this->redirect($this->generateUrl('user_index', ['page' => $pagerFanta->getNbPages()]), 302);
170 }
171 }
172
173 return $this->render('WallabagUserBundle:Manage:index.html.twig', [
174 'searchForm' => $form->createView(),
175 'users' => $pagerFanta,
176 ]);
177 }
149} 178}
diff --git a/src/Wallabag/UserBundle/Entity/User.php b/src/Wallabag/UserBundle/Entity/User.php
index 3a167de7..aba76ca7 100644
--- a/src/Wallabag/UserBundle/Entity/User.php
+++ b/src/Wallabag/UserBundle/Entity/User.php
@@ -4,11 +4,12 @@ namespace Wallabag\UserBundle\Entity;
4 4
5use Doctrine\Common\Collections\ArrayCollection; 5use Doctrine\Common\Collections\ArrayCollection;
6use Doctrine\ORM\Mapping as ORM; 6use Doctrine\ORM\Mapping as ORM;
7use JMS\Serializer\Annotation\Groups;
8use JMS\Serializer\Annotation\XmlRoot;
9use JMS\Serializer\Annotation\Accessor;
7use Scheb\TwoFactorBundle\Model\Email\TwoFactorInterface; 10use Scheb\TwoFactorBundle\Model\Email\TwoFactorInterface;
8use Scheb\TwoFactorBundle\Model\TrustedComputerInterface; 11use Scheb\TwoFactorBundle\Model\TrustedComputerInterface;
9use FOS\UserBundle\Model\User as BaseUser; 12use FOS\UserBundle\Model\User as BaseUser;
10use JMS\Serializer\Annotation\ExclusionPolicy;
11use JMS\Serializer\Annotation\Expose;
12use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; 13use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
13use Symfony\Component\Security\Core\User\UserInterface; 14use Symfony\Component\Security\Core\User\UserInterface;
14use Wallabag\ApiBundle\Entity\Client; 15use Wallabag\ApiBundle\Entity\Client;
@@ -18,23 +19,25 @@ use Wallabag\CoreBundle\Entity\Entry;
18/** 19/**
19 * User. 20 * User.
20 * 21 *
22 * @XmlRoot("user")
21 * @ORM\Entity(repositoryClass="Wallabag\UserBundle\Repository\UserRepository") 23 * @ORM\Entity(repositoryClass="Wallabag\UserBundle\Repository\UserRepository")
22 * @ORM\Table(name="`user`") 24 * @ORM\Table(name="`user`")
23 * @ORM\HasLifecycleCallbacks() 25 * @ORM\HasLifecycleCallbacks()
24 * @ExclusionPolicy("all")
25 * 26 *
26 * @UniqueEntity("email") 27 * @UniqueEntity("email")
27 * @UniqueEntity("username") 28 * @UniqueEntity("username")
28 */ 29 */
29class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface 30class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface
30{ 31{
32 /** @Serializer\XmlAttribute */
31 /** 33 /**
32 * @var int 34 * @var int
33 * 35 *
34 * @Expose
35 * @ORM\Column(name="id", type="integer") 36 * @ORM\Column(name="id", type="integer")
36 * @ORM\Id 37 * @ORM\Id
37 * @ORM\GeneratedValue(strategy="AUTO") 38 * @ORM\GeneratedValue(strategy="AUTO")
39 *
40 * @Groups({"user_api", "user_api_with_client"})
38 */ 41 */
39 protected $id; 42 protected $id;
40 43
@@ -42,20 +45,40 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
42 * @var string 45 * @var string
43 * 46 *
44 * @ORM\Column(name="name", type="text", nullable=true) 47 * @ORM\Column(name="name", type="text", nullable=true)
48 *
49 * @Groups({"user_api", "user_api_with_client"})
45 */ 50 */
46 protected $name; 51 protected $name;
47 52
48 /** 53 /**
49 * @var date 54 * @var string
55 *
56 * @Groups({"user_api", "user_api_with_client"})
57 */
58 protected $username;
59
60 /**
61 * @var string
62 *
63 * @Groups({"user_api", "user_api_with_client"})
64 */
65 protected $email;
66
67 /**
68 * @var \DateTime
50 * 69 *
51 * @ORM\Column(name="created_at", type="datetime") 70 * @ORM\Column(name="created_at", type="datetime")
71 *
72 * @Groups({"user_api", "user_api_with_client"})
52 */ 73 */
53 protected $createdAt; 74 protected $createdAt;
54 75
55 /** 76 /**
56 * @var date 77 * @var \DateTime
57 * 78 *
58 * @ORM\Column(name="updated_at", type="datetime") 79 * @ORM\Column(name="updated_at", type="datetime")
80 *
81 * @Groups({"user_api", "user_api_with_client"})
59 */ 82 */
60 protected $updatedAt; 83 protected $updatedAt;
61 84
@@ -75,7 +98,8 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
75 private $authCode; 98 private $authCode;
76 99
77 /** 100 /**
78 * @var bool Enabled yes/no 101 * @var bool
102 *
79 * @ORM\Column(type="boolean") 103 * @ORM\Column(type="boolean")
80 */ 104 */
81 private $twoFactorAuthentication = false; 105 private $twoFactorAuthentication = false;
@@ -86,10 +110,20 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
86 private $trusted; 110 private $trusted;
87 111
88 /** 112 /**
113 * @var ArrayCollection
114 *
89 * @ORM\OneToMany(targetEntity="Wallabag\ApiBundle\Entity\Client", mappedBy="user", cascade={"remove"}) 115 * @ORM\OneToMany(targetEntity="Wallabag\ApiBundle\Entity\Client", mappedBy="user", cascade={"remove"})
90 */ 116 */
91 protected $clients; 117 protected $clients;
92 118
119 /**
120 * @see getFirstClient() below
121 *
122 * @Groups({"user_api_with_client"})
123 * @Accessor(getter="getFirstClient")
124 */
125 protected $default_client;
126
93 public function __construct() 127 public function __construct()
94 { 128 {
95 parent::__construct(); 129 parent::__construct();
@@ -135,7 +169,7 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
135 } 169 }
136 170
137 /** 171 /**
138 * @return string 172 * @return \DateTime
139 */ 173 */
140 public function getCreatedAt() 174 public function getCreatedAt()
141 { 175 {
@@ -143,7 +177,7 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
143 } 177 }
144 178
145 /** 179 /**
146 * @return string 180 * @return \DateTime
147 */ 181 */
148 public function getUpdatedAt() 182 public function getUpdatedAt()
149 { 183 {
@@ -266,4 +300,16 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
266 { 300 {
267 return $this->clients; 301 return $this->clients;
268 } 302 }
303
304 /**
305 * Only used by the API when creating a new user it'll also return the first client (which was also created at the same time).
306 *
307 * @return Client
308 */
309 public function getFirstClient()
310 {
311 if (!empty($this->clients)) {
312 return $this->clients->first();
313 }
314 }
269} 315}
diff --git a/src/Wallabag/UserBundle/EventListener/AuthenticationFailureListener.php b/src/Wallabag/UserBundle/EventListener/AuthenticationFailureListener.php
new file mode 100644
index 00000000..10f13233
--- /dev/null
+++ b/src/Wallabag/UserBundle/EventListener/AuthenticationFailureListener.php
@@ -0,0 +1,40 @@
1<?php
2
3namespace Wallabag\UserBundle\EventListener;
4
5use Psr\Log\LoggerInterface;
6use Symfony\Component\EventDispatcher\EventSubscriberInterface;
7use Symfony\Component\HttpFoundation\RequestStack;
8use Symfony\Component\Security\Core\AuthenticationEvents;
9
10class AuthenticationFailureListener implements EventSubscriberInterface
11{
12 private $requestStack;
13 private $logger;
14
15 public function __construct(RequestStack $requestStack, LoggerInterface $logger)
16 {
17 $this->requestStack = $requestStack;
18 $this->logger = $logger;
19 }
20
21 /**
22 * {@inheritdoc}
23 */
24 public static function getSubscribedEvents()
25 {
26 return [
27 AuthenticationEvents::AUTHENTICATION_FAILURE => 'onAuthenticationFailure',
28 ];
29 }
30
31 /**
32 * On failure, add a custom error in log so server admin can configure fail2ban to block IP from people who try to login too much.
33 */
34 public function onAuthenticationFailure()
35 {
36 $request = $this->requestStack->getMasterRequest();
37
38 $this->logger->error('Authentication failure for user "'.$request->request->get('_username').'", from IP "'.$request->getClientIp().'", with UA: "'.$request->server->get('HTTP_USER_AGENT').'".');
39 }
40}
diff --git a/src/Wallabag/UserBundle/EventListener/CreateConfigListener.php b/src/Wallabag/UserBundle/EventListener/CreateConfigListener.php
index 0bdd1cae..e4d55c19 100644
--- a/src/Wallabag/UserBundle/EventListener/CreateConfigListener.php
+++ b/src/Wallabag/UserBundle/EventListener/CreateConfigListener.php
@@ -5,7 +5,6 @@ namespace Wallabag\UserBundle\EventListener;
5use Doctrine\ORM\EntityManager; 5use Doctrine\ORM\EntityManager;
6use FOS\UserBundle\Event\UserEvent; 6use FOS\UserBundle\Event\UserEvent;
7use FOS\UserBundle\FOSUserEvents; 7use FOS\UserBundle\FOSUserEvents;
8use Symfony\Component\EventDispatcher\EventDispatcherInterface;
9use Symfony\Component\EventDispatcher\EventSubscriberInterface; 8use Symfony\Component\EventDispatcher\EventSubscriberInterface;
10use Wallabag\CoreBundle\Entity\Config; 9use Wallabag\CoreBundle\Entity\Config;
11 10
@@ -47,7 +46,7 @@ class CreateConfigListener implements EventSubscriberInterface
47 ]; 46 ];
48 } 47 }
49 48
50 public function createConfig(UserEvent $event, $eventName = null, EventDispatcherInterface $eventDispatcher = null) 49 public function createConfig(UserEvent $event)
51 { 50 {
52 $config = new Config($event->getUser()); 51 $config = new Config($event->getUser());
53 $config->setTheme($this->theme); 52 $config->setTheme($this->theme);
diff --git a/src/Wallabag/UserBundle/Form/SearchUserType.php b/src/Wallabag/UserBundle/Form/SearchUserType.php
new file mode 100644
index 00000000..9ce46ee1
--- /dev/null
+++ b/src/Wallabag/UserBundle/Form/SearchUserType.php
@@ -0,0 +1,29 @@
1<?php
2
3namespace Wallabag\UserBundle\Form;
4
5use Symfony\Component\Form\AbstractType;
6use Symfony\Component\Form\Extension\Core\Type\TextType;
7use Symfony\Component\Form\FormBuilderInterface;
8use Symfony\Component\OptionsResolver\OptionsResolver;
9
10class SearchUserType extends AbstractType
11{
12 public function buildForm(FormBuilderInterface $builder, array $options)
13 {
14 $builder
15 ->setMethod('GET')
16 ->add('term', TextType::class, [
17 'required' => true,
18 'label' => 'user.new.form_search.term_label',
19 ])
20 ;
21 }
22
23 public function configureOptions(OptionsResolver $resolver)
24 {
25 $resolver->setDefaults([
26 'csrf_protection' => false,
27 ]);
28 }
29}
diff --git a/src/Wallabag/UserBundle/Repository/UserRepository.php b/src/Wallabag/UserBundle/Repository/UserRepository.php
index f913f52d..6adbe329 100644
--- a/src/Wallabag/UserBundle/Repository/UserRepository.php
+++ b/src/Wallabag/UserBundle/Repository/UserRepository.php
@@ -52,4 +52,17 @@ class UserRepository extends EntityRepository
52 ->getQuery() 52 ->getQuery()
53 ->getSingleScalarResult(); 53 ->getSingleScalarResult();
54 } 54 }
55
56 /**
57 * Retrieves users filtered with a search term.
58 *
59 * @param string $term
60 *
61 * @return QueryBuilder
62 */
63 public function getQueryBuilderForSearch($term)
64 {
65 return $this->createQueryBuilder('u')
66 ->andWhere('lower(u.username) LIKE lower(:term) OR lower(u.email) LIKE lower(:term) OR lower(u.name) LIKE lower(:term)')->setParameter('term', '%'.$term.'%');
67 }
55} 68}
diff --git a/src/Wallabag/UserBundle/Resources/config/services.yml b/src/Wallabag/UserBundle/Resources/config/services.yml
index 72f6f12c..d3925de3 100644
--- a/src/Wallabag/UserBundle/Resources/config/services.yml
+++ b/src/Wallabag/UserBundle/Resources/config/services.yml
@@ -7,7 +7,7 @@ services:
7 - "%scheb_two_factor.email.sender_email%" 7 - "%scheb_two_factor.email.sender_email%"
8 - "%scheb_two_factor.email.sender_name%" 8 - "%scheb_two_factor.email.sender_name%"
9 - '@=service(''craue_config'').get(''wallabag_support_url'')' 9 - '@=service(''craue_config'').get(''wallabag_support_url'')'
10 - '@=service(''craue_config'').get(''wallabag_url'')' 10 - '%domain_name%'
11 11
12 wallabag_user.password_resetting: 12 wallabag_user.password_resetting:
13 class: Wallabag\UserBundle\EventListener\PasswordResettingListener 13 class: Wallabag\UserBundle\EventListener\PasswordResettingListener
@@ -35,3 +35,11 @@ services:
35 - "%wallabag_core.list_mode%" 35 - "%wallabag_core.list_mode%"
36 tags: 36 tags:
37 - { name: kernel.event_subscriber } 37 - { name: kernel.event_subscriber }
38
39 wallabag_user.listener.authentication_failure_event_listener:
40 class: Wallabag\UserBundle\EventListener\AuthenticationFailureListener
41 arguments:
42 - "@request_stack"
43 - "@logger"
44 tags:
45 - { name: kernel.event_listener, event: security.authentication.failure, method: onAuthenticationFailure }
diff --git a/src/Wallabag/UserBundle/Resources/views/Manage/index.html.twig b/src/Wallabag/UserBundle/Resources/views/Manage/index.html.twig
index daba29e4..15002632 100644
--- a/src/Wallabag/UserBundle/Resources/views/Manage/index.html.twig
+++ b/src/Wallabag/UserBundle/Resources/views/Manage/index.html.twig
@@ -7,37 +7,60 @@
7 <div class="row"> 7 <div class="row">
8 <div class="col s12"> 8 <div class="col s12">
9 <div class="card-panel"> 9 <div class="card-panel">
10 {% if users.getNbPages > 1 %}
11 {{ pagerfanta(users, 'twitter_bootstrap_translated', {'proximity': 1}) }}
12 {% endif %}
10 <div class="row"> 13 <div class="row">
11 <div class="input-field col s12"> 14 <div class="col s6">
12 <p class="help">{{ 'user.description'|trans|raw }}</p> 15 <p class="help">{{ 'user.description'|trans|raw }}</p>
16 </div>
17 <div class="col s6">
18 <div class="input-field">
19 <form name="search_users" method="GET" action="{{ path('user_index')}}">
20 {% if form_errors(searchForm) %}
21 <span class="black-text">{{ form_errors(searchForm) }}</span>
22 {% endif %}
23
24 {% if form_errors(searchForm.term) %}
25 <span class="black-text">{{ form_errors(searchForm.term) }}</span>
26 {% endif %}
13 27
14 <table class="bordered"> 28 {{ form_widget(searchForm.term, { 'attr': {'autocomplete': 'off', 'placeholder': 'user.search.placeholder'} }) }}
15 <thead> 29
16 <tr> 30 {{ form_rest(searchForm) }}
17 <th>{{ 'user.form.username_label'|trans }}</th> 31 </form>
18 <th>{{ 'user.form.email_label'|trans }}</th> 32 </div>
19 <th>{{ 'user.form.last_login_label'|trans }}</th>
20 <th>{{ 'user.list.actions'|trans }}</th>
21 </tr>
22 </thead>
23 <tbody>
24 {% for user in users %}
25 <tr>
26 <td>{{ user.username }}</td>
27 <td>{{ user.email }}</td>
28 <td>{% if user.lastLogin %}{{ user.lastLogin|date('Y-m-d H:i:s') }}{% endif %}</td>
29 <td>
30 <a href="{{ path('user_edit', { 'id': user.id }) }}">{{ 'user.list.edit_action'|trans }}</a>
31 </td>
32 </tr>
33 {% endfor %}
34 </tbody>
35 </table>
36 <br />
37 <p>
38 <a href="{{ path('user_new') }}" class="waves-effect waves-light btn">{{ 'user.list.create_new_one'|trans }}</a>
39 </p>
40 </div> 33 </div>
34
35 <table class="bordered">
36 <thead>
37 <tr>
38 <th>{{ 'user.form.username_label'|trans }}</th>
39 <th>{{ 'user.form.email_label'|trans }}</th>
40 <th>{{ 'user.form.last_login_label'|trans }}</th>
41 <th>{{ 'user.list.actions'|trans }}</th>
42 </tr>
43 </thead>
44 <tbody>
45 {% for user in users %}
46 <tr>
47 <td>{{ user.username }}</td>
48 <td>{{ user.email }}</td>
49 <td>{% if user.lastLogin %}{{ user.lastLogin|date('Y-m-d H:i:s') }}{% endif %}</td>
50 <td>
51 <a href="{{ path('user_edit', { 'id': user.id }) }}">{{ 'user.list.edit_action'|trans }}</a>
52 </td>
53 </tr>
54 {% endfor %}
55 </tbody>
56 </table>
57 <br />
58 <p>
59 <a href="{{ path('user_new') }}" class="waves-effect waves-light btn">{{ 'user.list.create_new_one'|trans }}</a>
60 </p>
61 {% if users.getNbPages > 1 %}
62 {{ pagerfanta(users, 'twitter_bootstrap_translated', {'proximity': 1}) }}
63 {% endif %}
41 </div> 64 </div>
42 </div> 65 </div>
43 </div> 66 </div>