1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Modera\BackendSecurityBundle\Controller; |
4
|
|
|
|
5
|
|
|
use Modera\BackendSecurityBundle\ModeraBackendSecurityBundle; |
6
|
|
|
use Modera\SecurityBundle\Entity\User; |
7
|
|
|
use Modera\SecurityBundle\PasswordStrength\BadPasswordException; |
8
|
|
|
use Modera\SecurityBundle\PasswordStrength\PasswordManager; |
9
|
|
|
use Modera\SecurityBundle\Service\UserService; |
10
|
|
|
use Modera\ServerCrudBundle\Controller\AbstractCrudController; |
11
|
|
|
use Modera\ServerCrudBundle\DataMapping\DataMapperInterface; |
12
|
|
|
use Modera\ServerCrudBundle\Hydration\HydrationProfile; |
13
|
|
|
use Modera\ServerCrudBundle\Persistence\OperationResult; |
14
|
|
|
use Modera\FoundationBundle\Translation\T; |
15
|
|
|
use Modera\ServerCrudBundle\Validation\EntityValidatorInterface; |
16
|
|
|
use Modera\ServerCrudBundle\Validation\ValidationResult; |
17
|
|
|
use Psr\Log\LoggerInterface; |
18
|
|
|
use Sli\ExtJsIntegrationBundle\QueryBuilder\Parsing\Filter; |
19
|
|
|
use Sli\ExtJsIntegrationBundle\QueryBuilder\Parsing\Filters; |
20
|
|
|
use Symfony\Component\DependencyInjection\ContainerInterface; |
21
|
|
|
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; |
22
|
|
|
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; |
23
|
|
|
use Modera\BackendSecurityBundle\Service\MailService; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* @author Sergei Vizel <[email protected]> |
27
|
|
|
* @copyright 2014 Modera Foundation |
28
|
|
|
*/ |
29
|
|
|
class UsersController extends AbstractCrudController |
30
|
|
|
{ |
31
|
|
|
/** |
32
|
|
|
* @return array |
33
|
|
|
*/ |
34
|
|
|
public function getConfig() |
35
|
|
|
{ |
36
|
|
|
$self = $this; |
37
|
|
|
|
38
|
|
|
return array( |
39
|
|
|
'entity' => User::clazz(), |
40
|
|
|
'create_default_data_mapper' => function (ContainerInterface $container) { |
|
|
|
|
41
|
|
|
return $this->container->get('modera_backend_security.data_mapper.user_data_mapper'); |
42
|
|
|
}, |
43
|
|
|
'security' => array( |
44
|
|
|
'actions' => array( |
45
|
|
|
'create' => ModeraBackendSecurityBundle::ROLE_MANAGE_USER_PROFILES, |
46
|
|
|
'update' => function (AuthorizationCheckerInterface $ac, array $params) use ($self) { |
47
|
|
|
/* @var TokenStorageInterface $ts */ |
48
|
|
|
$ts = $self->get('security.token_storage'); |
49
|
|
|
/* @var User $user */ |
50
|
|
|
$user = $ts->getToken()->getUser(); |
51
|
|
|
|
52
|
|
|
if ($ac->isGranted(ModeraBackendSecurityBundle::ROLE_MANAGE_USER_PROFILES) |
53
|
|
|
|| $ac->isGranted(ModeraBackendSecurityBundle::ROLE_MANAGE_USER_PROFILE_INFORMATION)) { |
54
|
|
|
return true; |
55
|
|
|
} else { |
56
|
|
|
// irrespectively of what privileges user has we will always allow him to edit his |
57
|
|
|
// own profile data |
58
|
|
|
return $user instanceof User && isset($params['record']['id']) |
59
|
|
|
&& $user->getId() == $params['record']['id']; |
60
|
|
|
} |
61
|
|
|
}, |
62
|
|
|
'remove' => ModeraBackendSecurityBundle::ROLE_MANAGE_USER_PROFILES, |
63
|
|
|
'get' => function(AuthorizationCheckerInterface $ac, array $params) { |
64
|
|
|
$userId = null; |
65
|
|
|
if (isset($params['filter'])) { |
66
|
|
|
foreach (new Filters($params['filter']) as $filter) { |
67
|
|
|
/* @var Filter $filter */ |
68
|
|
|
|
69
|
|
|
if ($filter->getProperty() == 'id' && $filter->getComparator() == Filter::COMPARATOR_EQUAL) { |
70
|
|
|
$userId = $filter->getValue(); |
71
|
|
|
} |
72
|
|
|
} |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
$isPossiblyEditingOwnProfile = null !== $userId; |
76
|
|
|
if ($isPossiblyEditingOwnProfile) { |
77
|
|
|
/* @var TokenStorageInterface $ts */ |
78
|
|
|
$ts = $this->get('security.token_storage'); |
79
|
|
|
/* @var User $user */ |
80
|
|
|
$user = $ts->getToken()->getUser(); |
81
|
|
|
|
82
|
|
|
if ($user->getId() == $userId) { |
83
|
|
|
return true; |
84
|
|
|
} |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
return $ac->isGranted(ModeraBackendSecurityBundle::ROLE_MANAGE_USER_PROFILES) |
88
|
|
|
|| $ac->isGranted(ModeraBackendSecurityBundle::ROLE_MANAGE_USER_PROFILE_INFORMATION); |
89
|
|
|
}, |
90
|
|
|
'list' => ModeraBackendSecurityBundle::ROLE_ACCESS_BACKEND_TOOLS_SECURITY_SECTION, |
91
|
|
|
'batchUpdate' => ModeraBackendSecurityBundle::ROLE_MANAGE_USER_PROFILES, |
92
|
|
|
), |
93
|
|
|
), |
94
|
|
|
'hydration' => array( |
95
|
|
|
'groups' => array( |
96
|
|
|
'main-form' => ['id', 'username', 'email', 'firstName', 'lastName', 'middleName', 'meta'], |
97
|
|
|
'list' => function (User $user) { |
98
|
|
|
$groups = array(); |
99
|
|
|
foreach ($user->getGroups() as $group) { |
100
|
|
|
$groups[] = $group->getName(); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
return array( |
104
|
|
|
'id' => $user->getId(), |
105
|
|
|
'username' => $user->getUsername(), |
106
|
|
|
'email' => $user->getEmail(), |
107
|
|
|
'firstName' => $user->getFirstName(), |
108
|
|
|
'lastName' => $user->getLastName(), |
109
|
|
|
'middleName' => $user->getMiddleName(), |
110
|
|
|
'isActive' => $user->isActive(), |
111
|
|
|
'state' => $user->getState(), |
112
|
|
|
'groups' => $groups, |
113
|
|
|
'meta' => $user->getMeta(), |
114
|
|
|
); |
115
|
|
|
}, |
116
|
|
|
'compact-list' => function (User $user) { |
117
|
|
|
$groups = array(); |
118
|
|
|
foreach ($user->getGroups() as $group) { |
119
|
|
|
$groups[] = $group->getName(); |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
return array( |
123
|
|
|
'id' => $user->getId(), |
124
|
|
|
'username' => $user->getUsername(), |
125
|
|
|
'fullname' => $user->getFullName(), |
126
|
|
|
'isActive' => $user->isActive(), |
127
|
|
|
'state' => $user->getState(), |
128
|
|
|
); |
129
|
|
|
}, |
130
|
|
|
'delete-user' => ['username'], |
131
|
|
|
), |
132
|
|
|
'profiles' => array( |
133
|
|
|
'list', |
134
|
|
|
'delete-user', |
135
|
|
|
'main-form', |
136
|
|
|
'compact-list', |
137
|
|
|
'modera-backend-security-group-groupusers' => HydrationProfile::create(false)->useGroups(array('compact-list')), |
138
|
|
|
), |
139
|
|
|
), |
140
|
|
|
'map_data_on_create' => function (array $params, User $user, DataMapperInterface $defaultMapper, ContainerInterface $container) use ($self) { |
|
|
|
|
141
|
|
|
$defaultMapper->mapData($params, $user); |
142
|
|
|
|
143
|
|
|
if (isset($params['plainPassword']) && $params['plainPassword']) { |
144
|
|
|
$plainPassword = $params['plainPassword']; |
145
|
|
|
} else { |
146
|
|
|
$plainPassword = $this->getPasswordManager()->generatePassword(); |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
try { |
150
|
|
View Code Duplication |
if (isset($params['sendPassword']) && $params['sendPassword'] != '') { |
|
|
|
|
151
|
|
|
$this->getPasswordManager()->encodeAndSetPasswordAndThenEmailIt($user, $plainPassword); |
152
|
|
|
} else { |
153
|
|
|
$this->getPasswordManager()->encodeAndSetPassword($user, $plainPassword); |
154
|
|
|
} |
155
|
|
|
} catch (BadPasswordException $e) { |
156
|
|
|
throw new BadPasswordException($e->getErrors()[0], null, $e); |
157
|
|
|
} |
158
|
|
|
}, |
159
|
|
|
'map_data_on_update' => function (array $params, User $user, DataMapperInterface $defaultMapper, ContainerInterface $container) use ($self) { |
160
|
|
|
$defaultMapper->mapData($params, $user); |
161
|
|
|
|
162
|
|
|
/* @var LoggerInterface $activityMgr */ |
163
|
|
|
$activityMgr = $container->get('modera_activity_logger.manager.activity_manager'); |
164
|
|
|
|
165
|
|
|
if (isset($params['active'])) { |
166
|
|
|
/* @var UserService $userService */ |
167
|
|
|
$userService = $container->get('modera_security.service.user_service'); |
168
|
|
|
if ($params['active']) { |
169
|
|
|
$userService->enable($user); |
170
|
|
|
$activityMsg = T::trans('Profile enabled for user "%user%".', array('%user%' => $user->getUsername())); |
171
|
|
|
$activityContext = array( |
172
|
|
|
'type' => 'user.profile_enabled', |
173
|
|
|
'author' => $this->getUser()->getId(), |
174
|
|
|
); |
175
|
|
View Code Duplication |
} else { |
|
|
|
|
176
|
|
|
$userService->disable($user); |
177
|
|
|
$activityMsg = T::trans('Profile disabled for user "%user%".', array('%user%' => $user->getUsername())); |
178
|
|
|
$activityContext = array( |
179
|
|
|
'type' => 'user.profile_disabled', |
180
|
|
|
'author' => $this->getUser()->getId(), |
181
|
|
|
); |
182
|
|
|
} |
183
|
|
|
$activityMgr->info($activityMsg, $activityContext); |
184
|
|
|
} else if (isset($params['plainPassword']) && $params['plainPassword']) { |
185
|
|
|
// Password encoding and setting is done in "updated_entity_validator" |
186
|
|
|
|
187
|
|
|
$activityMsg = T::trans('Password has been changed for user "%user%".', array('%user%' => $user->getUsername())); |
188
|
|
|
$activityContext = array( |
189
|
|
|
'type' => 'user.password_changed', |
190
|
|
|
'author' => $this->getUser()->getId(), |
191
|
|
|
); |
192
|
|
|
$activityMgr->info($activityMsg, $activityContext); |
193
|
|
View Code Duplication |
} else { |
|
|
|
|
194
|
|
|
$activityMsg = T::trans('Profile data is changed for user "%user%".', array('%user%' => $user->getUsername())); |
195
|
|
|
$activityContext = array( |
196
|
|
|
'type' => 'user.profile_updated', |
197
|
|
|
'author' => $this->getUser()->getId(), |
198
|
|
|
); |
199
|
|
|
$activityMgr->info($activityMsg, $activityContext); |
200
|
|
|
} |
201
|
|
|
}, |
202
|
|
|
'updated_entity_validator' => function (array $params, User $user, EntityValidatorInterface $validator, array $config, ContainerInterface $container) { |
203
|
|
|
$isBatchUpdatedBeingPerformed = !isset($params['record']); |
204
|
|
|
if ($isBatchUpdatedBeingPerformed) { |
205
|
|
|
// Because of bug in AbstractCrudController (see MF-UPGRADE3.0 and search for "$recordParams" keyword) |
206
|
|
|
// it is tricky to perform proper validation here, but anyway it not likely that at any |
207
|
|
|
// time we are going to be setting passwords using a batch operation |
208
|
|
|
return new ValidationResult(); |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
/** @var UserService $userService */ |
212
|
|
|
$userService = $container->get('modera_security.service.user_service'); |
213
|
|
|
/** @var TokenStorageInterface $tokenStorage */ |
214
|
|
|
$tokenStorage = $container->get('security.token_storage'); |
215
|
|
|
/** @var AuthorizationCheckerInterface $authorizationChecker */ |
216
|
|
|
$authorizationChecker = $container->get('security.authorization_checker'); |
217
|
|
|
|
218
|
|
|
if ($tokenStorage->getToken()->getUser()->getId() != $user->getId()) { |
219
|
|
|
$result = new ValidationResult(); |
220
|
|
|
|
221
|
|
|
if ($userService->isRootUser($user)) { |
222
|
|
|
$result->addGeneralError('Access denied. Can not update root user!!!'); |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
if (!$authorizationChecker->isGranted(ModeraBackendSecurityBundle::ROLE_MANAGE_USER_PROFILES) |
226
|
|
|
&& $authorizationChecker->isGranted(ModeraBackendSecurityBundle::ROLE_MANAGE_USER_PROFILE_INFORMATION)) { |
227
|
|
|
$allowFieldsEdit = array( |
228
|
|
|
'id' => '', |
229
|
|
|
'firstName' => '', |
230
|
|
|
'lastName' => '', |
231
|
|
|
'email' => '', |
232
|
|
|
); |
233
|
|
|
|
234
|
|
|
foreach (array_diff_key($params['record'], $allowFieldsEdit) as $key => $value) { |
235
|
|
|
$result->addFieldError($key, 'Access denied.'); |
236
|
|
|
} |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
if ($result->hasErrors()) { |
240
|
|
|
return $result; |
241
|
|
|
} |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
$result = $validator->validate($user, $config); |
245
|
|
|
|
246
|
|
|
$params = $params['record']; |
247
|
|
|
if (isset($params['plainPassword']) && $params['plainPassword']) { |
248
|
|
|
try { |
249
|
|
|
// We are force to do it here because we have no access to validation in |
250
|
|
|
// "map_data_on_update" |
251
|
|
View Code Duplication |
if (isset($params['sendPassword']) && $params['sendPassword'] != '') { |
|
|
|
|
252
|
|
|
$this->getPasswordManager()->encodeAndSetPasswordAndThenEmailIt($user, $params['plainPassword']); |
253
|
|
|
} else { |
254
|
|
|
$this->getPasswordManager()->encodeAndSetPassword($user, $params['plainPassword']); |
255
|
|
|
} |
256
|
|
|
} catch (BadPasswordException $e) { |
257
|
|
|
$result->addFieldError('plainPassword', $e->getErrors()[0]); |
258
|
|
|
} |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
return $result; |
262
|
|
|
}, |
263
|
|
|
'remove_entities_handler' => function ($entities, $params, $defaultHandler, ContainerInterface $container) { |
264
|
|
|
/* @var UserService $userService */ |
265
|
|
|
$userService = $container->get('modera_security.service.user_service'); |
266
|
|
|
|
267
|
|
|
$operationResult = new OperationResult(); |
268
|
|
|
|
269
|
|
|
foreach ($entities as $entity) { |
270
|
|
|
/* @var User $entity*/ |
271
|
|
|
$userService->remove($entity); |
272
|
|
|
|
273
|
|
|
$operationResult->reportEntity(User::clazz(), $entity->getId(), OperationResult::TYPE_ENTITY_REMOVED); |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
return $operationResult; |
277
|
|
|
}, |
278
|
|
|
); |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
/** |
282
|
|
|
* @Remote |
283
|
|
|
*/ |
284
|
|
|
public function generatePasswordAction(array $params) |
285
|
|
|
{ |
286
|
|
|
/* @var User $authenticatedUser */ |
287
|
|
|
$authenticatedUser = $this->getUser(); |
288
|
|
|
|
289
|
|
|
$targetUser = null; |
|
|
|
|
290
|
|
|
if (isset($params['userId'])) { |
291
|
|
|
/* @var User $requestedUser */ |
292
|
|
|
$requestedUser = $this |
293
|
|
|
->getDoctrine() |
294
|
|
|
->getRepository(User::class) |
295
|
|
|
->find($params['userId']) |
296
|
|
|
; |
297
|
|
|
|
298
|
|
|
if ($requestedUser) { |
299
|
|
|
if (!$authenticatedUser->isEqualTo($requestedUser)) { |
300
|
|
|
$this->denyAccessUnlessGranted(ModeraBackendSecurityBundle::ROLE_MANAGE_USER_PROFILES); |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
$targetUser = $requestedUser; |
304
|
|
|
} else { |
305
|
|
|
throw $this->createAccessDeniedException(); |
306
|
|
|
} |
307
|
|
|
} else { |
308
|
|
|
$targetUser = $authenticatedUser; |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
return array( |
312
|
|
|
'success' => true, |
313
|
|
|
'result' => array( |
314
|
|
|
'plainPassword' => $this->getPasswordManager()->generatePassword($targetUser), |
315
|
|
|
), |
316
|
|
|
); |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
/** |
320
|
|
|
* @Remote |
321
|
|
|
*/ |
322
|
|
|
public function isPasswordRotationNeededAction(array $params) |
323
|
|
|
{ |
324
|
|
|
$isRotationNeeded = false; |
325
|
|
|
if (!$this->isGranted('ROLE_PREVIOUS_ADMIN')) { |
326
|
|
|
$isRotationNeeded = $this->getPasswordManager()->isItTimeToRotatePassword($this->getUser()); |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
return array( |
330
|
|
|
'success' => true, |
331
|
|
|
'result' => array( |
332
|
|
|
'isRotationNeeded' => $isRotationNeeded, |
333
|
|
|
), |
334
|
|
|
); |
335
|
|
|
} |
336
|
|
|
|
337
|
|
|
/** |
338
|
|
|
* @return PasswordManager |
339
|
|
|
*/ |
340
|
|
|
private function getPasswordManager() |
341
|
|
|
{ |
342
|
|
|
return $this->get('modera_security.password_strength.password_manager'); |
343
|
|
|
} |
344
|
|
|
} |
345
|
|
|
|
This check looks from parameters that have been defined for a function or method, but which are not used in the method body.