Symfony 4按用户名更改密码 - 电子邮件不能为空

时间:2018-05-25 13:15:31

标签: php symfony symfony4

简介

我一直在试图找出如何创建一个由用户名值管理的重置密码表单。

错误

Path        Message                           Invalid value     Violation
data.email  This value should not be blank.   null 



ConstraintViolation {#945 ▼
  -message: "This value should not be blank."
  -messageTemplate: "This value should not be blank."
  -parameters: [▶]
  -plural: null
  -root: Form {#620 ▶}
  -propertyPath: "data.email"
  -invalidValue: null
  -constraint: NotBlank {#477 …}
  -code: "c1051bb4-d103-4f74-8988-acbcafc7fdc3"
  -cause: null
}

预期

使用新密码更新我的用户对象。

我的代码

ForgotController.php

我知道这可能不是获取密码的正确方法,但搜索Symfony 4忘记密码表单会显示与我的版本无关的symfony2.4帖子

    <?php
        namespace App\Controller\User;

        use App\Entity\User;
        use App\Form\User\ChangePasswordType;
        use App\Repository\UserRepository;
        use Symfony\Bundle\FrameworkBundle\Controller\Controller;
        use Symfony\Component\HttpFoundation\Request;
        use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;

        class ForgotController extends Controller
        {
            public function forgot(Request $request, UserPasswordEncoderInterface $encoder)
            {
                $entityManager = $this->getDoctrine()->getManager();

                $changePassword = $request->request->get('change_password');

                $username = $changePassword['username'];
                $password = $changePassword['plainPassword']['first'];

                $user       = $entityManager->getRepository(User::class)->findBy(['username' => $username]);
                $userEntity = new User();

                if (!$user) {
                    $this->addFlash('danger', 'User not found for '. $username);
                }

                $form = $this->createForm(ChangePasswordType::class, $userEntity);
                $form->handleRequest($request);

                if ($form->isSubmitted() && $form->isValid()) {
                    try {
                        $pass = $encoder->encodePassword($userEntity, $password);

                        $userEntity->setPassword($pass);
                        $entityManager->flush();

                        $this->addFlash('success', 'Password Changed!');
                    } catch (Exception $e) {
                        $this->addFlash('danger', 'Something went skew-if. Please try again.');
                    }

                    return $this->redirectToRoute('login');
                }

                return $this->render('user/forgot.html.twig', array('form' => $form->createView()));
            }
        }

ChangePasswordType.php

<?php
    namespace App\Form\User;

    use App\Entity\User;
    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\Extension\Core\Type\PasswordType;
    use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
    use Symfony\Component\Form\Extension\Core\Type\TextType;
    use Symfony\Component\Form\FormBuilderInterface;
    use Symfony\Component\OptionsResolver\OptionsResolver;

    class ChangePasswordType extends AbstractType
    {
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            $builder->add('username', TextType::class)
                ->add('plainPassword', RepeatedType::class, array(
                'type' => PasswordType::class,
                'first_options' => array('label' => 'New Password'),
                'second_options' => array('label' => 'Repeat New Password')
            ));
        }

        public function configureOptions(OptionsResolver $resolver)
        {
            $resolver->setDefaults(array(
                'data_class' => User::class
            ));
        }
    }

forgot.html.twig

{% include 'builder/header.html.twig' %}

<div class="user-container" id="user-content">
    {% block body %}
        {% include 'builder/notices.html.twig' %}

        <div class="user-container">
            <i class="fas fa-user-edit fa-5x"></i>
        </div>

        <hr />

        {{ form_start(form) }}
            {{ form_row(form.username, { 'attr': {'class': 'form-control'} }) }}
            {{ form_row(form.plainPassword.first, { 'attr': {'class': 'form-control'} }) }}
            {{ form_row(form.plainPassword.second, { 'attr': {'class': 'form-control'} }) }}

            <div class="register-btn-container">
                <button class="btn btn-danger" id="return-to-dash-btn" type="button">Cancel!</button>
                <button class="btn btn-primary" type="submit">Update!</button>
            </div>
        {{ form_end(form) }}
    {% endblock %}
</div>

{% include 'builder/footer.html.twig' %}

我不确定为什么甚至会提到电子邮件,除非它试图将新用户插入数据库但是它不应该尝试根据我的控制器执行此操作?如何添加用户名识别的忘记密码表单?

2 个答案:

答案 0 :(得分:2)

由于您的更改密码表单只需要两个字段,我们将使用数组而不是用户实体。需要稍微调整一下ChangePasswordType:

    // ChangePasswordType
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            //'data_class' => User::class
        ));
    }

这是一个有效的忘记行动:

    public function forgot(
        Request $request, 
        UserPasswordEncoderInterface $encoder, 
        UserRepository $userRepository)
    {

        $userInfo = ['username' => null, 'plainPassword' => null];

        $form = $this->createForm(ChangePasswordType::class, $userInfo);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {

            $userInfo = $form->getData();
            $username = $userInfo['username'];
            $plainPassword = $userInfo['plainPassword'];

            $user = $userRepository->findOneBy(['username' => $username]);
            if ($user === null) {
                $this->addFlash('danger', 'Invalid username');
                return $this->redirectToRoute('forgot');
            }
            $password = $encoder->encodePassword($user, $plainPassword);

            $user->setPassword($password);
            $userRepository->flush();

            return $this->redirectToRoute('login');
        }

        return $this->render('user/forgot.html.twig', array('form' => $form->createView()));
    }

注入了UserRepository,它可以消除所有的学说废话。这里有一点需要注意,我会回来。

我们构建userInfo数组并让表单处理做它的事情。如果我们不必要,我们真的不想直接从请求对象获取属性。

然后我们让我们的实际用户实体进行更新。注意使用findOneBy而不是findBy。我们检查以确保用户名有效。如果你真的想得到花哨,那么你可以在表单中添加一个验证约束来自动进行检查。

我摆脱了所有的try / catch东西。它只是弄乱你的代码。到目前为止,如果抛出异常,那么它确实是异常的,并且可以由默认的异常处理程序处理。

你有正确的密码编码器。

然后我使用$ userRepository-&gt; flush();而不是$ entityManager-&gt; flush();开箱即用的存储库上没有刷新方法,因此您需要添加一个:

// UserRepository
public function flush()
{
    $this->_em->flush();
}

我个人喜欢只处理存储库而不是实体管理器。但是如果需要,你可以回去注入经理而不是存储库。你的电话。

正如评论中所提到的,您确实希望添加一些安全措施,以防止用户更改其他用户密码。

答案 1 :(得分:0)

沿着下面的方向实现一些东西 - 我已经像模板和路由一样留下了一些东西。这只是为了帮助你。

表单1:ForgottenUserType - 使用仅输入用户名/电子邮件并提交

<?php
namespace App\Controller\User;

use App\Entity\User;
use App\Form\User\ChangePasswordType;
use App\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;

class ResetPasswordController extends Controller
{
    /**
     * Action for when a user has forgotten their password, to request ForgottenUser form
     *
     * @param Request $request
     */
    public function requestAction(Request $request)
    {
        $tmpUser = new User();
        $entityManager = $this->getDoctrine()->getManager();

        $form = $this->createForm(ForgottenUserType::class, $tmpUser);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {

            $user = $entityManager->getRepository(User::class)->findBy(['username' => $tmpUser->getUsername()]);

            if ($user) {

                //check and set token
                if (null === $user->getConfirmationToken()) {
                    /** @var $tokenGenerator TokenGeneratorInterface */
                    $token = md5(uniqid($user->getUsername(), true)); //some unique token (you can create a nicer token generator in standalone class with a service)
                    $user->setConfirmationToken($token);
                    $user->setPasswordRequestedAt(new \DateTime());
                    $em->persist($user);
                    $em->flush();)

                    $this->addFlash('Info', 'If user is found, you will receive an email with further instructions.');

                    //send email using swiftmailer & include url with token
                }


            } else {
                //return to requestAction.
            }
        }

        //request template contains the ForgottenUserType form
        return $this->render(":path/to/template:request.html.twig", array(
            "forgotten_form" => $form->createView()
        ));

    }

    /**
     * Reset user password.
     *
     * @param Request $request
     * @param $token
     */
    public function resetAction(Request $request, $token)
    {
        $entityManager = $this->getDoctrine()->getManager();
        $user = $entityManager->getRepository(User::class)->findBy(['confirmationToken' => $token]); 

        if (null === $user) {                        
            return new RedirectResponse($this->generateUrl('resetting_request')); //to requestAction above. / create route
        }        

        $form = $this->createForm(ChangePasswordFormType::class, $user);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $user->SetConfirmationToken(null);
            $user->setPasswordRequestedAt(null);
            $entityManager->persist($user);
            $entityManager->flush()

            $this->addFlash("success", "Your password has been reset, log in now.");
            $url = $this->generateUrl('app.login'); //route to login page
            $response = new RedirectResponse($url);
            return $response;            
        }

        //reset template contains the ChangePasswordFormType form
        return $this->render(':path/to/forgottenpasswordtemplate:reset.html.twig', array(
            'token' => $token,
            'form' => $form->createView(),
        ));

    }
}

表单2:ChangePasswordFormType - 用户输入&amp;重复新密码。

{
"AWSTemplateFormatVersion": "2010-09-09",
"Parameters": {
    "EnvironmentValue": {
        "AllowedValues": [
            "Dev",
            "Test"
        ],
        "Default": "Dev",
        "Description": "What environment is this?",
        "Type": "String"
    },
    "RabbitMQErlangCookie": {
        "Description": "The erlang cookie to propagate to all nodes in the cluster",
        "Type": "String",
        "MinLength": "20",
        "MaxLength": "20",
        "Default": "TGFBTKPLRTOYFHNVSTWN",
        "AllowedPattern": "^[A-Z]*$",
        "NoEcho": true
    },
    "RabbitMQAdminUserID": {
        "Description": "The admin user name to create on the RabbitMQ cluster",
        "Type": "String",
        "MinLength": "5",
        "MaxLength": "20",
        "Default": "admin",
        "AllowedPattern": "[a-zA-Z0-9]*",
        "NoEcho": true
    },
    "RabbitMQAdminPassword": {
        "Description": "The admin password for the admin account",
        "Type": "String",
        "MinLength": "5",
        "MaxLength": "20",
        "Default": "xxxxxx",
        "AllowedPattern": "[a-zA-Z0-9!]*",
        "NoEcho": true
    },
    "InstanceAvailabilityZones" : {
        "Description" : "A list of avilability zones in which instances will be launched. ",
        "Type" : "CommaDelimitedList",
        "Default" : "us-east-1e,us-east-1d"
     },
    "Environment": {
        "Description": "The environment to confgiure (dev, test, stage, prod",
        "Type": "String",
        "AllowedValues": [
            "d",
            "t"
        ],
        "Default": "d",
        "NoEcho": false
    }
},
"Mappings": {
    "Environments" : {
        "Dev": {
            "VPCProtectedApp":"vpc-protected-app",
            "VPCProtectedDb":"vpc-protected-db",
            "VPCProtectedFe":"vpc-protected-fe",
            "ELB": "App-Dev",
            "SecurityGroup": "sg-soa-db",
            "Identifier": "d",
            "Prefix": "Dev",
            "RMQELB": "elb-soa-db-rmq-dev",
            "RMQELBTargetGroup": "elb-soarmq-target-group-dev",
            "RMQSubnets": "subnet-soa-db-1,subnet-soa-db-2",
            "RMQSecurityGroup":"sg-soa-db",
            "RMQClusterMin": "3",
            "RMQClusterMax": "3",
            "ConsulELB": "elb-soa-db-cons-dev",
            "ConsulSubnets": "subnet-soa-db-1,subnet-soa-db-2",
            "ConsulSecurityGroup":"sg-soa-db-cons",
            "ConsulClusterMin": "3",
            "ConsulClusterMax": "3"
        },
        "Test": {
            "VPCProtectedApp":"vpc-protected-app",
            "VPCProtectedDb":"vpc-protected-db",
            "VPCProtectedFe":"vpc-protected-fe",
            "ELB": "App-Dev",
            "SecurityGroup": "sg-soa-db",
            "Identifier": "t",
            "Prefix": "Test",
            "RMQELB": "elb-soa-db-rmq-test",
            "RMQELBTargetGroup": "elb-soarmq-target-group-test",
            "RMQSubnets": "subnet-soa-db-1,subnet-soa-db-2",
            "RMQSecurityGroup":"sg-soa-db",
            "RMQClusterMin": "3",
            "RMQClusterMax": "3",
            "ConsulELB": "elb-soa-db-cons-test",
            "ConsulSubnets": "subnet-soa-db-1,subnet-soa-db-2",
            "ConsulSecurityGroup":"sg-soa-db-cons",
            "ConsulClusterMin": "3",
            "ConsulClusterMax": "3"
        }
    }
},
"Resources": {
    "RabbitMQRole": {
        "Type": "AWS::IAM::Role",
        "Properties": {
            "AssumeRolePolicyDocument": {
                "Version": "2012-10-17",
                "Statement": [
                    {
                        "Effect": "Allow",
                        "Principal": {
                            "Service": [
                                "ec2.amazonaws.com"
                            ]
                        },
                        "Action": [
                            "sts:AssumeRole"
                        ]
                    }
                ]
            },
            "Path": "/",
            "Policies": [
                {
                    "PolicyName": "root",
                    "PolicyDocument": {
                        "Version": "2012-10-17",
                        "Statement": [
                            {
                                "Effect": "Allow",
                                "Action": [
                                    "autoscaling:DescribeAutoScalingInstances",
                                    "ec2:DescribeInstances"
                                ],
                                "Resource": [
                                    "*"
                                ]
                            },
                            {
                                "Effect": "Allow",
                                "Action": [
                                    "ecs:CreateCluster",
                                    "ecs:DeregisterContainerInstance",
                                    "ecs:DiscoverPollEndpoint",
                                    "ecs:Poll",
                                    "ecs:RegisterContainerInstance",
                                    "ecs:Submit*"
                                ],
                                "Resource": "*"
                            },
                            {
                                "Effect": "Allow",
                                "Action": "ec2:DescribeInstances",
                                "Resource": "*"
                            },
                            {
                                "Effect": "Allow",
                                "Action": [
                                    "logs:*"
                                ],
                                "Resource": [
                                    "arn:aws:logs:*:*:*"
                                ]
                            }
                        ]
                    }
                }
            ]
        }
    },
    "RabbitMQInstanceProfile": {
        "Type": "AWS::IAM::InstanceProfile",
        "Properties": {
            "Path": "/",
            "Roles": [
                {
                    "Ref": "RabbitMQRole"
                }
            ]
        }
    },
    "ELBSOARabbitMQ": {
        "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer",
        "Properties": {
            "Name": {"Fn::FindInMap" : ["Environments", {"Ref" : "EnvironmentValue" },"RMQELB"]},
            "Scheme": "internet-facing",
            "Subnets": [
                {
                    "Fn::ImportValue" : "subnet-soa-db-1" 
                },
                {
                    "Fn::ImportValue" : "subnet-soa-db-2" 
                }
            ],
            "SecurityGroups": [
                {
                    "Fn::ImportValue" : "sg-soa-db" 
                }
            ]
        }
    },
    "ELBSOARMQListener": {
        "Type": "AWS::ElasticLoadBalancingV2::Listener",
        "Properties": {
            "DefaultActions": [
                {
                    "TargetGroupArn": {
                        "Ref": "ELBSOARMQTargetGroup"
                    },
                    "Type": "forward"
                }
            ],
            "LoadBalancerArn": {
                "Ref": "ELBSOARabbitMQ"
            },
            "Port": 80,
            "Protocol": "HTTP"
        }
    },
    "ELBSOARMQListenerRule": {
        "Type": "AWS::ElasticLoadBalancingV2::ListenerRule",
        "Properties": {
            "Actions": [
                {
                    "TargetGroupArn": {
                        "Ref": "ELBSOARMQTargetGroup"
                    },
                    "Type": "forward"
                }
            ],
            "Conditions": [
                {
                    "Field": "path-pattern",
                    "Values": [
                        "/"
                    ]
                }
            ],
            "ListenerArn": {
                "Ref": "ELBSOARMQListener"
            },
            "Priority": 1
        }
    },
    "ELBSOARMQTargetGroup": {
        "Type": "AWS::ElasticLoadBalancingV2::TargetGroup",
        "Properties": {
            "TargetType": "instance",
            "HealthCheckIntervalSeconds": 30,
            "HealthCheckPort": 15672,
            "HealthCheckProtocol": "HTTP",
            "HealthCheckTimeoutSeconds": 3,
            "HealthyThresholdCount": 2,
            "Name":{"Fn::FindInMap" : ["Environments", {"Ref" : "EnvironmentValue" },"RMQELBTargetGroup"]},
            "Port": 15672,
            "Protocol": "HTTP",
            "UnhealthyThresholdCount": 2,
            "VpcId": {
                "Fn::ImportValue" : "vpc-protected-db" 
            }
        }
    },
    "SOARMQServerGroup": {
        "Type": "AWS::AutoScaling::AutoScalingGroup",
        "DependsOn": "ELBSOARabbitMQ",
        "Properties": {
            "LaunchConfigurationName": {
                "Ref": "SOARMQEc2InstanceLC"
            },
            "MinSize": "3",
            "MaxSize": "5",
            "TargetGroupARNs": [
                {
                    "Ref": "ELBSOARMQTargetGroup"
                }
            ],
            "Tags": [
                {
                    "ResourceType": "auto-scaling-group",
                    "ResourceId": "my-asg",
                    "InstanceName": "rabbitmq",
                    "PropagateAtLaunch": true,
                    "Value": "test",
                    "Key": "environment"
                },
                {
                    "ResourceType": "auto-scaling-group",
                    "ResourceId": "my-asg",
                    "InstanceName": "rabbitmq",
                    "PropagateAtLaunch": true,
                    "Value": "vavd-soa-rmq",
                    "Key": "Name"
                }
            ],
            "AvailabilityZones" : { "Ref" : "InstanceAvailabilityZones" },
            "VPCZoneIdentifier": [
                {
                    "Fn::ImportValue": "subnet-soa-db-1"
                },
                {
                    "Fn::ImportValue": "subnet-soa-db-2"
                }
            ]
        }
    },
    "SOARMQEc2InstanceLC": {
        "Type": "AWS::AutoScaling::LaunchConfiguration",
        "DependsOn": "ELBSOARabbitMQ",
        "Properties": {
            "IamInstanceProfile" : { "Ref" : "RabbitMQInstanceProfile" },
            "ImageId": "ami-5e414e24",
            "InstanceType": "m1.small",
            "KeyName": "soa_dev_us_east_1",
            "SecurityGroups": [
                {
                    "Fn::ImportValue" : "sg-soa-db" 
                }
            ],
            "UserData": {
                "Fn::Base64": {
                    "Fn::Join": [
                        "",
                        [
                            "#!/bin/bash -xe\n",
                            "sudo su\n",
                            "exec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1\n",
                            "echo \"1. Installing yum updates\"\n",
                            "sudo yum update -y\n",
                            "sudo yum install wget -y\n",
                            "sudo yum install socat -y\n",
                            "yum install -y aws-cfn-bootstrap\n",
                            "echo \"2. Downloading erlang distro and install\"\n",
                            "wget https://github.com/rabbitmq/erlang-rpm/releases/download/v20.3.0/erlang-20.3-1.el6.x86_64.rpm\n",
                            "sudo rpm -ivh erlang-20.3-1.el6.x86_64.rpm\n",
                            "export EC2_PUBLIC_IP=$(curl http://169.254.169.254/latest/meta-data/public-ipv4)\n",
                            "echo \"3. Downloading rabbitmq distro and installing\"\n",
                            "wget http://dl.bintray.com/rabbitmq/all/rabbitmq-server/3.7.4/rabbitmq-server-3.7.4-1.el6.noarch.rpm\n",
                            "sudo rpm -Uvh rabbitmq-server-3.7.4-1.el6.noarch.rpm\n",
                            "export RABBITMQ_USE_LONGNAME=true\n",
                            "echo \"4. Setting the erlang cookie for clustering\"\n",
                            "sudo sh -c \"echo ''",
                            {
                                "Ref": "RabbitMQErlangCookie"
                            },
                            "'' > /var/lib/rabbitmq/.erlang.cookie\"\n",
                            "sudo chown rabbitmq:rabbitmq /var/lib/rabbitmq/.erlang.cookie\n",
                            "sudo chmod 600 /var/lib/rabbitmq/.erlang.cookie\n",
                            "echo \"5. Writing the rabbitmq configurations for AWS Autocluster Group peer discovery\"\n",
                            "sudo cat << EOF > /etc/rabbitmq/rabbitmq.conf\n",
                            "cluster_formation.peer_discovery_backend = rabbit_peer_discovery_aws\n",
                            "cluster_formation.aws.region = us-east-1\n",
                            "cluster_formation.aws.use_autoscaling_group = true\n",
                            "log.console.level = debug\n",
                            "log.file.level = debug\n",
                            "EOF\n",
                            "echo \"6. Enable the management and peer discovery plugins\"\n",
                            "sudo rabbitmq-plugins enable rabbitmq_management\n",
                            "sudo rabbitmq-plugins --offline enable rabbitmq_peer_discovery_aws\n",
                            "echo \"7. Restart the service - stop the app prior to clustering\"\n",
                            "sudo service rabbitmq-server restart\n",
                            "sudo rabbitmqctl stop_app\n",
                            "sudo rabbitmqctl reset\n",
                            "echo \"8. Starting the application\"\n",
                            "sudo rabbitmqctl start_app\n",
                            "echo \"9. Adding admin user and setting permissions\"\n",
                            "sudo rabbitmqctl add_user ",
                            {
                                "Ref": "RabbitMQAdminUserID"
                            },
                            " ",
                            {
                                "Ref": "RabbitMQAdminPassword"
                            },
                            "\n",
                            "sudo rabbitmqctl set_user_tags ",
                            {
                                "Ref": "RabbitMQAdminUserID"
                            },
                            " administrator\n",
                            "sudo rabbitmqctl set_permissions -p / ",
                            {
                                "Ref": "RabbitMQAdminUserID"
                            },
                            " \".*\" \".*\" \".*\" \n",
                            "echo \"10. Configuration complete!\"\n"
                        ]
                    ]
                }
            }
        }
    }
}

控制器:ResetPasswordController - 处理表单1的用户查找请求和表单2的密码重置请求:

div