Initial commit: CloudOps infrastructure platform

This commit is contained in:
root
2026-04-09 19:58:57 +02:00
commit 1166a52f26
7762 changed files with 839452 additions and 0 deletions

View File

@@ -0,0 +1,54 @@
<?php
namespace Mautic\UserBundle\Controller\Api;
use Doctrine\Persistence\ManagerRegistry;
use Mautic\ApiBundle\Controller\CommonApiController;
use Mautic\ApiBundle\Helper\EntityResultHelper;
use Mautic\CoreBundle\Factory\ModelFactory;
use Mautic\CoreBundle\Helper\AppVersion;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
use Mautic\CoreBundle\Translation\Translator;
use Mautic\UserBundle\Entity\Role;
use Mautic\UserBundle\Model\RoleModel;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\RouterInterface;
/**
* @extends CommonApiController<Role>
*/
class RoleApiController extends CommonApiController
{
/**
* @var RoleModel|null
*/
protected $model;
public function __construct(CorePermissions $security, Translator $translator, EntityResultHelper $entityResultHelper, RouterInterface $router, FormFactoryInterface $formFactory, AppVersion $appVersion, RequestStack $requestStack, ManagerRegistry $doctrine, ModelFactory $modelFactory, EventDispatcherInterface $dispatcher, CoreParametersHelper $coreParametersHelper)
{
$roleModel = $modelFactory->getModel('user.role');
\assert($roleModel instanceof RoleModel);
$this->model = $roleModel;
$this->entityClass = Role::class;
$this->entityNameOne = 'role';
$this->entityNameMulti = 'roles';
$this->serializerGroups = ['roleDetails', 'publishDetails'];
parent::__construct($security, $translator, $entityResultHelper, $router, $formFactory, $appVersion, $requestStack, $doctrine, $modelFactory, $dispatcher, $coreParametersHelper);
}
/**
* @param Role &$entity
* @param string $action
*/
protected function preSaveEntity(&$entity, $form, $parameters, $action = 'edit')
{
if (isset($parameters['rawPermissions'])) {
$this->model->setRolePermissions($entity, $parameters['rawPermissions']);
}
}
}

View File

@@ -0,0 +1,226 @@
<?php
namespace Mautic\UserBundle\Controller\Api;
use Doctrine\Persistence\ManagerRegistry;
use Mautic\ApiBundle\Controller\CommonApiController;
use Mautic\ApiBundle\Helper\EntityResultHelper;
use Mautic\CoreBundle\Factory\ModelFactory;
use Mautic\CoreBundle\Helper\AppVersion;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
use Mautic\CoreBundle\Translation\Translator;
use Mautic\UserBundle\Entity\User;
use Mautic\UserBundle\Model\UserModel;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
/**
* @extends CommonApiController<User>
*/
class UserApiController extends CommonApiController
{
/**
* @var UserModel|null
*/
protected $model;
public function __construct(
CorePermissions $security,
Translator $translator,
EntityResultHelper $entityResultHelper,
RouterInterface $router,
FormFactoryInterface $formFactory,
AppVersion $appVersion,
private UserPasswordHasherInterface $hasher,
RequestStack $requestStack,
ManagerRegistry $doctrine,
ModelFactory $modelFactory,
EventDispatcherInterface $dispatcher,
CoreParametersHelper $coreParametersHelper,
) {
$userModel = $modelFactory->getModel('user.user');
\assert($userModel instanceof UserModel);
$this->model = $userModel;
$this->entityClass = User::class;
$this->entityNameOne = 'user';
$this->entityNameMulti = 'users';
$this->serializerGroups = ['userDetails', 'roleList', 'publishDetails'];
$this->dataInputMasks = ['signature' => 'html'];
parent::__construct($security, $translator, $entityResultHelper, $router, $formFactory, $appVersion, $requestStack, $doctrine, $modelFactory, $dispatcher, $coreParametersHelper);
}
/**
* Obtains the logged in user's data.
*
* @return Response
*
* @throws NotFoundHttpException
*/
public function getSelfAction(TokenStorageInterface $tokenStorage)
{
$currentUser = $tokenStorage->getToken()->getUser();
$view = $this->view($currentUser, Response::HTTP_OK);
return $this->handleView($view);
}
/**
* Creates a new user.
*/
public function newEntityAction(Request $request)
{
$entity = $this->model->getEntity();
if (!$this->security->isGranted('user:users:create')) {
return $this->accessDenied();
}
$parameters = $request->request->all();
if (isset($parameters['plainPassword']['password'])) {
$submittedPassword = $parameters['plainPassword']['password'];
$entity->setPassword($this->model->checkNewPassword($entity, $this->hasher, $submittedPassword));
}
return $this->processForm($request, $entity, $parameters, 'POST');
}
/**
* Edits an existing user or creates a new one on PUT if not found.
*
* @param int $id User ID
*
* @return Response
*
* @throws NotFoundHttpException
*/
public function editEntityAction(Request $request, $id)
{
$entity = $this->model->getEntity($id);
$parameters = $request->request->all();
$method = $request->getMethod();
if (!$this->security->isGranted('user:users:edit')) {
return $this->accessDenied();
}
if (null === $entity) {
if ('PATCH' === $method
|| ('PUT' === $method && !$this->security->isGranted('user:users:create'))
) {
// PATCH requires that an entity exists or must have create access for PUT
return $this->notFound();
} else {
$entity = $this->model->getEntity();
if (isset($parameters['plainPassword']['password'])) {
$submittedPassword = $parameters['plainPassword']['password'];
$entity->setPassword($this->model->checkNewPassword($entity, $this->hasher, $submittedPassword));
}
}
} else {
// Changing passwords via API is forbidden
if (!empty($parameters['plainPassword'])) {
unset($parameters['plainPassword']);
}
if ('PATCH' == $method) {
// PATCH will accept a diff so just remove the entities
// Changing username via API is forbidden
if (!empty($parameters['username'])) {
unset($parameters['username']);
}
} else {
// PUT requires the entire entity so overwrite the username with the original
$parameters['username'] = $entity->getUsername();
$parameters['role'] = $entity->getRole()->getId();
}
}
return $this->processForm($request, $entity, $parameters, $method);
}
protected function preSaveEntity(&$entity, $form, $parameters, $action = 'edit')
{
switch ($action) {
case 'new':
$submittedPassword = null;
if (isset($parameters['plainPassword'])) {
if (is_array($parameters['plainPassword']) && isset($parameters['plainPassword']['password'])) {
$submittedPassword = $parameters['plainPassword']['password'];
} else {
$submittedPassword = $parameters['plainPassword'];
}
}
$entity->setPassword($this->model->checkNewPassword($entity, $this->hasher, $submittedPassword, true));
break;
}
}
/**
* Verifies if a user has permission(s) to a action.
*
* @param int $id User ID
*
* @return Response
*
* @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
* @throws NotFoundHttpException
*/
public function isGrantedAction(Request $request, $id)
{
$entity = $this->model->getEntity($id);
if (!$entity instanceof $this->entityClass) {
return $this->notFound();
}
$permissions = $request->request->all()['permissions'] ?? [];
if (empty($permissions)) {
return $this->badRequest('mautic.api.call.permissionempty');
} elseif (!is_array($permissions)) {
$permissions = [$permissions];
}
$return = $this->security->isGranted($permissions, 'RETURN_ARRAY', $entity);
$view = $this->view($return, Response::HTTP_OK);
return $this->handleView($view);
}
/**
* Obtains a list of roles for user edits.
*
* @return Response
*/
public function getRolesAction(Request $request)
{
if (!$this->security->isGranted(
['user:users:create', 'user:users:edit'],
'MATCH_ONE'
)
) {
return $this->accessDenied();
}
$filter = $request->query->get('filter', null);
$limit = (int) $request->query->get('limit', null);
$roles = $this->model->getLookupResults('role', $filter, $limit);
$view = $this->view($roles, Response::HTTP_OK);
$context = $view->getContext()->setGroups(['roleList']);
$view->setContext($context);
return $this->handleView($view);
}
}

View File

@@ -0,0 +1,252 @@
<?php
declare(strict_types=1);
namespace Mautic\UserBundle\Controller;
use Mautic\CoreBundle\Controller\FormController;
use Mautic\CoreBundle\Helper\LanguageHelper;
use Mautic\UserBundle\Entity\User;
use Mautic\UserBundle\Model\UserModel;
use Mautic\UserBundle\Security\SAML\Helper as SAMLHelper;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class ProfileController extends FormController
{
/**
* Generate's account profile.
*/
public function indexAction(Request $request, LanguageHelper $languageHelper, UserPasswordHasherInterface $hasher,
TokenStorageInterface $tokenStorage, SAMLHelper $samlHelper): Response|RedirectResponse
{
// get current user
$me = $tokenStorage->getToken()->getUser();
\assert($me instanceof User);
/** @var UserModel $model */
$model = $this->getModel('user');
// set some permissions
$permissions = [
'apiAccess' => ($this->coreParametersHelper->get('api_enabled')) ?
$this->security->isGranted('api:access:full')
: 0,
'editName' => $this->security->isGranted('user:profile:editname'),
'editUsername' => $this->security->isGranted('user:profile:editusername'),
'editPosition' => $this->security->isGranted('user:profile:editposition'),
'editEmail' => $this->security->isGranted('user:profile:editemail'),
];
$action = $this->generateUrl('mautic_user_account');
$form = $model->createForm($me, $this->formFactory, $action, ['in_profile' => true]);
$overrides = [];
// make sure this user has access to edit privileged fields
foreach ($permissions as $permName => $hasAccess) {
if ('apiAccess' == $permName) {
continue;
}
if (!$hasAccess) {
// set the value to its original
switch ($permName) {
case 'editName':
$overrides['firstName'] = $me->getFirstName();
$overrides['lastName'] = $me->getLastName();
$form->remove('firstName');
$form->add(
'firstName_unbound',
TextType::class,
[
'label' => 'mautic.core.firstname',
'label_attr' => ['class' => 'control-label'],
'attr' => ['class' => 'form-control'],
'mapped' => false,
'disabled' => true,
'data' => $me->getFirstName(),
'required' => false,
]
);
$form->remove('lastName');
$form->add(
'lastName_unbound',
TextType::class,
[
'label' => 'mautic.core.lastname',
'label_attr' => ['class' => 'control-label'],
'attr' => ['class' => 'form-control'],
'mapped' => false,
'disabled' => true,
'data' => $me->getLastName(),
'required' => false,
]
);
break;
case 'editUsername':
$overrides['username'] = $me->getUserIdentifier();
$form->remove('username');
$form->add(
'username_unbound',
TextType::class,
[
'label' => 'mautic.core.username',
'label_attr' => ['class' => 'control-label'],
'attr' => ['class' => 'form-control'],
'mapped' => false,
'disabled' => true,
'data' => $me->getUserIdentifier(),
'required' => false,
]
);
break;
case 'editPosition':
$overrides['position'] = $me->getPosition();
$form->remove('position');
$form->add(
'position_unbound',
TextType::class,
[
'label' => 'mautic.core.position',
'label_attr' => ['class' => 'control-label'],
'attr' => ['class' => 'form-control'],
'mapped' => false,
'disabled' => true,
'data' => $me->getPosition(),
'required' => false,
]
);
break;
case 'editEmail':
$overrides['email'] = $me->getEmail();
$form->remove('email');
$form->add(
'email_unbound',
TextType::class,
[
'label' => 'mautic.core.type.email',
'label_attr' => ['class' => 'control-label'],
'attr' => ['class' => 'form-control'],
'mapped' => false,
'disabled' => true,
'data' => $me->getEmail(),
'required' => false,
]
);
break;
}
}
}
// Check for a submitted form and process it
$submitted = $request->getSession()->get('formProcessed', 0);
if ('POST' === $request->getMethod() && !$submitted) {
$request->getSession()->set('formProcessed', 1);
// check to see if the password needs to be rehashed
$formUser = $request->request->all()['user'] ?? [];
$submittedPassword = $formUser['plainPassword']['password'] ?? null;
$overrides['password'] = $model->checkNewPassword($me, $hasher, $submittedPassword);
if (!$cancelled = $this->isFormCancelled($form)) {
if ($this->isFormValid($form)) {
foreach ($overrides as $k => $v) {
$func = 'set'.ucfirst($k);
$me->$func($v);
}
// form is valid so process the data
$model->saveEntity($me);
// check if the user's locale has been downloaded already, fetch it if not
$installedLanguages = $languageHelper->getSupportedLanguages();
if ($me->getLocale() && !array_key_exists($me->getLocale(), $installedLanguages)) {
$fetchLanguage = $languageHelper->extractLanguagePackage($me->getLocale());
// If there is an error, we need to reset the user's locale to the default
if ($fetchLanguage['error']) {
$me->setLocale(null);
$model->saveEntity($me);
$message = 'mautic.core.could.not.set.language';
$messageVars = [];
if (isset($fetchLanguage['message'])) {
$message = $fetchLanguage['message'];
}
if (isset($fetchLanguage['vars'])) {
$messageVars = $fetchLanguage['vars'];
}
$this->addFlashMessage($message, $messageVars);
}
}
// Update timezone and locale
$tz = $me->getTimezone();
if (empty($tz)) {
$tz = $this->coreParametersHelper->getDefaultTimezone();
}
$request->getSession()->set('_timezone', $tz);
$locale = $me->getLocale();
if (empty($locale)) {
$locale = $this->coreParametersHelper->get('locale');
}
$request->getSession()->set('_locale', $locale);
$returnUrl = $this->generateUrl('mautic_user_account');
return $this->postActionRedirect(
[
'returnUrl' => $returnUrl,
'contentTemplate' => 'Mautic\UserBundle\Controller\ProfileController::indexAction',
'passthroughVars' => [
'mauticContent' => 'user',
],
'flashes' => [ // success
[
'type' => 'notice',
'msg' => 'mautic.user.account.notice.updated',
],
],
]
);
}
} else {
return $this->redirectToRoute('mautic_dashboard_index');
}
}
$request->getSession()->set('formProcessed', 0);
$isSamlUser = $samlHelper->isSamlSession();
if ($isSamlUser) {
$form->remove('plainPassword');
}
$parameters = [
'permissions' => $permissions,
'me' => $me,
'userForm' => $form->createView(),
'isSamlUser' => $isSamlUser,
'authorizedClients' => $this->forward('Mautic\ApiBundle\Controller\ClientController::authorizedClientsAction')->getContent(),
];
return $this->delegateView(
[
'viewParameters' => $parameters,
'contentTemplate' => '@MauticUser/Profile/index.html.twig',
'passthroughVars' => [
'route' => $this->generateUrl('mautic_user_account'),
'mauticContent' => 'user',
],
]
);
}
}

View File

@@ -0,0 +1,142 @@
<?php
namespace Mautic\UserBundle\Controller;
use Mautic\CoreBundle\Controller\FormController;
use Mautic\UserBundle\Entity\User;
use Mautic\UserBundle\Form\Type\PasswordResetConfirmType;
use Mautic\UserBundle\Form\Type\PasswordResetType;
use Mautic\UserBundle\Model\UserModel;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
class PublicController extends FormController
{
/**
* Generates a new password for the user and emails it to them.
*/
public function passwordResetAction(Request $request): \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
{
/** @var UserModel $model */
$model = $this->getModel('user');
$data = ['identifier' => ''];
$action = $this->generateUrl('mautic_user_passwordreset');
$form = $this->formFactory->create(PasswordResetType::class, $data, ['action' => $action]);
// /Check for a submitted form and process it
if ('POST' === $request->getMethod()) {
if ($isValid = $this->isFormValid($form)) {
// find the user
$data = $form->getData();
$user = $model->getRepository()->findByIdentifier($data['identifier']);
/**
* Calculation of time to standardize fix response for vulnerability
* Users enumeration - forgot password. Constant response time is 1s.
*/
$desiredTime = 1.0;
$startTime = microtime(true);
try {
if (null !== $user) {
$model->sendResetEmail($user);
}
$this->addFlashMessage('mautic.user.user.notice.passwordreset');
} catch (\Exception) {
$this->addFlashMessage('mautic.user.user.notice.passwordreset.error', [], 'error');
}
$endTime = microtime(true);
$executionTime = $endTime - $startTime;
if ($executionTime < $desiredTime) {
usleep((int) (($desiredTime - $executionTime) * 1000000));
}
return $this->redirectToRoute('login');
}
}
return $this->delegateView([
'viewParameters' => [
'form' => $form->createView(),
],
'contentTemplate' => '@MauticUser/Security/reset.html.twig',
'passthroughVars' => [
'route' => $action,
],
]);
}
public function passwordResetConfirmAction(Request $request, UserPasswordHasherInterface $hasher): mixed
{
/** @var UserModel $model */
$model = $this->getModel('user');
$data = ['identifier' => '', 'password' => '', 'password_confirm' => ''];
$action = $this->generateUrl('mautic_user_passwordresetconfirm');
$form = $this->formFactory->create(PasswordResetConfirmType::class, [], ['action' => $action]);
$token = $request->query->get('token');
if ($token) {
$request->getSession()->set('resetToken', $token);
}
// /Check for a submitted form and process it
if ('POST' === $request->getMethod()) {
if ($isValid = $this->isFormValid($form)) {
// find the user
$data = $form->getData();
/** @var User $user */
$user = $model->getRepository()->findByIdentifier($data['identifier']);
if (null == $user) {
$this->addFlashMessage('mautic.user.user.notice.passwordreset.success');
return $this->redirectToRoute('login');
} else {
if ($request->getSession()->has('resetToken')) {
$resetToken = $request->getSession()->get('resetToken');
if ($model->confirmResetToken($user, $resetToken)) {
$encodedPassword = $model->checkNewPassword($user, $hasher, $data['plainPassword']);
$user->setPassword($encodedPassword);
$model->saveEntity($user);
$this->addFlashMessage('mautic.user.user.notice.passwordreset.success');
$request->getSession()->remove('resetToken');
return $this->redirectToRoute('login');
}
return $this->delegateView([
'viewParameters' => [
'form' => $form->createView(),
],
'contentTemplate' => '@MauticUser/Security/resetconfirm.html.twig',
'passthroughVars' => [
'route' => $action,
],
]);
} else {
$this->addFlashMessage('mautic.user.user.notice.passwordreset.missingtoken');
return $this->redirectToRoute('mautic_user_passwordresetconfirm');
}
}
}
}
return $this->delegateView([
'viewParameters' => [
'form' => $form->createView(),
],
'contentTemplate' => '@MauticUser/Security/resetconfirm.html.twig',
'passthroughVars' => [
'route' => $action,
],
]);
}
}

View File

@@ -0,0 +1,485 @@
<?php
namespace Mautic\UserBundle\Controller;
use Mautic\CoreBundle\Controller\FormController;
use Mautic\CoreBundle\Factory\PageHelperFactoryInterface;
use Mautic\UserBundle\Entity;
use Mautic\UserBundle\Entity\PermissionRepository;
use Mautic\UserBundle\Entity\UserRepository;
use Mautic\UserBundle\Model\RoleModel;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\PreconditionRequiredHttpException;
class RoleController extends FormController
{
/**
* Generate's default role list view.
*
* @param int $page
*
* @return Response
*/
public function indexAction(Request $request, PageHelperFactoryInterface $pageHelperFactory, $page = 1)
{
if (!$this->security->isGranted('user:roles:view')) {
return $this->accessDenied();
}
$this->setListFilters();
$pageHelper = $pageHelperFactory->make('mautic.role', $page);
$limit = $pageHelper->getLimit();
$start = $pageHelper->getStart();
$orderBy = $request->getSession()->get('mautic.role.orderby', 'r.name');
$orderByDir = $request->getSession()->get('mautic.role.orderbydir', 'ASC');
$filter = $request->get('search', $request->getSession()->get('mautic.role.filter', ''));
$tmpl = $request->isXmlHttpRequest() ? $request->get('tmpl', 'index') : 'index';
$model = $this->getModel('user.role');
\assert($model instanceof RoleModel);
$items = $model->getEntities(
[
'start' => $start,
'limit' => $limit,
'filter' => $filter,
'orderBy' => $orderBy,
'orderByDir' => $orderByDir,
]
);
$request->getSession()->set('mautic.role.filter', $filter);
$count = count($items);
if ($count && $count < ($start + 1)) {
$lastPage = $pageHelper->countPage($count);
$returnUrl = $this->generateUrl('mautic_role_index', ['page' => $lastPage]);
$pageHelper->rememberPage($lastPage);
return $this->postActionRedirect([
'returnUrl' => $returnUrl,
'viewParameters' => [
'page' => $lastPage,
'tmpl' => $tmpl,
],
'contentTemplate' => 'Mautic\UserBundle\Controller\RoleController::indexAction',
'passthroughVars' => [
'activeLink' => '#mautic_role_index',
'mauticContent' => 'role',
],
]);
}
$roleIds = [];
foreach ($items as $role) {
$roleIds[] = $role->getId();
}
$pageHelper->rememberPage($page);
return $this->delegateView([
'viewParameters' => [
'items' => $items,
'userCounts' => (!empty($roleIds)) ? $model->getRepository()->getUserCount($roleIds) : [],
'searchValue' => $filter,
'page' => $page,
'limit' => $limit,
'tmpl' => $tmpl,
'permissions' => [
'create' => $this->security->isGranted('user:roles:create'),
'edit' => $this->security->isGranted('user:roles:edit'),
'delete' => $this->security->isGranted('user:roles:delete'),
],
],
'contentTemplate' => '@MauticUser/Role/list.html.twig',
'passthroughVars' => [
'route' => $this->generateUrl('mautic_role_index', ['page' => $page]),
'mauticContent' => 'role',
],
]);
}
/**
* Generate's new role form and processes post data.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse|\Symfony\Component\HttpFoundation\RedirectResponse|Response
*/
public function newAction(Request $request)
{
if (!$this->security->isGranted('user:roles:create')) {
return $this->accessDenied();
}
// retrieve the entity
$entity = new Entity\Role();
$model = $this->getModel('user.role');
\assert($model instanceof RoleModel);
// set the return URL for post actions
$returnUrl = $this->generateUrl('mautic_role_index');
// set the page we came from
$page = $request->getSession()->get('mautic.role.page', 1);
$action = $this->generateUrl('mautic_role_action', ['objectAction' => 'new']);
// get the user form factory
$permissionsConfig = $this->getPermissionsConfig($entity);
$form = $model->createForm($entity, $this->formFactory, $action, ['permissionsConfig' => $permissionsConfig['config']]);
// /Check for a submitted form and process it
if ('POST' === $request->getMethod()) {
$valid = false;
if (!$cancelled = $this->isFormCancelled($form)) {
if ($valid = $this->isFormValid($form)) {
// set the permissions
$role = $request->request->all()['role'] ?? [];
$permissions = $role['permissions'] ?? null;
$model->setRolePermissions($entity, $permissions);
// form is valid so process the data
$model->saveEntity($entity);
$this->addFlashMessage('mautic.core.notice.created', [
'%name%' => $entity->getName(),
'%menu_link%' => 'mautic_role_index',
'%url%' => $this->generateUrl('mautic_role_action', [
'objectAction' => 'edit',
'objectId' => $entity->getId(),
]),
]);
}
}
if ($cancelled || ($valid && $this->getFormButton($form, ['buttons', 'save'])->isClicked())) {
return $this->postActionRedirect([
'returnUrl' => $returnUrl,
'viewParameters' => ['page' => $page],
'contentTemplate' => 'Mautic\UserBundle\Controller\RoleController::indexAction',
'passthroughVars' => [
'activeLink' => '#mautic_role_index',
'mauticContent' => 'role',
],
]);
} elseif ($valid) {
return $this->editAction($request, $entity->getId(), true);
}
}
return $this->delegateView([
'viewParameters' => [
'form' => $form->createView(),
'permissionsConfig' => $permissionsConfig,
],
'contentTemplate' => '@MauticUser/Role/form.html.twig',
'passthroughVars' => [
'activeLink' => '#mautic_role_new',
'route' => $this->generateUrl('mautic_role_action', ['objectAction' => 'new']),
'mauticContent' => 'role',
'permissionList' => $permissionsConfig['list'],
],
]);
}
/**
* Generate's role edit form and processes post data.
*
* @param int $objectId
* @param bool $ignorePost
*
* @return \Symfony\Component\HttpFoundation\JsonResponse|Response
*/
public function editAction(Request $request, $objectId, $ignorePost = false)
{
if (!$this->security->isGranted('user:roles:edit')) {
return $this->accessDenied();
}
/** @var RoleModel $model */
$model = $this->getModel('user.role');
$entity = $model->getEntity($objectId);
// set the page we came from
$page = $request->getSession()->get('mautic.role.page', 1);
// set the return URL
$returnUrl = $this->generateUrl('mautic_role_index', ['page' => $page]);
$postActionVars = [
'returnUrl' => $returnUrl,
'viewParameters' => ['page' => $page],
'contentTemplate' => 'Mautic\UserBundle\Controller\RoleController::indexAction',
'passthroughVars' => [
'activeLink' => '#mautic_role_index',
'mauticContent' => 'role',
],
];
// user not found
if (null === $entity) {
return $this->postActionRedirect(
array_merge($postActionVars, [
'flashes' => [
[
'type' => 'error',
'msg' => 'mautic.user.role.error.notfound',
'msgVars' => ['%id' => $objectId],
],
],
])
);
} elseif ($model->isLocked($entity)) {
// deny access if the entity is locked
return $this->isLocked($postActionVars, $entity, 'user.role');
}
$permissionsConfig = $this->getPermissionsConfig($entity);
$action = $this->generateUrl('mautic_role_action', ['objectAction' => 'edit', 'objectId' => $objectId]);
$form = $model->createForm($entity, $this->formFactory, $action, ['permissionsConfig' => $permissionsConfig['config']]);
// /Check for a submitted form and process it
if (!$ignorePost && 'POST' === $request->getMethod()) {
$valid = false;
if (!$cancelled = $this->isFormCancelled($form)) {
if ($valid = $this->isFormValid($form)) {
// set the permissions
$role = $request->request->all()['role'] ?? [];
$permissions = $role['permissions'] ?? null;
$model->setRolePermissions($entity, $permissions);
// form is valid so process the data
$model->saveEntity($entity, $this->getFormButton($form, ['buttons', 'save'])->isClicked());
$this->addFlashMessage('mautic.core.notice.updated', [
'%name%' => $entity->getName(),
'%menu_link%' => 'mautic_role_index',
'%url%' => $this->generateUrl('mautic_role_action', [
'objectAction' => 'edit',
'objectId' => $entity->getId(),
]),
]);
}
} else {
// unlock the entity
$model->unlockEntity($entity);
}
if ($cancelled || ($valid && $this->getFormButton($form, ['buttons', 'save'])->isClicked())) {
return $this->postActionRedirect($postActionVars);
} else {
// the form has to be rebuilt because the permissions were updated
$permissionsConfig = $this->getPermissionsConfig($entity);
$form = $model->createForm($entity, $this->formFactory, $action, ['permissionsConfig' => $permissionsConfig['config']]);
}
} else {
// lock the entity
$model->lockEntity($entity);
}
return $this->delegateView([
'viewParameters' => [
'form' => $form->createView(),
'permissionsConfig' => $permissionsConfig,
],
'contentTemplate' => '@MauticUser/Role/form.html.twig',
'passthroughVars' => [
'activeLink' => '#mautic_role_index',
'route' => $action,
'mauticContent' => 'role',
'permissionList' => $permissionsConfig['list'],
],
]);
}
private function getPermissionsConfig(Entity\Role $role): array
{
$permissionObjects = $this->security->getPermissionObjects();
$translator = $this->translator;
$permissionRepo = $this->doctrine->getRepository(Entity\Permission::class);
\assert($permissionRepo instanceof PermissionRepository);
$permissionsArray = ($role->getId()) ? $permissionRepo->getPermissionsByRole($role, true) : [];
$permissions = [];
$permissionsList = [];
/** @var \Mautic\CoreBundle\Security\Permissions\AbstractPermissions $object */
foreach ($permissionObjects as $object) {
if (!is_object($object)) {
continue;
}
if ($object->isEnabled()) {
$bundle = $object->getName();
$label = $translator->trans("mautic.{$bundle}.permissions.header");
// convert the permission bits from the db into readable names
$data = $object->convertBitsToPermissionNames($permissionsArray);
// get the ratio of granted/total
[$granted, $total] = $object->getPermissionRatio($data);
$permissions[$bundle] = [
'label' => $label,
'permissionObject' => $object,
'ratio' => [$granted, $total],
'data' => $data,
];
$perms = $object->getPermissions();
foreach ($perms as $level => $perm) {
$levelPerms = array_keys($perm);
$object->parseForJavascript($levelPerms);
$permissionsList[$bundle][$level] = $levelPerms;
}
}
}
// order permissions by label
uasort($permissions, fn ($a, $b): int => strnatcmp($a['label'], $b['label']));
return ['config' => $permissions, 'list' => $permissionsList];
}
/**
* Delete's a role.
*
* @param int $objectId
*
* @return Response
*/
public function deleteAction(Request $request, $objectId)
{
if (!$this->security->isGranted('user:roles:delete')) {
return $this->accessDenied();
}
$page = $request->getSession()->get('mautic.role.page', 1);
$returnUrl = $this->generateUrl('mautic_role_index', ['page' => $page]);
$success = 0;
$flashes = [];
$postActionVars = [
'returnUrl' => $returnUrl,
'viewParameters' => ['page' => $page],
'contentTemplate' => 'Mautic\UserBundle\Controller\RoleController::indexAction',
'passthroughVars' => [
'activeLink' => '#mautic_role_index',
'success' => $success,
'mauticContent' => 'role',
],
];
if (Request::METHOD_POST === $request->getMethod()) {
try {
$model = $this->getModel('user.role');
\assert($model instanceof RoleModel);
$entity = $model->getEntity($objectId);
if (null === $entity) {
$flashes[] = [
'type' => 'error',
'msg' => 'mautic.user.role.error.notfound',
'msgVars' => ['%id%' => $objectId],
];
} elseif ($model->isLocked($entity)) {
return $this->isLocked($postActionVars, $entity, 'user.role');
} else {
$model->deleteEntity($entity);
$name = $entity->getName();
$flashes[] = [
'type' => 'notice',
'msg' => 'mautic.core.notice.deleted',
'msgVars' => [
'%name%' => $name,
'%id%' => $objectId,
],
];
}
} catch (PreconditionRequiredHttpException $e) {
$flashes[] = [
'type' => 'error',
'msg' => $e->getMessage(),
];
}
} // else don't do anything
return $this->postActionRedirect(
array_merge($postActionVars, [
'flashes' => $flashes,
])
);
}
/**
* Deletes a group of entities.
*/
public function batchDeleteAction(Request $request, RoleModel $model): Response
{
$page = $request->getSession()->get('mautic.role.page', 1);
$returnUrl = $this->generateUrl('mautic_role_index', ['page' => $page]);
$flashes = [];
$postActionVars = [
'returnUrl' => $returnUrl,
'viewParameters' => ['page' => $page],
'contentTemplate' => 'Mautic\UserBundle\Controller\RoleController::indexAction',
'passthroughVars' => [
'activeLink' => '#mautic_role_index',
'mauticContent' => 'role',
],
];
if (Request::METHOD_POST === $request->getMethod()) {
$ids = json_decode($request->query->get('ids', ''));
$deleteIds = [];
$userRepo = $this->doctrine->getRepository(Entity\User::class);
\assert($userRepo instanceof UserRepository);
// Loop over the IDs to perform access checks pre-delete
foreach ($ids as $objectId) {
$entity = $model->getEntity($objectId);
$users = $userRepo->findByRole($entity);
if (null === $entity) {
$flashes[] = [
'type' => 'error',
'msg' => 'mautic.user.role.error.notfound',
'msgVars' => ['%id%' => $objectId],
];
} elseif (count($users)) {
$flashes[] = [
'type' => 'error',
'msg' => 'mautic.user.role.error.deletenotallowed',
'msgVars' => ['%name%' => $entity->getName()],
];
} elseif (!$this->security->isGranted('user:roles:delete')) {
$flashes[] = $this->accessDenied(true);
} elseif ($model->isLocked($entity)) {
$flashes[] = $this->isLocked($postActionVars, $entity, 'user.role', true);
} else {
$deleteIds[] = $objectId;
}
}
// Delete everything we are able to
if (!empty($deleteIds)) {
$entities = $model->deleteEntities($deleteIds);
$flashes[] = [
'type' => 'notice',
'msg' => 'mautic.user.role.notice.batch_deleted',
'msgVars' => [
'%count%' => count($entities),
],
];
}
} // else don't do anything
return $this->postActionRedirect(
array_merge($postActionVars, [
'flashes' => $flashes,
])
);
}
}

View File

@@ -0,0 +1,165 @@
<?php
declare(strict_types=1);
namespace Mautic\UserBundle\Controller;
use Doctrine\Persistence\ManagerRegistry;
use Mautic\CoreBundle\Controller\CommonController;
use Mautic\CoreBundle\Factory\ModelFactory;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Mautic\CoreBundle\Helper\UserHelper;
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
use Mautic\CoreBundle\Service\FlashBag;
use Mautic\CoreBundle\Translation\Translator;
use Mautic\PluginBundle\Helper\IntegrationHelper;
use Mautic\UserBundle\Exception\WeakPasswordException;
use Mautic\UserBundle\Security\SAML\Helper as SAMLHelper;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Exception;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Contracts\Translation\TranslatorInterface;
class SecurityController extends CommonController implements EventSubscriberInterface
{
public function __construct(
ManagerRegistry $doctrine,
ModelFactory $modelFactory,
UserHelper $userHelper,
CoreParametersHelper $coreParametersHelper,
EventDispatcherInterface $dispatcher,
Translator $translator,
FlashBag $flashBag,
?RequestStack $requestStack,
?CorePermissions $security,
private AuthorizationCheckerInterface $authorizationChecker,
) {
parent::__construct($doctrine, $modelFactory, $userHelper, $coreParametersHelper, $dispatcher, $translator, $flashBag, $requestStack, $security);
}
public function onRequest(RequestEvent $event): void
{
$controller = $event->getRequest()->attributes->get('_controller');
\assert(is_string($controller));
if (!str_contains($controller, self::class)) {
return;
}
// redirect user if they are already authenticated
if ($this->authorizationChecker->isGranted('IS_AUTHENTICATED_FULLY')
|| $this->authorizationChecker->isGranted('IS_AUTHENTICATED_REMEMBERED')
) {
$redirectUrl = $this->generateUrl('mautic_dashboard_index');
$event->setResponse(new RedirectResponse($redirectUrl));
}
}
/**
* Generates login form and processes login.
*/
public function loginAction(Request $request, AuthenticationUtils $authenticationUtils, IntegrationHelper $integrationHelper, TranslatorInterface $translator): Response
{
$error = $authenticationUtils->getLastAuthenticationError();
if (null !== $error) {
if ($error instanceof WeakPasswordException) {
$this->addFlash(FlashBag::LEVEL_ERROR, $translator->trans('mautic.user.auth.error.weakpassword', [], 'flashes'));
return $this->forward('Mautic\UserBundle\Controller\PublicController::passwordResetAction');
} elseif ($error instanceof Exception\BadCredentialsException) {
$msg = 'mautic.user.auth.error.invalidlogin';
} elseif ($error instanceof Exception\DisabledException) {
$msg = 'mautic.user.auth.error.disabledaccount';
} else {
$msg = $error->getMessage();
}
$this->addFlashMessage($msg, [], FlashBag::LEVEL_ERROR, null, false);
}
$request->query->set('tmpl', 'login');
// Get a list of SSO integrations
$integrations = $integrationHelper->getIntegrationObjects(null, ['sso_service'], true, null, true);
return $this->delegateView([
'viewParameters' => [
'last_username' => $authenticationUtils->getLastUsername(),
'integrations' => $integrations,
],
'contentTemplate' => '@MauticUser/Security/login.html.twig',
'passthroughVars' => [
'route' => $this->generateUrl('login'),
'mauticContent' => 'user',
'sessionExpired' => true,
],
]);
}
/**
* Do nothing.
*/
public function loginCheckAction(): void
{
}
/**
* The plugin should be handling this in it's listener.
*/
public function ssoLoginAction($integration): RedirectResponse
{
return new RedirectResponse($this->generateUrl('login'));
}
/**
* The plugin should be handling this in it's listener.
*/
public function ssoLoginCheckAction($integration): RedirectResponse
{
// The plugin should be handling this in it's listener
return new RedirectResponse($this->generateUrl('login'));
}
public function samlLoginRetryAction(Request $request, SAMLHelper $samlHelper, SessionInterface $session): Response
{
if (!$samlHelper->isSamlEnabled()) {
return new RedirectResponse($this->generateUrl('login'));
}
$session->invalidate();
$this->addFlashMessage('mautic.user.security.saml.clearsession', [], FlashBag::LEVEL_ERROR);
return $this->delegateView([
'viewParameters' => [
'loginRoute' => $this->generateUrl('lightsaml_sp.discovery'),
],
'contentTemplate' => '@MauticUser/Security/saml_login_retry.html.twig',
'passthroughVars' => [
'route' => $this->generateUrl('mautic_base_index'),
'mauticContent' => 'user',
'sessionExpired' => true,
],
]);
}
/**
* @return array<string, string>
*/
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => 'onRequest',
];
}
}

View File

@@ -0,0 +1,628 @@
<?php
declare(strict_types=1);
namespace Mautic\UserBundle\Controller;
use JMS\Serializer\SerializerInterface;
use Mautic\CoreBundle\Controller\FormController;
use Mautic\CoreBundle\Factory\PageHelperFactoryInterface;
use Mautic\CoreBundle\Helper\InputHelper;
use Mautic\CoreBundle\Helper\IpLookupHelper;
use Mautic\CoreBundle\Helper\LanguageHelper;
use Mautic\CoreBundle\Model\AuditLogModel;
use Mautic\CoreBundle\Model\FormModel;
use Mautic\EmailBundle\Helper\MailHelper;
use Mautic\UserBundle\Form\Type\ContactType;
use Mautic\UserBundle\Model\RoleModel;
use Mautic\UserBundle\Model\UserModel;
use Mautic\UserBundle\Security\SAML\Helper as SAMLHelper;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
class UserController extends FormController
{
/**
* Generate's default user list.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse|Response
*/
public function indexAction(Request $request, PageHelperFactoryInterface $pageHelperFactory, int $page = 1)
{
if (!$this->security->isGranted('user:users:view')) {
return $this->accessDenied();
}
$pageHelper = $pageHelperFactory->make('mautic.user', $page);
$this->setListFilters();
$currentUserId = $this->user->getId();
$limit = $pageHelper->getLimit();
$start = $pageHelper->getStart();
$orderBy = $request->getSession()->get('mautic.user.orderby', 'u.lastName, u.firstName, u.username');
$orderByDir = $request->getSession()->get('mautic.user.orderbydir', 'ASC');
$search = $request->get('search', $request->getSession()->get('mautic.user.filter', ''));
$search = html_entity_decode($search);
$request->getSession()->set('mautic.user.filter', $search);
// do some default filtering
$filter = ['string' => $search, 'force' => ''];
$tmpl = $request->isXmlHttpRequest() ? $request->get('tmpl', 'index') : 'index';
$users = $this->getModel('user.user')->getEntities(
[
'start' => $start,
'limit' => $limit,
'filter' => $filter,
'orderBy' => $orderBy,
'orderByDir' => $orderByDir,
]);
// Check to see if the number of pages match the number of users
$count = count($users);
if ($count && $count < ($start + 1)) {
// the number of entities are now less then the current page so redirect to the last page
$lastPage = $pageHelper->countPage($count);
$pageHelper->rememberPage($lastPage);
$returnUrl = $this->generateUrl('mautic_user_index', ['page' => $lastPage]);
return $this->postActionRedirect([
'returnUrl' => $returnUrl,
'viewParameters' => [
'page' => $lastPage,
'tmpl' => $tmpl,
],
'contentTemplate' => 'Mautic\UserBundle\Controller\UserController::indexAction',
'passthroughVars' => [
'activeLink' => '#mautic_user_index',
'mauticContent' => 'user',
],
]);
}
$pageHelper->rememberPage($page);
return $this->delegateView([
'viewParameters' => [
'items' => $users,
'searchValue' => $search,
'page' => $page,
'limit' => $limit,
'tmpl' => $tmpl,
'currentUserId' => $currentUserId,
'permissions' => [
'create' => $this->security->isGranted('user:users:create'),
'edit' => $this->security->isGranted('user:users:editother'),
'delete' => $this->security->isGranted('user:users:deleteother'),
],
],
'contentTemplate' => '@MauticUser/User/list.html.twig',
'passthroughVars' => [
'route' => $this->generateUrl('mautic_user_index', ['page' => $page]),
'mauticContent' => 'user',
],
]);
}
/**
* Generate's form and processes new post data.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse|Response
*/
public function newAction(Request $request, LanguageHelper $languageHelper, UserPasswordHasherInterface $hasher, SAMLHelper $samlHelper)
{
if (!$this->security->isGranted('user:users:create')) {
return $this->accessDenied();
}
/** @var UserModel $model */
$model = $this->getModel('user.user');
// retrieve the user entity
$user = $model->getEntity();
// set the return URL for post actions
$returnUrl = $this->generateUrl('mautic_user_index');
// set the page we came from
$page = $request->getSession()->get('mautic.user.page', 1);
// get the user form factory
$action = $this->generateUrl('mautic_user_action', ['objectAction' => 'new']);
$form = $model->createForm($user, $this->formFactory, $action);
// Check for a submitted form and process it
if ('POST' === $request->getMethod()) {
$valid = false;
if (!$cancelled = $this->isFormCancelled($form)) {
// check to see if the password needs to be rehashed
$formUser = $request->request->all()['user'] ?? [];
$submittedPassword = $formUser['plainPassword']['password'] ?? null;
$password = $model->checkNewPassword($user, $hasher, $submittedPassword);
if ($valid = $this->isFormValid($form)) {
// form is valid so process the data
$user->setPassword($password);
$model->saveEntity($user);
// check if the user's locale has been downloaded already, fetch it if not
$installedLanguages = $languageHelper->getSupportedLanguages();
if ($user->getLocale() && !array_key_exists($user->getLocale(), $installedLanguages)) {
$fetchLanguage = $languageHelper->extractLanguagePackage($user->getLocale());
// If there is an error, we need to reset the user's locale to the default
if ($fetchLanguage['error']) {
$user->setLocale(null);
$model->saveEntity($user);
$message = 'mautic.core.could.not.set.language';
$messageVars = [];
if (isset($fetchLanguage['message'])) {
$message = $fetchLanguage['message'];
}
if (isset($fetchLanguage['vars'])) {
$messageVars = $fetchLanguage['vars'];
}
$this->addFlashMessage($message, $messageVars);
}
}
$this->addFlashMessage('mautic.core.notice.created', [
'%name%' => $user->getName(),
'%menu_link%' => 'mautic_user_index',
'%url%' => $this->generateUrl('mautic_user_action', [
'objectAction' => 'edit',
'objectId' => $user->getId(),
]),
]);
}
}
if ($cancelled || ($valid && $this->getFormButton($form, ['buttons', 'save'])->isClicked())) {
return $this->postActionRedirect([
'returnUrl' => $returnUrl,
'viewParameters' => ['page' => $page, 'isSamlUser' => false],
'contentTemplate' => 'Mautic\UserBundle\Controller\UserController::indexAction',
'passthroughVars' => [
'activeLink' => '#mautic_user_index',
'mauticContent' => 'user',
],
]);
} elseif ($valid && !$cancelled) {
return $this->editAction($request, $languageHelper, $hasher, $samlHelper, $user->getId(), true);
}
}
return $this->delegateView([
'viewParameters' => ['form' => $form->createView(), 'isSamlUser' => false],
'contentTemplate' => '@MauticUser/User/form.html.twig',
'passthroughVars' => [
'activeLink' => '#mautic_user_new',
'route' => $action,
'mauticContent' => 'user',
],
]);
}
/**
* Generates edit form and processes post data.
*
* @param int $objectId
* @param bool $ignorePost
*
* @return \Symfony\Component\HttpFoundation\JsonResponse|Response
*/
public function editAction(Request $request, LanguageHelper $languageHelper, UserPasswordHasherInterface $hasher, SAMLHelper $samlHelper, $objectId, $ignorePost = false)
{
if (!$this->security->isGranted('user:users:edit')) {
return $this->accessDenied();
}
$model = $this->getModel('user.user');
\assert($model instanceof UserModel);
$user = $model->getEntity($objectId);
if (null === $user) {
return $this->postActionRedirect([
'returnUrl' => $this->generateUrl('mautic_user_index'),
'flashes' => [
[
'type' => 'error',
'msg' => 'mautic.user.user.error.notfound',
'msgVars' => ['%id%' => $objectId],
],
],
]);
}
$oldEmail = $user->getEmail();
/** @var AuditLogModel $auditLogModel */
$auditLogModel = $this->getModel('core.auditlog');
$auditLogRepository = $auditLogModel->getRepository();
$userActivity = $auditLogRepository->getLogsForUser($user);
$users = $model->getEntities();
$roleModel = $this->getModel('user.role');
\assert($roleModel instanceof RoleModel);
$roleRepository = $roleModel->getRepository();
$roles = $roleRepository->getEntities();
// set the page we came from
$page = $request->getSession()->get('mautic.user.page', 1);
// set the return URL
$returnUrl = $this->generateUrl('mautic_user_index', ['page' => $page]);
$postActionVars = [
'returnUrl' => $returnUrl,
'viewParameters' => ['page' => $page],
'contentTemplate' => 'Mautic\UserBundle\Controller\UserController::indexAction',
'passthroughVars' => [
'activeLink' => '#mautic_user_index',
'mauticContent' => 'user',
],
];
if ($model->isLocked($user)) {
// deny access if the entity is locked
return $this->isLocked($postActionVars, $user, 'user.user');
}
$action = $this->generateUrl('mautic_user_action', ['objectAction' => 'edit', 'objectId' => $objectId]);
$form = $model->createForm($user, $this->formFactory, $action);
$isSamlUser = $samlHelper->isSamlSession();
if ($isSamlUser) {
$form->remove('plainPassword');
}
// /Check for a submitted form and process it
if (!$ignorePost && 'POST' === $request->getMethod()) {
$valid = false;
if (!$cancelled = $this->isFormCancelled($form)) {
// check to see if the password needs to be rehashed
$formUser = $request->request->all()['user'] ?? [];
$submittedPassword = $formUser['plainPassword']['password'] ?? null;
$password = $model->checkNewPassword($user, $hasher, $submittedPassword);
$newEmail = $formUser['email'] ?? null;
if ($valid = $this->isFormValid($form)) {
// form is valid so process the data
$user->setPassword($password);
$model->saveEntity($user, $this->getFormButton($form, ['buttons', 'save'])->isClicked());
if (!empty($submittedPassword)) {
$model->sendChangePasswordInfo($user);
}
if ($newEmail !== $oldEmail) {
$model->sendChangeEmailInfo($oldEmail, $user);
}
// check if the user's locale has been downloaded already, fetch it if not
$installedLanguages = $languageHelper->getSupportedLanguages();
if ($user->getLocale() && !array_key_exists($user->getLocale(), $installedLanguages)) {
$fetchLanguage = $languageHelper->extractLanguagePackage($user->getLocale());
// If there is an error, we need to reset the user's locale to the default
if ($fetchLanguage['error']) {
$user->setLocale(null);
$model->saveEntity($user);
$message = 'mautic.core.could.not.set.language';
$messageVars = [];
if (isset($fetchLanguage['message'])) {
$message = $fetchLanguage['message'];
}
if (isset($fetchLanguage['vars'])) {
$messageVars = $fetchLanguage['vars'];
}
$this->addFlashMessage($message, $messageVars);
}
}
$this->addFlashMessage('mautic.core.notice.updated', [
'%name%' => $user->getName(),
'%menu_link%' => 'mautic_user_index',
'%url%' => $this->generateUrl('mautic_user_action', [
'objectAction' => 'edit',
'objectId' => $user->getId(),
]),
]);
}
} else {
// unlock the entity
$model->unlockEntity($user);
}
if ($cancelled || ($valid && $this->getFormButton($form, ['buttons', 'save'])->isClicked())) {
return $this->postActionRedirect($postActionVars);
}
} else {
// lock the entity
$model->lockEntity($user);
}
return $this->delegateView([
'viewParameters' => [
'form' => $form->createView(),
'logs' => $userActivity,
'users' => $users,
'roles' => $roles,
'editAction' => true,
'isSamlUser' => $isSamlUser,
],
'contentTemplate' => '@MauticUser/User/form.html.twig',
'passthroughVars' => [
'activeLink' => '#mautic_user_index',
'route' => $action,
'mauticContent' => 'user',
],
]);
}
/**
* Deletes a user object.
*
* @param int $objectId
*
* @return Response
*/
public function deleteAction(Request $request, $objectId)
{
if (!$this->security->isGranted('user:users:delete')) {
return $this->accessDenied();
}
$currentUser = $this->user;
$page = $request->getSession()->get('mautic.user.page', 1);
$returnUrl = $this->generateUrl('mautic_user_index', ['page' => $page]);
$success = 0;
$flashes = [];
$postActionVars = [
'returnUrl' => $returnUrl,
'viewParameters' => ['page' => $page],
'contentTemplate' => 'Mautic\UserBundle\Controller\UserController::indexAction',
'passthroughVars' => [
'activeLink' => '#mautic_user_index',
'route' => $returnUrl,
'success' => $success,
'mauticContent' => 'user',
],
];
if ('POST' === $request->getMethod()) {
// ensure the user logged in is not getting deleted
if ((int) $currentUser->getId() !== (int) $objectId) {
$model = $this->getModel('user.user');
\assert($model instanceof UserModel);
$entity = $model->getEntity($objectId);
if (null === $entity) {
$flashes[] = [
'type' => 'error',
'msg' => 'mautic.user.user.error.notfound',
'msgVars' => ['%id%' => $objectId],
];
} elseif ($model->isLocked($entity)) {
return $this->isLocked($postActionVars, $entity, 'user.user');
} else {
$model->deleteEntity($entity);
$name = $entity->getName();
$flashes[] = [
'type' => 'notice',
'msg' => 'mautic.core.notice.deleted',
'msgVars' => [
'%name%' => $name,
'%id%' => $objectId,
],
];
}
} else {
$flashes[] = [
'type' => 'error',
'msg' => 'mautic.user.user.error.cannotdeleteself',
];
}
} // else don't do anything
return $this->postActionRedirect(
array_merge($postActionVars, [
'flashes' => $flashes,
])
);
}
/**
* Contacts a user.
*
* @param int $objectId
*/
public function contactAction(Request $request, SerializerInterface $serializer, MailHelper $mailer, IpLookupHelper $ipLookupHelper, $objectId): Response|\Symfony\Component\HttpFoundation\RedirectResponse
{
$model = $this->getModel('user.user');
$user = $model->getEntity($objectId);
// user not found
if (null === $user) {
return $this->postActionRedirect([
'returnUrl' => $this->generateUrl('mautic_dashboard_index'),
'contentTemplate' => 'Mautic\UserBundle\Controller\UserController::contactAction',
'flashes' => [
[
'type' => 'error',
'msg' => 'mautic.user.user.error.notfound',
'msgVars' => ['%id%' => $objectId],
],
],
]);
}
$action = $this->generateUrl('mautic_user_action', ['objectAction' => 'contact', 'objectId' => $objectId]);
$form = $this->createForm(ContactType::class, [], ['action' => $action]);
$currentUser = $this->user;
if ('POST' === $request->getMethod()) {
$contact = $request->request->all()['contact'] ?? [];
$formUrl = $contact['returnUrl'] ?? '';
$returnUrl = $formUrl ? urldecode($formUrl) : $this->generateUrl('mautic_dashboard_index');
$valid = false;
if (!$cancelled = $this->isFormCancelled($form)) {
if ($valid = $this->isFormValid($form)) {
$subject = InputHelper::clean($form->get('msg_subject')->getData());
$body = InputHelper::clean($form->get('msg_body')->getData());
$mailer->setFrom($currentUser->getEmail(), $currentUser->getName());
$mailer->setSubject($subject);
$mailer->setTo($user->getEmail(), $user->getName());
$mailer->setBody($body);
$mailer->send();
$reEntity = $form->get('entity')->getData();
if (empty($reEntity)) {
$bundle = $object = 'user';
$entityId = $user->getId();
} else {
$bundle = $object = $reEntity;
if (strpos($reEntity, ':')) {
[$bundle, $object] = explode(':', $reEntity);
}
$entityId = $form->get('id')->getData();
}
$details = $serializer->serialize([
'from' => $currentUser->getName(),
'to' => $user->getName(),
'subject' => $subject,
'message' => $body,
], 'json');
$log = [
'bundle' => $bundle,
'object' => $object,
'objectId' => $entityId,
'action' => 'communication',
'details' => $details,
'ipAddress' => $ipLookupHelper->getIpAddressFromRequest(),
];
$auditLogModel = $this->getModel('core.auditlog');
\assert($auditLogModel instanceof AuditLogModel);
$auditLogModel->writeToLog($log);
$this->addFlashMessage('mautic.user.user.notice.messagesent', ['%name%' => $user->getName()]);
}
}
if ($cancelled || $valid) {
return $this->redirect($returnUrl);
}
} else {
$reEntityId = (int) $request->get('id');
$reSubject = InputHelper::clean($request->get('subject'));
$returnUrl = InputHelper::clean($request->get('returnUrl', $this->generateUrl('mautic_dashboard_index')));
$reEntity = InputHelper::clean($request->get('entity'));
$form->get('entity')->setData($reEntity);
$form->get('id')->setData($reEntityId);
$form->get('returnUrl')->setData($returnUrl);
if (!empty($reEntity) && !empty($reEntityId)) {
/** @var FormModel<object> $model */
$model = $this->getModel($reEntity);
$entity = $model->getEntity($reEntityId);
if (null !== $entity) {
$subject = $model->getUserContactSubject($reSubject, $entity);
$form->get('msg_subject')->setData($subject);
}
}
}
return $this->delegateView([
'viewParameters' => [
'form' => $form->createView(),
'user' => $user,
],
'contentTemplate' => '@MauticUser/User/contact.html.twig',
'passthroughVars' => [
'route' => $action,
'mauticContent' => 'user',
],
]);
}
/**
* Deletes a group of entities.
*/
public function batchDeleteAction(Request $request): Response
{
$page = $request->getSession()->get('mautic.user.page', 1);
$returnUrl = $this->generateUrl('mautic_user_index', ['page' => $page]);
$flashes = [];
$postActionVars = [
'returnUrl' => $returnUrl,
'viewParameters' => ['page' => $page],
'contentTemplate' => 'Mautic\UserBundle\Controller\UserController::indexAction',
'passthroughVars' => [
'activeLink' => '#mautic_user_index',
'mauticContent' => 'user',
],
];
if (Request::METHOD_POST === $request->getMethod()) {
$model = $this->getModel('user');
\assert($model instanceof UserModel);
$ids = json_decode($request->query->get('ids', ''));
$deleteIds = [];
$currentUser = $this->user;
// Loop over the IDs to perform access checks pre-delete
foreach ($ids as $objectId) {
$entity = $model->getEntity($objectId);
if ((int) $currentUser->getId() === (int) $objectId) {
$flashes[] = [
'type' => 'error',
'msg' => 'mautic.user.user.error.cannotdeleteself',
];
} elseif (null === $entity) {
$flashes[] = [
'type' => 'error',
'msg' => 'mautic.user.user.error.notfound',
'msgVars' => ['%id%' => $objectId],
];
} elseif (!$this->security->isGranted('user:users:delete')) {
$flashes[] = $this->accessDenied(true);
} elseif ($model->isLocked($entity)) {
$flashes[] = $this->isLocked($postActionVars, $entity, 'user', true);
} else {
$deleteIds[] = $objectId;
}
}
// Delete everything we are able to
if (!empty($deleteIds)) {
$entities = $model->deleteEntities($deleteIds);
$flashes[] = [
'type' => 'notice',
'msg' => 'mautic.user.user.notice.batch_deleted',
'msgVars' => [
'%count%' => count($entities),
],
];
}
} // else don't do anything
return $this->postActionRedirect(
array_merge($postActionVars, [
'flashes' => $flashes,
])
);
}
}