Related
I have a small problem where I couldn't find the answer on internet and I'm not quite sure how php and interfaces work.
So the problem is I have a if(!variable instanceof class). But here, the checked class is an interface and it is suppose to be in a array as you can see in the following code
abstract class Action
{
final public function call(Bone $bone)
{
$sites = $this->getSites($bone);
foreach ($sites as $site) {
if (!$site instanceof Site) {
throw new \Exception("Invalid entry");
}
}
}
}
class BonesSites
{
public function getSites(string $site): array
{
if ($site === 'Egypt') {
return [
[
'siteId' => 1,
'name' => 'Cairo',
'bone' => 'T-Rex bones',
],
[
'siteId' => 2,
'name' => 'Giza',
'bone' => 'Raptors bones',
],
[
'siteId' => 3,
'name' => 'Alexandria',
'bone' => 'Bronchiosaurus bones',
],
];
}
return ['error' => 'Site not found!'];
}
}
interface Bone
{
public function getName(): string;
}
interface Site
{
}
Any idea how to return an interface within an array?
You would need to create an additional class, named Site, and return an array of objects.
class Site
{
private int $siteId;
private string $name;
private string $bone;
/**
* #param int $siteId
* #param string $name
* #param string $bone
*/
public function __construct(int $siteId, string $name, string $bone)
{
$this->siteId = $siteId;
$this->name = $name;
$this->bone = $bone;
}
/**
* #return int
*/
public function getSiteId(): int
{
return $this->siteId;
}
/**
* #param int $siteId
*/
public function setSiteId(int $siteId): void
{
$this->siteId = $siteId;
}
/**
* #return string
*/
public function getName(): string
{
return $this->name;
}
/**
* #param string $name
*/
public function setName(string $name): void
{
$this->name = $name;
}
/**
* #return string
*/
public function getBone(): string
{
return $this->bone;
}
/**
* #param string $bone
*/
public function setBone(string $bone): void
{
$this->bone = $bone;
}
}
And to return the array of sites:
return
[
(new Site(1, 'Cairo', 'T-Rex bones')),
(new Site(2, 'Giza', 'Raptors bones')),
(new Site(3, 'Alexandria', 'Bronchiosaurus bones'))
];
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'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;
}
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'