In my application, once a user logs in, taken to the home page where he can view his details. There is a button "Edit Profile" where the user will be taken to a page where he can edit the data. Once the editing successes, he is redirected back to the home page. But here, it is redirected to the login page. I think the session is expired unexpectedly. How to overcome this issue?
// This is my update info controller
/**
* #Route("/update/{id}", name="update")
* #param $id
* #param Request $request
* #param UserPasswordEncoderInterface $passwordEncoder
* #param UserInterface $loggedUser
* #return \Symfony\Component\HttpFoundation\RedirectResponse|Response
*/
public function updateUser($id,Request $request, UserPasswordEncoderInterface $passwordEncoder, UrlGeneratorInterface $urlGenerator){
$loggedUser = $this->get('security.token_storage')->getToken()->getUser()->getId();
if ($id == $loggedUser){
$em = $this->getDoctrine()->getManager();
$conn =$em->getConnection();
$user = $em->find(User::class,$id);
$form = $this->createForm(RegisterType::class,$user, [
'validation_groups' => ['update'],
]);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
$file = $request->files->get('register')['image'];
if($file){
$fileName = md5(uniqid()).'.'.$file->guessExtension();
$file->move(
$this->getParameter('uploads_dir'), $fileName
);
$user->setImage($fileName);
}
if($user->getPassword() !="") {
$user->setPassword($passwordEncoder->encodePassword($user,$user->getPassword()));
$sql = '
UPDATE user
SET first_name = :firstName, last_name = :lastName, id_number = :idNumber, phone_number = :phoneNumber, address = :address, password = :password
WHERE id= :id
';
$stmt = $conn->prepare($sql);
$stmt->execute(['firstName' => $user->getFirstName(),
'lastName' => $user->getLastName(),
'idNumber' => $user->getIdNumber(),
'phoneNumber' => $user->getPhoneNumber(),
'address' => $user->getAddress(),
'password' => $user->getPassword(),
'id' => $id]);
} else {
$sql = '
UPDATE user
SET first_name = :firstName, last_name = :lastName, id_number = :idNumber, phone_number = :phoneNumber, address = :address
WHERE id= :id
';
$stmt = $conn->prepare($sql);
$stmt->execute(['firstName' => $user->getFirstName(),
'lastName' => $user->getLastName(),
'idNumber' => $user->getIdNumber(),
'phoneNumber' => $user->getPhoneNumber(),
'address' => $user->getAddress(),
'id' => $id]);
}
return new RedirectResponse($urlGenerator->generate('home'));
}
} else {
return new RedirectResponse($urlGenerator->generate('home'));
}
return $this->render('register/update.html.twig', [
'form'=>$form->createView(),
]);
}
// This is RegisterType form
class RegisterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email',EmailType::class,[
'label'=>'Email',
'required' => false,
'attr'=>['placeholder'=>"Email"]
])
->add('password',RepeatedType::class,[
'type' => PasswordType::class,
'invalid_message' => 'The password fields must match.',
'required' => false,
'options' => ['attr' => ['class' => 'password-field']],
'first_options' => ['label' => 'Password','attr'=>['placeholder'=>"Password"]],
'second_options' => ['label' => 'Confirm Password','attr'=>['placeholder'=>"Confirm Password"]],
])
->add('firstName',TextType::class,['label'=>'First Name', 'attr'=>['placeholder'=>"First Name"]])
->add('lastName',TextType::class,['label'=>'Last Name','attr'=>['placeholder'=>"Last Name"]])
->add('address',TextareaType::class,['required' => false,'label'=>'Address','attr'=>['placeholder'=>"Address"]])
->add('idNumber',TextType::class,['label'=>'NIC Number','attr'=>['placeholder'=>"NIC Number"]])
->add('phoneNumber',TelType::class,['label'=>'Phone Number','attr'=>['placeholder'=>"Phone Number"]])
->add('image',FileType::class,['label'=>'Photo','required'=>false,'attr'=>['hidden'=>"hidden", 'accept'=>"image/jpeg, image/png"]])
->add('save',SubmitType::class,[
'label'=>'Register',
'attr' => [
'class'=>"btn btn-outline-success float-right"
]
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
// This is my User Class
class User implements UserInterface{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=180, unique=true)
* #Assert\Email()
* #Assert\NotBlank()
*/
private $email;
/**
* #ORM\Column(type="json")
*/
private $roles = [];
/**
* #var string The hashed password
* #ORM\Column(type="string")
* #Assert\NotBlank()
*/
private $password;
/**
* #ORM\Column(type="string",length=255)
* #Assert\NotBlank(groups={"update"})
*
*/
private $firstName;
/**
* #ORM\Column(type="string",length=255)
* #Assert\NotBlank(groups={"update"})
*/
private $lastName;
/**
* #ORM\Column(type="string",length=255,nullable=true)
*
*/
private $image;
/**
* #ORM\Column(type="string", nullable=true)
*/
private $address;
/**
* #ORM\Column(type="string",length=10)
* #Assert\Length("10",groups={"update"})
*/
private $phoneNumber;
/**
* #ORM\Column(type="string",length=10)
* #Assert\NotBlank(groups={"update"})
* #Assert\Length("10",groups={"update"})
*/
private $idNumber;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Vehicle", mappedBy="user")
*/
private $vehicle;
/**
* #ORM\OneToOne(targetEntity="App\Entity\Account", inversedBy="user")
*/
private $account;
public function __construct()
{
$this->vehicle = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getEmail()
{
return $this->email;
}
public function setEmail( $email): self
{
$this->email = $email;
return $this;
}
/**
* A visual identifier that represents this user.
*
* #see UserInterface
*/
public function getUsername()
{
return (string) $this->email;
}
public function getRoles(): ?array
{
return $this->roles;
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
public function getPassword()
{
return $this->password;
}
public function setPassword($password): self
{
$this->password = $password;
return $this;
}
/**
* #see UserInterface
*/
public function getSalt()
{
// not needed when using the "bcrypt" algorithm in security.yaml
}
/**
* #see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
public function getFirstName()
{
return $this->firstName;
}
public function setFirstName( $firstName): self
{
$this->firstName = $firstName;
return $this;
}
public function getLastName()
{
return $this->lastName;
}
public function setLastName( $lastName): self
{
$this->lastName = $lastName;
return $this;
}
public function getImage(): ?string
{
return $this->image;
}
public function setImage(string $image): self
{
$this->image = $image;
return $this;
}
public function getAddress()
{
return $this->address;
}
public function setAddress( $address): self
{
$this->address = $address;
return $this;
}
public function getIdNumber()
{
return $this->idNumber;
}
public function setIdNumber( $idNumber): self
{
$this->idNumber = $idNumber;
return $this;
}
public function getPhoneNumber()
{
return $this->phoneNumber;
}
public function setPhoneNumber( $phoneNumber): self
{
$this->phoneNumber = $phoneNumber;
return $this;
}
/**
* #return Collection|Vehicle[]
*/
public function getVehicle(): Collection
{
return $this->vehicle;
}
public function addVehicle(Vehicle $vehicle): self
{
if (!$this->vehicle->contains($vehicle)) {
$this->vehicle[] = $vehicle;
$vehicle->setUser($this);
}
return $this;
}
public function removeVehicle(Vehicle $vehicle): self
{
if ($this->vehicle->contains($vehicle)) {
$this->vehicle->removeElement($vehicle);
// set the owning side to null (unless already changed)
if ($vehicle->getUser() === $this) {
$vehicle->setUser(null);
}
}
return $this;
}
public function getAccount(): ?Account
{
return $this->account;
}
public function setAccount(?Account $account): self
{
$this->account = $account;
return $this;
}
}
Assuming you are using the default voter and entity security user provider.
This should apply for Symfony 3.4+, but knowing which version of Symfony you are using, would grant other approaches.
At the end of every request (unless your firewall is stateless), your
User object is serialized to the session. At the beginning of the next
request, it's deserialized and then passed to your user provider to
"refresh" it (e.g. Doctrine queries for a fresh user).
Then, the two User objects (the original from the session and the
refreshed User object) are "compared" to see if they are "equal". By
default, the core AbstractToken class compares the return values of
the getPassword(), getSalt() and getUsername() methods. If any of
these are different, your user will be logged out. This is a security
measure to make sure that malicious users can be de-authenticated if
core user data changes.
However, in some cases, this process can cause unexpected
authentication problems. If you're having problems authenticating, it
could be that you are authenticating successfully, but you immediately
lose authentication after the first redirect.
Source: https://symfony.com/doc/current/security/user_provider.html#understanding-how-users-are-refreshed-from-the-session
The issue appears to be caused by
$user->setPassword($passwordEncoder->encodePassword($user,$user->getPassword()));
Which will generate a new hashed password from the submitted password, invalidating the user state, even if it is identical.
You would need to store the user's plain-text password, and validate if it has changed, and apply the password changes only if it changed.
Additionally your image form setting is not valid, since your User::$image requires a string, but the form will upload a File object (causing an invalid Entity state or calling File::__toString and changing the image). You should use a separate property for the image upload and manually draw the current image in your view or consider using a data transformer in your Form rather than in your controller to handle the state change. See: https://symfony.com/doc/current/form/data_transformers.html
Replace your current password and image form fields with the plainPassword and uploadImage fields.
class RegisterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
//...
->add('plainPassword',RepeatedType::class,[
'type' => PasswordType::class,
'invalid_message' => 'The password fields must match.',
'required' => false,
'options' => ['attr' => ['class' => 'password-field']],
'first_options' => ['label' => 'Password','attr'=>['placeholder'=>"Password"]],
'second_options' => ['label' => 'Confirm Password','attr'=>['placeholder'=>"Confirm Password"]],
])
->add('uploadImage',FileType::class,['label'=>'Photo','required'=>false,'attr'=>['hidden'=>"hidden", 'accept'=>"image/jpeg, image/png"]]);
//...
}
You should also seriously consider using a DTO, instead of the direct User entity from Doctrine to manage your data, to prevent an invalid entity state.
Then create the properties and getter/setter methods in you User entity, to store the form values.
class User implements UserInterface
{
/**
* #var string
*/
private $plainPassword = '';
/**
* #var File|null
*/
private $uploadImage;
public function getPlainPassword(): string
{
return $this->plainPassword;
}
public function setPlainPassword(string $plainPassword): void
{
$this->plainPassword = $plainPassword;
}
/**
* #see UserInterface
*/
public function eraseCredentials()
{
$this->plainPassword = null;
}
public function getUploadImage(): ?File
{
return $this->uploadImage;
}
public function setUploadImage(?File $file): void
{
$this->uploadImage = $file;
}
//...
}
Since you're using the Entity manager and the RegisterType field, you can remove the manual update queries. Since the $form->handleRequest() will be applying the changes directly to the User object. I also suggest using the Paramconverter to benefit from the entity Dependency Injection for the User object.
/**
* #Route("/{user}/update", name="update", requirements={ "user":"\d+" }, methods={"GET","POST"})
* #param User $user
* #param Request $request
* #param UserPasswordEncoderInterface $passwordEncoder
* #param UserInterface $loggedUser
* #return Response
*/
public function updateUser(User $user, Request $request, UserPasswordEncoderInterface $passwordEncoder, UrlGeneratorInterface $urlGenerator): Response
{
$loggedinUser = $this->getUser(); //helper from ControllerTrait
if ($loggedinUser && loggedinUser->getId() === $user->getId()) {
$form = $this->createForm(RegisterType::class,$user, [
'validation_groups' => ['update'],
]);
$currentImage = $user->getImage();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
if ($file = $user->getUploadImage()) {
//this logic should be moved to the Form using a data transformer
$fileName = md5(uniqid()).'.'.$file->guessExtension();
$file->move(
$this->getParameter('uploads_dir'), $fileName
);
$user->setImage($fileName);
}
if ('' !== $user->getPlainPassword() && !$passwordEncoder->isPasswordValid($user->getPassword(), $user->getPlainPassword())) {
//change password only when changed
$user->setPassword($passwordEncoder->encodePassword($user, $user->getPlainPassword()));
$user->eraseCredentials();
}
$em = $this->getDoctrine()->getManager();
$em->flush();
return new RedirectResponse($urlGenerator->generate('home'));
}
return $this->render('register/update.html.twig', [
'form'=>$form->createView(),
]);
}
return new RedirectResponse($urlGenerator->generate('home'));
}
If you are using Symfony < 4.1, you will need to implement \Serializable and add the serialize and unserialize methods to your User class, otherwise your entire User object will be serialized and invalidated on any change.
class User implements UserInterface, \Serializable
{
//...
/** #see \Serializable::serialize() */
public function serialize()
{
return serialize(array(
$this->id,
$this->username,
$this->password,
//$this->roles //(optional)
));
}
/** #see \Serializable::unserialize() */
public function unserialize($serialized)
{
list (
$this->id,
$this->username,
$this->password,
//$this->roles //(optional)
) = unserialize($serialized, array('allowed_classes' => false));
}
}
Use a temporary field to hold the plaintext password in the form pre-encoding/pre-hashing (see: https://symfony.com/doc/4.0/doctrine/registration_form.html#registration-password-max - field is called plainPassword or similar).
I suspect that there is some unexpected behaviour when setting an empty password, which might invalidate session cache (symfony stores some user data to determine, if the user must be reloaded from database, and if relevant data changed, user might be logged out). The redirection alone should definitely not logout a user.
Hopefully, this will be enough.
Related
I am trying to migrate a some parts of a project made with Typescript + Express + Firebase to Symfony 5 and MySQL. But I don't get why the ValidatorInterface isn't working.
When I submit the form with unexpected characters (i.e.: a password with 3 charactesrs) it creates the user despites the validation constraints assigned in the User entity. The only constraint validation that actually works despites it does not show any form errors, is the UniqueEntity.
This is how my controller looks like:
/**
* GET: Display form
* POST: Creates a user
*
* #Route("/users/crear", name="create_user", methods={"GET", "POST"})
*/
public function create_user(Request $request, EntityManagerInterface $entityManager, ValidatorInterface $validator)
{
$user = new User();
// Form
if ($request->isMethod('GET')) {
$form = $this->createForm(CrearUserType::class, $user);
return $this->render('user/create.html.twig', [
'form' => $form->createView(),
'errors' => []
]);
}
// Form submit
$data = $request->request->get('create_user');
$errors = [];
$user->setName($data['name']);
$user->setEmail($data['email']);
$user->setPassword($data['password']);
$user->setCreatedAt(new \DateTime());
$user->setUpdatedAt(new \DateTime());
$form = $this->createForm(CrearUserType::class, $user);
// Entity validation
$errors = $validator->validate($user);
if (count($errors) > 0) { // This is always 0
$user->setPassword('');
return $this->render('user/create.html.twig', [
'form' => $form->createView(),
'user' => $user,
'errors' => $errors
]);
}
try {
$user->setPassword(password_hash($user->getPassword(), PASSWORD_BCRYPT));
$entityManager->persist($user);
$entityManager->flush();
$this->addFlash(
'success',
'User '.$user->getName().' has been saved.'
);
return $this->redirectToRoute('users');
} catch(\Exception $exception) {
$user->setPassword('');
$this->addFlash(
'error',
'Server error: ' . $exception->getMessage()
);
return $this->render('user/create.html.twig', [
'form' => $form->createView(),
'user' => $user,
'errors' => $errors
]);
}
}
And this is my entity:
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
* #UniqueEntity("email")
*/
class User
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
* #Assert\NotBlank(message="Name is mandatory")
*/
private $name;
/**
* #ORM\Column(type="string", length=255, unique=true)
* #Assert\NotBlank(message="Email is mandatory")
* #Assert\Email(
* message="Invalid email address"
* )
*/
private $email;
/**
* #ORM\Column(type="string", length=60)
* #Assert\NotBlank(message="Password is mandatory")
* #Assert\GreaterThanOrEqual(
* value=6,
* message="The password has to be at least 6 chars long"
* )
*/
private $password;
/**
* #ORM\Column(type="date")
*/
private $createdAt;
/**
* #ORM\Column(type="date")
*/
private $updatedAt;
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
public function getPassword(): ?string
{
return $this->password;
}
public function setPassword(?string $password): self
{
$this->password = $password;
return $this;
}
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->createdAt;
}
public function setCreatedAt(\DateTimeInterface $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
public function getUpdatedAt(): ?\DateTimeInterface
{
return $this->updatedAt;
}
public function setUpdatedAt(\DateTimeInterface $updatedAt): self
{
$this->updatedAt = $updatedAt;
return $this;
}
}
So, any clues on what is wrong here?
I'm not sure of my response, because I never handle request with a ValidatorInterface. I use the handlerequest method provided by controller.
You created the form
$form = $this->createForm(CrearUserType::class, $user);
But you forgot to handle request
$form->handleRequest($request);
So your form does not validate data forwarded by request. Try to add this line just after the $form creation. Then, you can test that the user entity is valid with
if ($form->isValid()) {
//no errors
}
//error exists
I have table rows with created employees. Every employee has unique ID. When user clicks on employee, he can add a new skill for this employee with name and level fields. I wrote the code that successfully creates employees and show it, but I don't know how to add skill to unique ID, that it was represented just for one specific user, not all of them.
I made two tables with doctrine - Person and Skill, as well as two controllers for them. Also, I made assotiations ManyToOne, so that one employee could have multiple skills.
Entity for Skill
class Skill
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Person", inversedBy="skills")
* #ORM\JoinColumn(nullable=false)
*/
private $person;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
/**
* #ORM\Column(type="string", length=255)
*/
private $level;
public function getId(): ?int
{
return $this->id;
}
public function getPerson(): ?Person
{
return $this->person;
}
public function setPerson(?Person $person): self
{
$this->person = $person;
return $this;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getLevel(): ?string
{
return $this->level;
}
public function setLevel(string $level): self
{
$this->level = $level;
return $this;
}
}
Entity for Person
class Person
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Skill", mappedBy="person")
*/
private $skills;
public function __construct()
{
$this->skills = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
/**
* #return Collection|Skill[]
*/
public function getSkills(): Collection
{
return $this->skills;
}
public function addSkill(Skill $skill): self
{
if (!$this->skills->contains($skill)) {
$this->skills[] = $skill;
$skill->setPerson($this);
}
return $this;
}
public function removeSkill(Skill $skill): self
{
if ($this->skills->contains($skill)) {
$this->skills->removeElement($skill);
// set the owning side to null (unless already changed)
if ($skill->getPerson() === $this) {
$skill->setPerson(null);
}
}
return $this;
}
}
Function that shoud create/add new skill to employee
/**
* #Route("/skill/new", name="new_skill")
* Method({"GET", "POST"})
*/
public function new(Request $request) {
$skill = new Skill();
$form = $this->createFormBuilder($skill)
->add('name', TextType::class, array('attr' => array('class' => 'form-control')))
->add('level', TextareaType::class, array(
'attr' => array('class' => 'form-control')
))
->add('save', SubmitType::class, array(
'label' => 'Create',
'attr' => array('class' => 'btn btn-primary mt-3')
))
->getForm();
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
$skill = $form->getData();
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($skill);
$entityManager->flush();
return $this->redirectToRoute('skill_list');
}
return $this->render('main/new.html.twig', array(
'form' => $form->createView()
));
}
You will have to persist($person) as well
You will have to fetch the Person and set him to Skill before your flush
/**
* #Route("/skill/new", name="new_skill")
* Method({"GET", "POST"})
*/
public function new(Request $request) {
$personId = $request->request->get('person_id'); //POST data
$person = $entityManager->getRepository(Person::class)
->find($personId);
$skill = new Skill();
$skill->setPerson($person);
$form = $this->createFormBuilder($skill)
/// add fields
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/// $skill = $form->getData();
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($skill);
$entityManager->flush();
return $this->redirectToRoute('skill_list');
}
return $this->render('main/new.html.twig', array(
'form' => $form->createView()
));
}
In your case, you delegate a technique to a person that is not really correct. Since when you want to add this technique to another person, it will send you an error. The preferable in this situation is a ManyToMany. Otherwise you can always create an associative class to retrieve the identifiers of the two classes.
I'm new in this framework actually this is my first time to use framework
Can someone help me on this error I don't know how to fix this error? here is my code:
The problem- Call to a member function validatePassword() on null
the actual problem I am getting here
public function validatePassword($attribute, $params)
{
//in loginForm.php file model
loginForm.php model
namespace app\models;
use Yii;
use yii\base\Model;
/**
* LoginForm is the model behind the login form.
*
* #property User|null $user This property is read-only.
*
*/
class LoginForm extends Model
{
public $username;
public $password;
public $rememberMe = true;
private $_user = false;
/**
* #return array the validation rules.
*/
public function rules()
{
return [
// username and password are both required
[['username', 'password'], 'required'],
// rememberMe must be a boolean value
['rememberMe', 'boolean'],
// password is validated by validatePassword()
['password', 'validatePassword'],
];
}
/**
* Validates the password.
* This method serves as the inline validation for password.
*
* #param string $attribute the attribute currently being validated
* #param array $params the additional name-value pairs given in the rule
*/
public function validatePassword($attribute, $params)
{
if (!$this->hasErrors()) {
$user = $this->getUser();
echo"<pre>";
if (!$user || !$user->validatePassword($this->password)) {
$this->addError($attribute, 'Incorrect username or password.');
}
}
}
/**
* Logs in a user using the provided username and password.
* #return bool whether the user is logged in successfully
*/
public function login()
{
if ($this->validate()) {
return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0);
}
return false;
}
/**
* Finds user by [[username]]
*
* #return User|null
*/
public function getUser()
{
if ($this->_user === false) {
$this->_user = User::findByUsername($this->username);
}
return $this->_user;
}
}
I am unable to find out what the exact cause of error.
this is user.php model file
<?php
namespace app\models;
class User extends \yii\base\Object implements \yii\web\IdentityInterface
{
public $id;
public $username;
public $password;
public $admin_email;
public $authKey;
public $accessToken;
public static function tableName()
{
return 'admin_info';
}
/**
* #inheritdoc
*/
public function rules()
{
return [
#[['username', 'admin_email', 'password'], 'required'],
[['username','password'],'required'],
[['username', 'password'], 'string', 'max' => 20],
[['admin_email'], 'string', 'max' => 50],
['admin_email','email'],
['password','password'],
];
}
/**
* #inheritdoc
*/
public function attributeLabels()
{
return [
'admin_id' => 'Admin ID',
'username' => 'Username',
'admin_email' => 'Admin Email',
'password' => 'Password',
];
}
/**
* #inheritdoc
*/
public static function findIdentity($id)
{
return isset(self::$users[$id]) ? new static(self::$users[$id]) : null;
}
/**
* #inheritdoc
*/
public static function findIdentityByAccessToken($token, $type = null)
{
foreach (self::$users as $user) {
if ($user['accessToken'] === $token) {
return new static($user);
}
}
return null;
}
/**
* Finds user by username
*
* #param string $username
* #return static|null
*/
public static function findByUsername($username)
{
foreach (self::$users as $user) {
if (strcasecmp($user['username'], $username) === 0) {
return new static($user);
}
}
return null;
}
/**
* #inheritdoc
*/
public function getId()
{
return $this->id;
}
/**
* #inheritdoc
*/
public function getAuthKey()
{
return $this->authKey;
}
/**
* #inheritdoc
*/
public function validateAuthKey($authKey)
{
return $this->authKey === $authKey;
}
/**
* Validates password
*
* #param string $password password to validate
* #return bool if password provided is valid for current user
*/
public function validatePassword($password)
{
return $this->password === $password;
}
public static function findByUsername($username){
return static::findOne(['username' => $username]);
}
}
Yii2 has an excellent debugger, but you have to be in the dev environment to use it so from a command line, so cd to the root of your project run php init-dev.
Whether in your controller or for example in loginForm.php temporarily place
Yii::info('password is:' . $this->passwrod); in the validatePassword() function to ensure you are getting the expected output.
Now attempt your login and review the log for your critical info:
If nothing there, maybe add more info logging at previous touchpoints.
Remember to not use debug in your production environment and to remove your debugging code.
I'm new to Yii and I have problem with its User and login system .
I should use 3 tables for login check but when I use custom query I face
"Argument 1 passed to yii\web\User::login() must implement interface yii\web\IdentityInterface, ActiveQuery given"
my tables are like :
user : user_id, name, family, birthday, ...
email : email_user_fk, email_addr, email_active, email_cdt
passwd : passwd_user_fk, passwd_hashed, passwd_active, passwd_cdt
and my query is like :
SELECT
user.user_id, email.email_addr, email.email_active,
passwd.passwd_hashed, passwd_passwd_active , ...
FROM user
JOIN email ON user.user_id = email.email_user_fk
JOIN passwd ON user.user_id = passwd.passwd_user_fk
WHERE
email.email_addr = :email
Is there any Idea ,Please ??
class User extends ActiveRecord implements IdentityInterface
{
const STATUS_DELETED = 0;
const STATUS_ACTIVE = 10;
/**
* #inheritdoc
*/
public static function tableName()
{
return 'user';
}
public static function primaryKey(){
return 'user_id';
}
/**
* #inheritdoc
*/
public function behaviors()
{
return [
TimestampBehavior::className(),
];
}
/**
* #inheritdoc
*/
public static function find
Identity($id)
{
return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]);
}
/**
* #inheritdoc
*/
public static function findIdentityByAccessToken($token, $type = null)
{
throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.');
}
/**
* Finds user by username
*
* #param string $username
* #return static|null
*/
public static function findByUsername($username)
{
return static::findOne(['email' => $username]);
}
public static function findByEmail($email)
{
return User::find()
->joinWith(['emails'])
->where("email.email_address = 'me#mail.com' ")
->one();
}
public static function findByMobile($email)
{
return User::find()
->joinWith(['mobiles'])
->where("mobile.mobile_address = '0931515124' ")
->one();
}
/**
* Finds user by password reset token
*
* #param string $token password reset token
* #return static|null
*/
public static function findByPasswordResetToken($token)
{
if (!static::isPasswordResetTokenValid($token)) {
return null;
}
return static::findOne([
'password_reset_token' => $token,
'status' => self::STATUS_ACTIVE,
]);
}
/**
* Finds out if password reset token is valid
*
* #param string $token password reset token
* #return boolean
*/
public static function isPasswordResetTokenValid($token)
{
if (empty($token)) {
return false;
}
$expire = Yii::$app->params['user.passwordResetTokenExpire'];
$parts = explode('_', $token);
$timestamp = (int) end($parts);
return $timestamp + $expire >= time();
}
/**
* #inheritdoc
*/
public function getId()
{
return $this->getPrimaryKey();
}
/**
* #inheritdoc
*/
public function getAuthKey()
{
return $this->auth_key;
}
/**
* #inheritdoc
*/
public function validateAuthKey($authKey)
{
return $this->getAuthKey() === $authKey;
}
/**
* Validates password
*
* #param string $password password to validate
* #return boolean if password provided is valid for current user
*/
public function validatePassword($password)
{
return Yii::$app->security->validatePassword($password, $this->password_hash);
}
/**
* Generates password hash from password and sets it to the model
*
* #param string $password
*/
public function setPassword($password)
{
$this->password_hash = Yii::$app->security->generatePasswordHash($password);
}
/**
* Generates "remember me" authentication key
*/
public function generateAuthKey()
{
$this->auth_key = Yii::$app->security->generateRandomString();
}
/**
* Generates new password reset token
*/
public function generatePasswordResetToken()
{
$this->password_reset_token = Yii::$app->security->generateRandomString() . '_' . time();
}
/**
* Removes password reset token
*/
public function removePasswordResetToken()
{
$this->password_reset_token = null;
}
public function rules()
{
return [
[['user_name', 'user_family', 'user_birthday'], 'required'],
[['user_gender', 'city_id_fk', 'user_status'], 'integer'],
[['user_birthday', 'user_cdt'], 'safe'],
[['user_name'], 'string', 'max' => 32],
[['user_family'], 'string', 'max' => 48],
[['user_tel', 'user_postcode'], 'string', 'max' => 12],
[['user_address'], 'string', 'max' => 128],
[['user_profile_image', 'user_cover_image'], 'string', 'max' => 256]
];
}
/**
* #return \yii\db\ActiveQuery
*/
public function getEmails()
{
return $this->hasMany(Email::className(), ['email_user_id_fk' => 'user_id']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getMobiles()
{
return $this->hasMany(Mobile::className(), ['mobile_user_id_fk' => 'user_id']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getPasswds()
{
return $this->hasMany(Passwd::className(), ['passwd_user_id_fk' => 'user_id']);
}
}
What this error is showing is that when you execute Yii::$app->user->login() at some point you must pass a User object that implements identity interface as a parameter (and it seems you are passing another type of object).
What this method does is allow you to save the information from the user after logging. First the user provides a username, then it has to retrieve info for this username from the database and instatiate a user object with this info. This is the object that you have to pass to the Yii::$app->user->login() function.
Read about the User class here.
and find a good sample here.
Right i am working on yii 2.0 trying to amend the login system. It works when the users are just a hand coded array. Instead i want to make this work from a database.
I will show you what the initial Model looks like:
<?php
namespace app\models;
class User extends \yii\base\Object implements \yii\web\IdentityInterface
{
public $id;
public $username;
public $password;
public $authKey;
public $accessToken;
private static $users = [
'100' => [
'id' => '100',
'username' => 'admin',
'password' => 'admin',
'authKey' => 'test100key',
'accessToken' => '100-token',
],
'101' => [
'id' => '101',
'username' => 'demo',
'password' => 'demo',
'authKey' => 'test101key',
'accessToken' => '101-token',
],
];
/**
* #inheritdoc
*/
public static function findIdentity($id)
{
return isset(self::$users[$id]) ? new static(self::$users[$id]) : null;
}
/**
* #inheritdoc
*/
public static function findIdentityByAccessToken($token, $type = null)
{
foreach (self::$users as $user) {
if ($user['accessToken'] === $token) {
return new static($user);
}
}
return null;
}
/**
* Finds user by username
*
* #param string $username
* #return static|null
*/
public static function findByUsername($username)
{
foreach (self::$users as $user) {
if (strcasecmp($user['username'], $username) === 0) {
return new static($user);
}
}
return null;
}
/**
* #inheritdoc
*/
public function getId()
{
return $this->id;
}
/**
* #inheritdoc
*/
public function getAuthKey()
{
return $this->authKey;
}
/**
* #inheritdoc
*/
public function validateAuthKey($authKey)
{
return $this->authKey === $authKey;
}
/**
* Validates password
*
* #param string $password password to validate
* #return boolean if password provided is valid for current user
*/
public function validatePassword($password)
{
return $this->password === $password;
}
}
okay so as you can see it is working on the hand coded array called $users. So i have made a table called "Users" and made the columns id username password authkey and accessToken.
Hoping that it would do the same thing with a database table, however i am getting an error when i try to log in. This is my new code
<?php
namespace app\models;
/**
* This is the model class for table "Cases".
*
* #property integer $id
* #property string $username
* #property string $password
* #property string $authkey
* #property string $accessToken
*/
class User extends \yii\db\ActiveRecord
{
/**
* #inheritdoc
*/
public static function tableName()
{
return 'users';
}
public function rules()
{
return [
[['id','username','password','authkey','accessToken'], 'required'],
[['id'], 'integer'],
];
}
/**
* #inheritdoc
*/
public function attributeLabels()
{
return [
'id' => 'id',
'username' => 'username',
'password' => 'password',
'authkey' => 'authkey',
'accessToken' => 'accessToken',
];
}
/**
* #inheritdoc
*/
public static function findIdentity($id)
{
return isset(self::$users[$id]) ? new static(self::$users[$id]) : null;
}
/**
* #inheritdoc
*/
public static function findIdentityByAccessToken($token, $type = null)
{
foreach (self::$users as $user) {
if ($user['accessToken'] === $token) {
return new static($user);
}
}
return null;
}
/**
* Finds user by username
*
* #param string $username
* #return static|null
*/
public static function findByUsername($username)
{
foreach (self::$Users as $user) {
if (strcasecmp($user['username'], $username) === 0) {
return new static($user);
}
}
return null;
}
/**
* #inheritdoc
*/
public function getId()
{
return $this->id;
}
/**
* #inheritdoc
*/
public function getAuthKey()
{
return $this->authKey;
}
/**
* #inheritdoc
*/
public function validateAuthKey($authKey)
{
return $this->authKey === $authKey;
}
/**
* Validates password
*
* #param string $password password to validate
* #return boolean if password provided is valid for current user
*/
public function validatePassword($password)
{
return $this->password === $password;
}
}
The error message i am getting when i try to log in is "Access to undeclared static property: app\models\User::$Users".
If you need to see my LoginForm Model and Controller action i will post them underneath here.
public function actionLogin()
{
if (!\Yii::$app->user->isGuest) {
return $this->goHome();
}
$model = new LoginForm();
if ($model->load(Yii::$app->request->post()) && $model->login()) {
return $this->goBack();
} else {
return $this->render('login', [
'model' => $model,
]);
}
}
and LoginForm model is:
<?php
namespace app\models;
use Yii;
use yii\base\Model;
/**
* LoginForm is the model behind the login form.
*/
class LoginForm extends Model
{
public $username;
public $password;
public $rememberMe = true;
private $_user = false;
/**
* #return array the validation rules.
*/
public function rules()
{
return [
// username and password are both required
[['username', 'password'], 'required'],
// rememberMe must be a boolean value
['rememberMe', 'boolean'],
// password is validated by validatePassword()
['password', 'validatePassword'],
];
}
/**
* Validates the password.
* This method serves as the inline validation for password.
*
* #param string $attribute the attribute currently being validated
* #param array $params the additional name-value pairs given in the rule
*/
public function validatePassword($attribute, $params)
{
if (!$this->hasErrors()) {
$user = $this->getUser();
if (!$user || !$user->validatePassword($this->password)) {
$this->addError($attribute, 'Incorrect username or password.');
}
}
}
/**
* Logs in a user using the provided username and password.
* #return boolean whether the user is logged in successfully
*/
public function login()
{
if ($this->validate()) {
return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0);
} else {
return false;
}
}
/**
* Finds user by [[username]]
*
* #return User|null
*/
public function getUser()
{
if ($this->_user === false) {
$this->_user = User::findByUsername($this->username);
}
return $this->_user;
}
}
Can any one advise what i should do , once again the error i get is:
Access to undeclared static property: app\models\User::$Users
and this error corresponds to this section of code
foreach (self::$Users as $user) {
if (strcasecmp($user['username'], $username) === 0) {
return new static($user);
}
}
In your new User class change this methods:
public static function findIdentity($id)
{
return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]);
}
/**
* #inheritdoc
*/
public static function findIdentityByAccessToken($token, $type = null)
{
throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.');
}
/**
* Finds user by username
*
* #param string $username
* #return static|null
*/
public static function findByUsername($username)
{
return static::findOne(['username' => $username, 'status' => self::STATUS_ACTIVE]);
}
See more in Yii2 Advanced template - https://github.com/yiisoft/yii2-app-advanced/blob/master/common/models/User.php#L61
Or take this class(from Yii2 Advanced template) in your app.
Use one Object for User Model instead your three different objects.
class User extends ActiveRecord implements IdentityInterface
{
const LOGIN_SCENARIO = 'login';
const CREATE_SCENARIO = 'create';
const UPDATE_SCENARIO = 'update';
/**
* Table name
*
* #return string
*/
public static function tableName()
{
return 'user';
}
/**
* Primary key
*/
public static function primaryKey()
{
return ['id'];
}
/**
* Set attribute labels
*
* #return array
*/
public function attributeLabels()
{
return [
'login' => Yii::t('app', 'Login'),
'password' => Yii::t('app', 'Password')
];
}
/**
* Rules
*
* #return array
*/
public function rules()
{
return [
[
[
'login',
'password'
],
'required',
'on' => self::LOGIN_SCENARIO
],
[
'password',
'validatePassword',
'on' => self::LOGIN_SCENARIO
],
[
[
'role',
'login',
'confirm',
'password',
],
'required',
'on' => self::CREATE_SCENARIO
],
[
[
'role',
'login',
],
'required',
'on' => self::UPDATE_SCENARIO
],
[
[
'name',
'status',
'password',
'create_dt',
'update_dt'
],
'safe',
],
];
}
/**
* Password validation
*/
public function validatePassword($attribute)
{
// Validate pass
}
/**
* #param null $id
*
* #return bool|mixed
*/
public function saveUser($id = null)
{
/** #var self $user */
$user = $this->findIdentity($id) ? $this->findIdentity($id) : $this;
$user->setScenario($this->scenario);
// Make Save
}
/**
* #param $id
*
* #throws \Exception
*/
public function deleteUser($id)
{
/** #var self $user */
if($user = $this->findIdentity($id)) {
// Make Delete
}
}
/**
* Finds an identity by the given ID.
*
* #param string|integer $id the ID to be looked for
*
* #return IdentityInterface the identity object that matches the given ID.
* Null should be returned if such an identity cannot be found
* or the identity is not in an active state (disabled, deleted, etc.)
*/
public static function findIdentity($id)
{
return static::findOne($id);
}
/**
* Returns an ID that can uniquely identify a user identity.
* #return string|integer an ID that uniquely identifies a user identity.
*/
public function getId()
{
return $this->getPrimaryKey();
}
}
And one User Model working with users, with different scenarios, can create, delete, make login etc.
And at html, for login form, put code like this:
<?php $model = new namespace\User() ?>
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, 'login') ?>
<?= $form->field($model, 'password')->passwordInput() ?>
And for login, in controller, put code like this:
/**
* Login action
*
* #return mixed
*/
public function actionLogin()
{
$user = new namespace\User();
$user->setScenario(namespace\User::LOGIN_SCENARIO);
$user->load(Yii::$app->request->post());
if($user->validate()) {
// Make Login
return true;
}
return false;
}