CakePHP Auth Component Using 2 Tables - authentication

CakePHP Version 1.2.5
I would like a single user to have multiple email addresses.
I would like a single user to have a single password.
I would like users to log in using any of their multiple email addresses and their single password.
I have created a users table with an id and a password field.
I have created a user_email_addresses table with an id field a user_id field and an email_address field.
Question:
How do I modify the auth component minimally to look for the "username" in this case, "email_address", in the user_email_addresses table and the "password" in the users table?
Seems as though modifying the identify method in the auth component might do it. But I think modifying the auth component directly is a bad idea - any ideas on how to extend and still possibly modify the identify method? http://cakebaker.42dh.com/2009/09/08/extending-cakephps-core-components/ or possibly nominate a different authenticate object?
Starting line 774:
function identify($user = null, $conditions = null) {
if ($conditions === false) {
$conditions = null;
} elseif (is_array($conditions)) {
$conditions = array_merge((array)$this->userScope, $conditions);
} else {
$conditions = $this->userScope;
}
if (empty($user)) {
$user = $this->user();
if (empty($user)) {
return null;
}
} elseif (is_object($user) && is_a($user, 'Model')) {
if (!$user->exists()) {
return null;
}
$user = $user->read();
$user = $user[$this->userModel];
} elseif (is_array($user) && isset($user[$this->userModel])) {
$user = $user[$this->userModel];
}
if (is_array($user) && (isset($user[$this->fields['username']]) || isset($user[$this->userModel . '.' . $this->fields['username']]))) {
if (isset($user[$this->fields['username']]) && !empty($user[$this->fields['username']]) && !empty($user[$this->fields['password']])) {
if (trim($user[$this->fields['username']]) == '=' || trim($user[$this->fields['password']]) == '=') {
return false;
}
$find = array(
$this->userModel.'.'.$this->fields['username'] => $user[$this->fields['username']],
$this->userModel.'.'.$this->fields['password'] => $user[$this->fields['password']]
);
} elseif (isset($user[$this->userModel . '.' . $this->fields['username']]) && !empty($user[$this->userModel . '.' . $this->fields['username']])) {
if (trim($user[$this->userModel . '.' . $this->fields['username']]) == '=' || trim($user[$this->userModel . '.' . $this->fields['password']]) == '=') {
return false;
}
$find = array(
$this->userModel.'.'.$this->fields['username'] => $user[$this->userModel . '.' . $this->fields['username']],
$this->userModel.'.'.$this->fields['password'] => $user[$this->userModel . '.' . $this->fields['password']]
);
} else {
return false;
}
$model =& $this->getModel();
$data = $model->find(array_merge($find, $conditions), null, null, 0);
if (empty($data) || empty($data[$this->userModel])) {
return null;
}
} elseif (!empty($user) && is_string($user)) {
$model =& $this->getModel();
$data = $model->find(array_merge(array($model->escapeField() => $user), $conditions));
if (empty($data) || empty($data[$this->userModel])) {
return null;
}
}
if (!empty($data)) {
if (!empty($data[$this->userModel][$this->fields['password']])) {
unset($data[$this->userModel][$this->fields['password']]);
}
return $data[$this->userModel];
}
return null;
}

AuthComponent::identify() takes two parameters, $user and $conditions
if ($conditions === false) {
$conditions = null;
} elseif (is_array($conditions)) {
$conditions = array_merge((array)$this->userScope, $conditions);
} else {
$conditions = $this->userScope;
}
Looking at the above snippet, if you pass false as the $conditions, the method will execute with no model conditions.
Also, looking at the rest of the code, if you pass a $user value of type string, it won't execute most of the user-related code until it gets here:
} elseif (!empty($user) && is_string($user)) {
$model =& $this->getModel();
$data = $model->find(array_merge(array($model->escapeField() => $user), $conditions));
if (empty($data) || empty($data[$this->userModel])) {
return null;
}
}
Here it runs Model::escapeField(), with no parameters, which returns an escaped version of User.id (by default) and maps this field to the string that was passed in. It then merges this with the $conditions array and performs a Model::find().
It should be safe to say that if the string is the user's ID and there are no conditions it will find the person with that ID every time.
As such, you should be able to extend AuthComponent to do what you want like so:
// app/controllers/components/app_auth.php
<?php
App::import('Component', 'Auth');
class AppAuthComponent extends AuthComponent {
/**
* Custom user identification
*/
function identify($user=null, $conditions=null) {
// get the model AuthComponent is configured to use
$model =& $this->getModel(); // default is User
// do a query that will find a User record when given successful login data
$user = $model->find('first', array('conditions' => array(
'EmailAddress.' . $this->fields['username'] => $user[$this->userModel][$this->fields['username']],
'User.' . $this->fields['password'] => $user[$this->userModel][$this->fields['password']],
));
// return null if user invalid
if (!$user) {
return null; // this is what AuthComponent::identify would return on failure
}
// call original AuthComponent::identify with string for $user and false for $conditions
return parent::identify($user[$this->userModel][$model->primaryKey], false);
}
}
?>
You will have to replace all references to Auth with AppAuth in your application unless you follow this handy tip (the approach in the comments is nice).

Related

Implement Phalcon 4 Database Existence validator (similar to Uniqueness)

Often I need to validate if given value is existing in certain column (attribute) of a table (model).
This can be useful in foreign keys of a model, to check if the given values exists.
Most probably the validation logic can be mostly the same as for Uniqueness, except the comparison here can be something like > 0.
A possible usage scenario could be like below:
$validator->add(
'organization_id',
new ExistenceOnDbValidator(
[
'model' => Organization::class,
'expr'=> ' id = %s ',
'excludeNullValue'=> true,
'message' => 'Organization does not exist.',
]
)
);
Finally I implemented myself a validator called ExistenceOnDbValidator and it works fine.
Usage
$validator = new Validation();
$validator->add(
'organization_id',
new ExistenceOnDbValidator(
[
'model' => Organization::class,
'expr' => ' id = %s ',
'ignoreNullValue' => false,
'message' => 'Selected organization does not exist.',
]
)
);
Implenentation
use Phalcon\Messages\Message;
use Phalcon\Validation;
use Phalcon\Validation\AbstractValidator;
use Phalcon\Validation\ValidatorInterface;
class ExistenceOnDb extends AbstractValidator implements ValidatorInterface
{
public function validate(Validation $validator, $attribute): bool
{
$expr = $this->getOption('expr');
$model = $this->getOption('model');
$value = $validator->getValue($attribute);
$ignoreNullValue = true;
if ($this->hasOption('ignoreNullValue')) {
$ignoreNullValue = $this->getOption('ignoreNullValue');
}
if ((is_null($value) || empty($value)) && $ignoreNullValue == true) {
return true;
}
$expr = sprintf(
$expr,
$value,
);
$result = $model::findFirst($expr);
if ((is_null($result) || empty($result))) {
$message = $this->getOption('message');
$validator->appendMessage(new Message($message));
return false;
}
return true;
}
}

How to use KnpPaginatorBundle to paginate results for a search form?

I'm working on a Symfony 2 project and I used KnpPaginatorBundle the first page works correctly but the second one shows me this Error : " The controller must return a response (null given). Did you forget to add a return statement somewhere in your controller? " i didn't understand this please help me
this is my controller:
public function searchAction(Request $request) {
$search_form = $this->createForm(new SearchInterventionBatimentType());
if ($request->isMethod('post')) {
$search_form->handleRequest($request);
if ($search_form->isValid()) {
$data = $search_form->getData();
$from = $data['from'];
$to = $data['to'];
$intervenant= $data['intervenant'];
$type= $data['type'];
$batiment = $data['batiment'];
$em = $this->getDoctrine()->getManager();
$intervention = new InterventionBatiment();
if(is_null($intervenant) && is_null($type) && is_null($batiment)) {
$intervention = $em->getRepository('SecteurBundle:InterventionBatiment')->findByDate($from,$to);
} elseif(is_null($type) && is_null($batiment)) {
$intervention = $em->getRepository('SecteurBundle:InterventionBatiment')->findByDateAndIntervenant($from,$to,$intervenant);
} elseif (is_null($intervenant) && is_null($batiment)) {
$intervention = $em->getRepository('SecteurBundle:InterventionBatiment')->findByDateAndType($from,$to,$type);
} elseif (is_null($intervenant) && is_null($type)) {
$intervention = $em->getRepository('SecteurBundle:InterventionBatiment')->findByDateAndBatiment($from,$to,$batiment);
} elseif (is_null($batiment)) {
$intervention = $em->getRepository('SecteurBundle:InterventionBatiment')->findByDateAndIntervenantAndType($from,$to,$type,$intervenant);
} else {
$intervention = $em->getRepository('SecteurBundle:InterventionBatiment')->findByAll($from,$to,$type,$intervenant,$batiment);
}
$paginator= $this->get('knp_paginator');
$result= $paginator->paginate(
$intervention, /* query NOT result */
$request->query->getInt('page', 1)/*page number*/,
$request->query->getInt('limit', 8)/*limit per page*/
);
return $this->render(
'SecteurBundle:InterventionBatiment:recherche.html.twig',
array('interventions' => $result,'form' => $search_form->createView())
);
}
}
}
the controller should always need to send a response.
In your case you need to send a response if the form is not valid
if ($search_form->isValid()) {
}
return your response here too (if the form is not valid)

Allow user to change password Cakephp 2

I've looked over various approaches, and various questions published on this subject, but with no luck. In my case, the controller code "appears" to work, and the message flashes up "Your changes have been saved", but the password database field is unchanged. Is there something I am missing?
Controller code
public function changepass($id = null) {
$this->layout = 'profile_page';
//$this->request->data['User']['id'] = $this->Session->read('Auth.User.id');
$user = $this->User->find('first', array(
'conditions' => array('User.id' => $this->Auth->user('id'))
)); // 'User.id' => $id
$this->set('user',$user);
if ($this->request->is('post') || $this->request->is('put'))
{
$this->User->saveField('password', AuthComponent::password($this->request->data['User']['newpass']));
// $this->User->saveField('password', $this->data['User']['password']);
// $this->data['User']['password']= $this->request->data['User']['newpass'];
if ($this->User->save($this->request->data))
{
$this->Session->setFlash(__('Your password has been changed!'));
$this->redirect(array('controller'=>'articles','action'=>'index'));
}
else
{
$this->Session->setFlash(__('Whoops! Something went wrong... try again?'));
$this->redirect(array('controller'=>'users','action'=>'changepass'));
}
}
$this->request->data = $this->User->read(null, $id);
unset($this->request->data['User']['password']); // tried commenting out
}
Model
public function beforeSave($options = array()) {
if (isset($this->data[$this->alias]['password'])) {
$this->data[$this->alias]['password'] = AuthComponent::password($this->data[$this->alias]['password']);
if (isset($this->data[$this->alias]['newpass'])) {
$this->data[$this->alias]['password'] = AuthComponent::password($this->data[$this->alias]['newpass']);
}
}
return true;
}
Of course later I'd put in existing password check, and confirm new password check, but i need to get the existing password update basic approach working.
Many thanks in advance for any light you can shed on this,
I think I've sussed this. First, major bloop on my part - in my view I'd put echo $this->Form->create('User', array('action' => 'edit')); -- of course change action to 'changepass'
New Controller code:
public function changepass ($id = null) {
$this->layout = 'profile_page';
$this->User->id = $id;
if (!$this->User->exists()) {
throw new NotFoundException(__('Invalid user'));
}
//debug($this->request->data);
if ($this->request->is('post') || $this->request->is('put')) {
$this->data['User']['password']= $this->request->data['User']['newpass'];
if ($this->User->save($this->request->data)) {
$this->Session->setFlash(__('Your password changes have been saved'));
$this->redirect(array('controller' => 'articles', 'action' => 'index'));
} else {
$this->Session->setFlash(__('The profile could not be saved. Please, try again.'));
}
} else {
if ($this->Auth->user('id')!= $id) {
$this->Session->setFlash('You are not allowed that operation!');
$this->redirect(array('controller' => 'articles', 'action' => 'index'));
}
$this->request->data = $this->User->read(null, $id);
debug($this->request->data);
unset($this->request->data['User']['password']);
}
}
Model - tidied up as per advice from eboletaire
public function beforeSave($options = array()) {
if (isset($this->data[$this->alias]['password'])) {
$this->data[$this->alias]['password'] = AuthComponent::password($this->data[$this->alias]['password']);
}
if (isset($this->data[$this->alias]['newpass'])) {
$this->data[$this->alias]['password'] = AuthComponent::password($this->data[$this->alias]['newpass']);
}
return true;
}
if ($this->request->is('post') || $this->request->is('put')) {
$this->data['User']['password']= $this->request->data['User']['newpass'];
if ($this->User->save($this->request->data)) {
Should be
if ($this->request->is('post') || $this->request->is('put')) {
$this->data['User']['id'] = $id;
$this->data['User']['password']= $this->request->data['User']['newpass'];
if ($this->User->save($this->data)) {
Watch out for $this->data vs $this->request->data.
First of all, you are saving the password twice. Remove/comment this line:
$this->User->saveField('password', AuthComponent::password($this->request->data['User']['newpass']));
Anyway, I think the problem is in your model. Check out your beforeSave method. Why are you setting password first with field password and then with field newpass???
PD. Cleaning up your code I've also seen that maybe the second if should be outside the first one.

Magento API, Return Orders with NULL values

Using the magento api version 1 and soap.
Need to return all orders with 'coupon_code'=> NULL
The call I'm attempting:
$order_listAR = $proxy->call($sessionId, 'sales_order.list', array(array('coupon_code'=>array('null'=>'null'))));
The ouput I want returned is this:
array(237) {
["state"]=>
string(8) "complete"
["status"]=>
string(8) "complete"
["coupon_code"]=> NULL
So far this seems to work properly, but I'm not sure if ('null'=>'null') is the proper way to find NULL values in the array. Can someone explain why this works, and, or if this is the correct syntax? I don't have any margin for error on this.
Yes, the syntax you use is correct to filter against null.
array(
'coupon_code' => array(
'null' => 'this_value_doesnt_matter'
)
)
Magento maps* the API method sales_order.list to Mage_Sales_Model_Order_Api::items().
public function items($filters = null)
{
:
$collection = Mage::getModel("sales/order")->getCollection()
:
if (is_array($filters)) {
try {
foreach ($filters as $field => $value) {
if (isset($this->_attributesMap['order'][$field])) {
$field = $this->_attributesMap['order'][$field];
}
$collection->addFieldToFilter($field, $value);
}
} catch (Mage_Core_Exception $e) {
$this->_fault('filters_invalid', $e->getMessage());
}
}
:
}
The items() method uses a Mage_Sales_Model_Resource_Order_Collection to fetch the orders for the API call. That collection is based on Varien_Data_Collection_Db, so
$collection->addFieldToFilter($field, $value)
from above essentially does call
Varien_Data_Collection_Db::addFieldToFilter()
If you follow the latter, you'll hit Varien_Db_Adapter_Pdo_Mysql::prepareSqlCondition() in the end, params being
$fieldName = 'coupon_code'
$condition = array('null' => 'null')
Excerpt of that method:
public function prepareSqlCondition($fieldName, $condition)
{
$conditionKeyMap = array(
'eq' => "{{fieldName}} = ?",
:
'notnull' => "{{fieldName}} IS NOT NULL",
'null' => "{{fieldName}} IS NULL",
:
'sneq' => null
);
:
$query = '';
if (is_array($condition)) {
:
$key = key(array_intersect_key($condition, $conditionKeyMap));
if (isset($condition['from']) || isset($condition['to'])) {
:
} elseif (array_key_exists($key, $conditionKeyMap)) {
$value = $condition[$key];
if (($key == 'seq') || ($key == 'sneq')) {
:
}
$query = $this->_prepareQuotedSqlCondition($conditionKeyMap[$key], $value, $fieldName);
} else {
:
}
}
:
}
In your case _prepareQuotedSqlCondition() will be called with
$text = '{{fieldName}} IS NULL'
$value = 'null'
$fieldName = 'coupon_code'
which will result in $query = 'coupon_code IS NULL'.
If you take a closer look at the conversion method
protected function _prepareQuotedSqlCondition($text, $value, $fieldName)
{
$sql = $this->quoteInto($text, $value);
$sql = str_replace('{{fieldName}}', $fieldName, $sql);
return $sql;
}
you'll also see, why the value of the 'null' => 'null' key/value pair does not matter at all. That's because $text will be '{{fieldName}} IS NULL', i.e. not containing any binding ?.
Hence, there's nothing to replace for _quoteInto()^^
* see app/code/core/Mage/Sales/etc/api.xml

Yii::app()->user->isGuest always returns true even though login was successful

I started to make some differences between those users which have authenticated and those that not. For this, i am using
Yii::app()->user->id;
However, in a determined view i put the following code:
<?php
if(Yii::app()->user->isGuest) {
print("Welcome back Guest!");
print("Your id is ".Yii::app()->user->id);
} else {
print("Welcome back ".Yii::app()->user->name);
print("Your id is ".Yii::app()->user->id);
}?>
And i always get the "welcome back guest!", whether i have logged in (successfully) or not. And if i have logged in, then it displays the welcome message together with the user's id!
EDIT
#briiC.lv
Hey.. sorry for the late reply, I hope you are still following this! I am not extending the given UserIdentity class. Is this mandatory? Since i still dont get very well the whole authorization issue, i thought it would be best to give a try with the class they provide, and then extend with my own functionality.. Anyway, next i post my UserIdentity class with its small tweaks.. maybe the problem lies here??
<?php class UserIdentity extends CUserIdentity{
private $_id;
public function authenticate()
{
$user = Users::model()->findAll('username=\''.$this->username.'\' AND password=\''.$this->encryptedPassword.'\'');
if(!isset($user[0]))
{
return false;
}
else
{
$this->setState('id', $user[0]->id);
$this->username = $user[0]->username;
$this->errorCode=self::ERROR_NONE;
return true;
}
}
public function getId()
{
return $this->_id;
}
}
Here is the output i got when i started to log as you suggested; i got this output immediately after successfully logging in.
[05:23:21.833][trace][vardump] CWebUser#1 (
[allowAutoLogin] => true
[guestName] => 'Guest'
[loginUrl] => array ( '0' => '/site/login' )
[identityCookie] => null
[authTimeout] => null
[autoRenewCookie] => false
[autoUpdateFlash] => true
[CWebUser:_keyPrefix] => '0f4431ceed8f17883650835e575b504b'
[CWebUser:_access] => array()
[behaviors] => array()
[CApplicationComponent:_initialized] => true
[CComponent:_e] => null
[CComponent:_m] => null
)
Any help is much appreciated!
Maybe you can try to debug harder:
change messages to something like this:
if(Yii::app()->user->isGuest) {
print("Not logged");
} else {
print_r(Yii::app()->user);
print("Welcome ".Yii::app()->user->name);
print("Your id is ".Yii::app()->user->id);
}
And check session variable in your config/main.php file
...
'session' => array(
'autoStart'=>true,
),
...
The error is in the following line
$this->setState('id', $user[0]->id);
As seen in the official yii documentation regarding auth & auth, setState should be used for anything but the id field. In order to implement the key Yii will use to identify your user, return a unique value per user in the Identity getId() function.
In your case, this means you simply have to change the above line into the following:
$this->_id = $user[0]->id;
Regarding the actual inner working of the login procedure, I'd recommend a look at the CWebUser class, and especially at its login function, which is responsible for the actual storage of the Identity getId() return value.
when you call authenticate function login user as
$userIdentity = new UserIdentity($username, $password);
$userIdentity->authenticate();
if ($userIdentity->errorCode===UserIdentity::ERROR_NONE) {
Yii::app()->user->login($userIdentity,0);
}
and fetch id as
echo 'id='.Yii::app()->user->getId();
apply this code and check
I have faced same problem and found that only one line in UserIdentity Component will resolve this issue.
This is your code:
else
{
$this->setState('id', $user[0]->id);
$this->username = $user[0]->username;
$this->errorCode=self::ERROR_NONE;
return true;
}
Update this code by this one
else
{
$this->_id = $user[0]->id;
$this->setState('id', $user[0]->id);
$this->username = $user[0]->username;
$this->errorCode=self::ERROR_NONE;
return true;
}
First of all, you need to know condition that sets guest and logged-in user apart.
Based on Yii master's branch CWebUser::getIsGuest():
public function getIsGuest()
{
return $this->getState('__id')===null;
}
Compared to your code:
$user = Users::model()->findAll('username=\''.$this->username.'\' AND password=\''.$this->encryptedPassword.'\'');
if(!isset($user[0])) {
// false
} else {
$this->setState('id', $user[0]->id); // this is for persistent state sakes
...
}
}
In short: you did supply 'id' to Identity persistent state but Yii CWebUser expecting '__id' based on UserIdentity::getId().
Solution is pretty dead simple. You just need to set $this->_id
$user = Users::model()->findAll('username=\''.$this->username.'\' AND password=\''.$this->encryptedPassword.'\'');
if(!isset($user[0])) {
// false
} else {
$this->setState('id', $user[0]->id); // this is for persistent state sakes
$this->_id = $user[0]->id; // this is UserIdentity's ID that'll be fetch by CWebUser
...
}
}
This routine explains how CWebUser get UserIdentity's ID: https://github.com/yiisoft/yii/blob/master/framework/web/auth/CWebUser.php#L221
Please do test it out.
Please try following code. Its working well
//config/main.php
return array (
'component' => array(
'session' => array(
'savePath' => INSTANCE_ROOT.DS.'runtime'.DS.'session',
'autoStart' => true,
),
)
);
// LoginController
class LoginController extends CController {
public function actionLogin () {
if(isset($_POST['LoginForm']))
{
$form = new LoginForm;
$form->setAttributes($_POST['LoginForm']);
if ($form->validate()) {
$user = Users::model()->find('upper(username) = :username', array(
':username' => strtoupper($form->username)));
if($user)
return $this->authenticate($user, $form);
else {
Yii::log( 'som.....', 'error');
$form->addError('password', Yii::t('Username or Password is incorrect'));
}
return false;
}
}
}
protected function authenticate($user, $form) {
$identity = new UserIdentity($user->username, $form->password);
$identity->authenticate();
switch($identity->errorCode) {
case UserIdentity::ERROR_NONE:
$duration = $form->rememberMe ? 3600*24*30 : 0; // 30 days
Yii::app()->user->login($identity,$duration);
return $user;
break;
case UserIdentity::ERROR_EMAIL_INVALID:
$form->addError("password",Yii::t('Username or Password is incorrect'));
break;
case UserIdentity::ERROR_STATUS_INACTIVE:
$form->addError("status",Yii::t('This account is not activated.'));
break;
case UserIdentity::ERROR_STATUS_BANNED:
$form->addError("status",Yii::t('This account is blocked.'));
break;
case UserIdentity::ERROR_STATUS_REMOVED:
$form->addError('status', Yii::t('Your account has been deleted.'));
break;
case UserIdentity::ERROR_PASSWORD_INVALID:
Yii::log( Yii::t(
'Password invalid for user {username} (Ip-Address: {ip})', array(
'{ip}' => Yii::app()->request->getUserHostAddress(),
'{username}' => $form->username)), 'error');
if(!$form->hasErrors())
$form->addError("password",Yii::t('Username or Password is incorrect'));
break;
return false;
}
}
}
class UserIdentity extends CUserIdentity {
const ERROR_EMAIL_INVALID=3;
const ERROR_STATUS_INACTIVE=4;
const ERROR_STATUS_BANNED=5;
const ERROR_STATUS_REMOVED=6;
const ERROR_STATUS_USER_DOES_NOT_EXIST=7;
public function authenticate()
{
$user = Users::model()->find('username = :username', array(
':username' => $this->username));
if(!$user)
return self::ERROR_STATUS_USER_DOES_NOT_EXIST;
if(Users::encrypt($this->password)!==$user->password)
$this->errorCode=self::ERROR_PASSWORD_INVALID;
else if($user->status == YumUser::STATUS_INACTIVE)
$this->errorCode=self::ERROR_STATUS_INACTIVE;
else if($user->status == YumUser::STATUS_BANNED)
$this->errorCode=self::ERROR_STATUS_BANNED;
else if($user->status == YumUser::STATUS_REMOVED)
$this->errorCode=self::ERROR_STATUS_REMOVED;
return !$this->errorCode;
}
}
class Users extends CActiveModel
{
const STATUS_INACTIVE = 0;
const STATUS_ACTIVE = 1;
const STATUS_BANNED = -1;
const STATUS_REMOVED = -2;
// some ..........
public static function encrypt($string = "")
{
$salt = 'salt';
$string = sprintf("%s%s%s", $salt, $string, $salt);
return md5($string);
}
}
check your security configuration for cookies and sessions.
disable session.use_only_cookies & session.cookie_httponly in php.ini
file.
in PHP.INI => session.use_only_cookies = 0