I could really use a second pair of eyes. I am attempting to create a custom user provider using Silex's SecurityServiceProvider in conjunction with Doctrine MongoDB ODM, and I keep receiving the following error when attempting to log in:
RuntimeException in ContextListener.php line 177:
There is no user provider for user "FooBar\Document\User".
Note: If I hard-code the users, it works perfectly
'users' => [
'admin' => [
'ROLE_ADMIN', '$2y$12$W6FYYG1YTdMUNSosk14jluYsCwTe5wL7qwi3bnRMsqQEShb.89OiG'
],
]
As far as I can tell, I have followed the Silex documentation, which can be found here. I verified that it is successfully fetching the user from the db, so I know that is not the issue. Below is the pertinent code, including snippets from app.php, the user class, and my custom user provider class.
$app->register(new Neutron\Silex\Provider\MongoDBODMServiceProvider(), $dbConfig);
// Create document manager alias
$app['dm'] = $app['doctrine.odm.mongodb.dm'];
$app->register(new Silex\Provider\SecurityServiceProvider(), [
'security.firewalls' => [
'login' => [
'pattern' => '^/login$',
'anonymous' => true
],
'secured' => [
'pattern' => '^.*$',
'form' => [
'login_path' => '/login',
'check_path' => '/login/authenticate',
],
'logout' => [
'logout_path' => '/logout',
'target' => '/'
],
'users' => $app->share(function() use ($app) {
return new \FooBar\Repository\UserRepository($app['dm']);
})
],
'unsecured' => [
'anonymous' => true
]
],
'security.role_hierarchy' => [
'ROLE_ADMIN' => [
'ROLE_USER',
],
'ROLE_SUPER_ADMIN' => [
'ROLE_ADMIN',
'ROLE_ALLOWED_TO_SWITCH'
]
],
'security.access_rules' => [
['^/login', 'IS_AUTHENTICATED_ANONYMOUSLY'],
['^.*', 'ROLE_USER']
]
]);
$app['security.encoder.digest'] = $app->share(function() {
return new Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder(12);
});
namespace FooBar\Repository;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
class UserRepository implements UserProviderInterface
{
public function loadUserByUsername($username)
{
try {
$user = $this->dm->createQueryBuilder('\FooBar\Document\User')
->field('username')->equals($username)
->getQuery()
->getSingleResult();
} catch (NoResultException $e) {
throw new UsernameNotFoundException(sprintf(
'Unable to find an active User object identified by "%s".',
$username
), 0, $e);
}
return $user;
}
public function refreshUser(UserInterface $user)
{
$class = get_class($user);
if (!$this->supportsClass($class)) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $class));
}
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
{
return $class === '\FooBar\Document\User' || is_subclass_of($class, '\FooBar\Document\User');
}
}
<?php
namespace FooBar\Document;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
/**
* #ODM\Document(repositoryClass="FooBar\Repository\UserRepository")
*/
class User implements AdvancedUserInterface
{
/**
* Document Id
* #ODM\Id
*/
protected $id;
/**
* User first name
* #ODM\String
*/
protected $firstName;
/**
* User last name
* #ODM\String
*/
protected $lastName;
/**
* User email
* #ODM\String
* #ODM\UniqueIndex
*/
protected $email;
/**
* User username
* #ODM\String
* #ODM\UniqueIndex
*/
protected $username;
/**
* User password
* #ODM\String
*/
protected $password;
/**
* Whether or not user account is expired
* #ODM\Boolean
*/
protected $isAccountExpired;
/**
* Whether or not user account is locked
* #ODM\Boolean
*/
protected $isAccountLocked;
/**
* Whether or not user credentials are expired
* #ODM\Boolean
*/
protected $isCredentialsExpired;
/**
* Whether or not user is active
* #ODM\Boolean
*/
protected $isActive;
/**
* Whether or not user credentials are erased
* #ODM\Boolean
*/
protected $isCredentialsErased;
/**
* User roles
* #ODM\Collection
*/
protected $roles;
/
public function __construct()
{
parent::__construct();
$this->isActive = true;
$this->isAccountLocked = false;
$this->isAccountExpired = false;
$this->isCredentialsExpired = false;
$this->isCredentialsErased = false;
$this->roles = new ArrayCollection;
// Temporary
$this->roles = ['ROLE_USER'];
}
public function setFirstName($firstName)
{
$this->firstName = $firstName;
}
public function setLastName($lastName)
{
$this->lastName = $lastName;
}
public function setEmail($email)
{
$this->email = ($email);
}
public function setUsername($username)
{
$this->username = $username;
}
public function setPassword($password)
{
$this->password = $password;
}
public function getFirstName()
{
return $this->firstName;
}
public function getLastName()
{
return $this->lastName;
}
public function getEmail()
{
return $this->email;
}
public function getUsername()
{
return $this->username;
}
public function getPassword()
{
return $this->password;
}
public function getSalt()
{
return null;
}
public function getRoles()
{
return $this->roles;
}
public function isAccountNonExpired()
{
return !$this->isAccountExpired;
}
public function isAccountNonLocked()
{
return !$this->isAccountLocked;
}
public function isCredentialsNonExpired()
{
return !$this->isCredentialsExpired;
}
public function isCredentialsErased()
{
return $this->isCredentialsErased;
}
public function isEnabled()
{
return $this->isActive;
}
public function eraseCredentials()
{
$this->username = null;
$this->password = null;
$this->isActive = false;
$this->isAccountLocked = true;
$this->isAccountExpired = true;
$this->isCredentialsExpired = true;
$this->isCredentialsErased = true;
return $this;
}
public function serialize()
{
return serialize([
$this->id,
$this->firstName,
$this->lastName,
$this->email,
$this->username,
]);
}
public function unserialize($serialized)
{
list(
$this->id,
$this->firstName,
$this->lastName,
$this->email,
$this->username,
) = unserialize($serialized);
}
}
get_class($user);
will return
FooBar\Document\User
instead of
\FooBar\Document\User
So, replace
$class === '\FooBar\Document\User'
by
$class === 'FooBar\Document\User'
Related
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.
I have a problem with logging in Yii2.
I created a database with table named users and corresponding model Users using Gii.
I also changed config/web.php file:
'user' => [
'identityClass' => 'app\models\Users',
'enableAutoLogin' => true,
],
Here is my app\models\Users:
namespace app\models;
/**
* This is the model class for table "users".
*
* #property integer $id
* #property string $username
* #property string $email
* #property string $password
*/
class Users extends \yii\db\ActiveRecord implements \yii\web\IdentityInterface {
/**
* #inheritdoc
*/
public static function tableName() {
return 'users';
}
/**
* #inheritdoc
*/
public function rules() {
return [
[['username', 'email', 'password'], 'required'],
[['username', 'email'], 'string', 'max' => 200],
[['password'], 'string', 'max' => 300],
];
}
/**
* #inheritdoc
*/
public function attributeLabels() {
return [
'id' => 'ID',
'username' => 'Username',
'email' => 'Email',
'password' => 'Password',
];
}
public static function findByUsername($username) {
return self::findOne(['username' => $username]);
}
public function validatePassword($password) {
return $this->password === $password;
}
public static function findIdentity($id) {
$user = Users::findOne($id);
if (count($user)) {
return new static($user);
}
return null;
}
/**
* #inheritdoc
*/
public static function findIdentityByAccessToken($token, $type = null) {
throw new \yii\base\NotSupportedException();
}
public function getAuthKey() {
return $this->authKey;
}
public function validateAuthKey($authKey) {
return $this->authKey === $authKey;
}
public function getId() {
return $this->username;
}
}
And here is my SiteController code in actionLogin():
public function actionLogin()
{
if (!Yii::$app->user->isGuest) {
return $this->goHome();
}
$model = new LoginForm();
if ($model->load(Yii::$app->request->post()) && $model->login()) {
//var_dump(Yii::$app->user->identity);
return $this->goBack();
//return $this->render('login',Yii::$app->user->identity);
}
return $this->render('login', [
'model' => $model,
]);
}
This is when redirect to home not show username and logout but when I use:
var_dump(Yii::$app->user->identity);
It shows all variable set true.
and here is loginform
<?php
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();
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 : 20);
}
return false;
}
/**
* Finds user by [[username]]
*
* #return User|null
*/
public function getUser()
{
if ($this->_user === false) {
$this->_user = Users::findByUsername($this->username);
}
return $this->_user;
}
}
I created same program before and works fine. Where is the problem?
Any way to solve this?
Your findIdentity() method looks suspicious. You're using username as identity ID, but Users::findOne($id) will search by id column in users table. Try change it to:
public static function findIdentity($id) {
return static::findByUsername($id);
}
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.
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;
}
I am trying to implement login function with database using Yii 2.0. I have tried for 3 days but it still not working. What did i do wrong?
Here is my user and loginform class
loginform.php
<?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;
}
}
User.php
<?php
namespace app\models;
use Yii;
use yii\base\NotSupportedException;
use yii\helpers\Security;
use yii\web\IdentityInterface;
class User extends \yii\db\ActiveRecord implements \yii\web\IdentityInterface
{
public static function tableName()
{
return 'usys.usex';
}
public function rules()
{
return [
[['passwd', 'user_name'], 'required'],
[['user_type_uid', 'user_status_uid', 'msisdn', 'passwd_strength', 'banned', 'created'], 'integer']
];
}
public function attributeLabels()
{
return [
'usex_uid' => 'User id',
'username' => 'Username',
'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) {
$user = self::find()
->where(["user_uid" => $username])
->one();
if (!count($user)) {
return null;
}
return new static($user);
}
/**
* #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;
}
}
I try to trace validation function to validateAnswer but when i tried to print $this->password it doesn't have any value.
Anyone has a clue?