#php #cakephp #cakephp-3.0
#php #cakephp #cakephp-3.0
Вопрос:
Я работаю над переносом веб-приложения, разработанного с использованием CakePHP 1.2, я переношу его на версию 3.9.4. Все пароли пользователей в базе данных хэшируются с использованием MD5, но версия 3.9.4 использует bcrypt в качестве хэшера по умолчанию. Я хочу использовать MD5, чтобы я мог аутентифицировать пользователей с помощью MD5. Я просмотрел эту статью и использовал класс LegacyPasswordHasher, как указано в статье, но он не работает. Вот как я настроил AuthComponent в AppController.
$this->loadComponent('Auth', [
'authenticate' => ['Form' => [
'passwordHasher' => [
'className' => 'Legacy',
],
'fields' => [
'username' => 'email',
'password' => 'password',
],
]],
'loginAction' => [
'controller' => 'Users',
'action' => 'login',
],
]);
И вот мой класс LegcayPasswordHasher:
<?php
namespace AppAuth;
use CakeAuthAbstractPasswordHasher;
class LegacyPasswordHasher extends AbstractPasswordHasher {
public function hash($password) {
return md5($password);
}
public function check($password, $hashedPassword) {
$userPassword = md5($password);
return $userPassword === $hashedPassword;
}
}
Это UserAuthComponent
из старой системы. Находится в app/Controller/Component
.
class UserAuthComponent extends Component {
/**
* This component uses following components
*
* @var array
*/
var $components = array('Session', 'Cookie', 'RequestHandler', 'ControllerList');
/**
* configur key
*
* @var string
*/
var $configureKey='User';
function initialize(Controller $controller) {
}
function __construct(ComponentCollection $collection, $settings = array()) {
parent::__construct($collection, $settings);
}
function startup(Controller $controller = null) {
}
/**
* Called before the controller action. You can use this method to configure and customize components
* or perform logic that needs to happen before each controller action.
*
* @param object $c current controller object
* @return void
*/
function beforeFilter(amp;$c) {
UsermgmtInIt($this);
$user = $this->__getActiveUser();
$pageRedirect = $c->Session->read('permission_error_redirect');
$c->Session->delete('permission_error_redirect');
$controller = $c->params['controller'];
$action = $c->params['action'];
$actionUrl = $controller.'/'.$action;
$requested= (isset($c->params['requested']) amp;amp; $c->params['requested']==1) ? true : false;
$permissionFree=array('users/login', 'users/logout', 'users/register', 'users/userVerification', 'users/forgotPassword', 'users/activatePassword', 'pages/display', 'users/accessDenied', 'users/emailVerification','users/admin_login','schools/garminApi','schools/viewActivitymy','schools/viewbonuspointupdate','students/viewclassstudentleader','students/viewclassstudentleaderexport','schools/viewpointsupdate');
$access =str_replace(' ','',ucwords(str_replace('_',' ',$controller))).'/'.$action;
$allControllers=$this->ControllerList->getControllerWithMethods();
$errorPage=false;
if (!in_array($access, $allControllers)) {
$errorPage=true;
}
if( $action == "viewActivitymy" || $action == "viewActivitymycron" || $action == "viewActivitymycronfitbit"|| $action == "viewActivityfitbit" || $action=="viewPointactivity" || $action =="viewActivitymycron_manual" || $action =="viewbonuspointupdate" || $action =="viewpointsupdate" || $action =="viewclassstudentleader" || $action =="viewclassstudentleaderexport"){
}else{
if ((empty($pageRedirect) || $actionUrl!='users/login') amp;amp; !$requested amp;amp; !in_array($actionUrl, $permissionFree) amp;amp; !$errorPage) {
App::import("Model", "UserGroup");
$userGroupModel = new UserGroup;
if (!$this->isLogged()) {
if (!$userGroupModel->isGuestAccess($controller, $action)) {
$c->log('permission: actionUrl-'.$actionUrl, LOG_DEBUG);
$c->Session->write('permission_error_redirect','/users/login');
$c->Session->setFlash('You need to be signed in to view this page.');
$cUrl = '/'.$c->params->url;
if(!empty($_SERVER['QUERY_STRING'])) {
$rUrl = $_SERVER['REQUEST_URI'];
$pos =strpos($rUrl, $cUrl);
$cUrl=substr($rUrl, $pos, strlen($rUrl));
}
$c->Session->write('OriginAfterLogin', $cUrl);
$c->redirect('/login');
}
} else {
if (!$userGroupModel->isUserGroupAccess($controller, $action, $this->getGroupId())) {
$c->log('permission: actionUrl-'.$actionUrl, LOG_DEBUG);
$c->Session->write('permission_error_redirect','/users/login');
$c->redirect('/accessDenied');
}
}
}
}
}
/**
* Used to check whether user is logged in or not
*
* @access public
* @return boolean
*/
public function isLogged() {
return ($this->getUserId() !== null);
}
/**
* Used to get user from session
*
* @access public
* @return array
*/
public function getUser() {
return $this->Session->read('UserAuth');
}
/**
* Used to get user id from session
*
* @access public
* @return integer
*/
public function getUserId() {
return $this->Session->read('UserAuth.User.id');
}
/**
* Used to get group id from session
*
* @access public
* @return integer
*/
public function getGroupId() {
return $this->Session->read('UserAuth.User.user_group_id');
}
/**
* Used to get group name from session
*
* @access public
* @return string
*/
public function getGroupName() {
return $this->Session->read('UserAuth.UserGroup.name');
}
/**
* Used to check is admin logged in
*
* @access public
* @return string
*/
public function isAdmin() {
$groupId = $this->Session->read('UserAuth.User.user_group_id');
if($groupId==ADMIN_GROUP_ID) {
return true;
}
return false;
}
/**
* Used to check is guest logged in
*
* @access public
* @return string
*/
public function isGuest() {
$groupId = $this->Session->read('UserAuth.User.user_group_id');
if(empty($groupId)) {
return true;
}
return false;
}
/**
* Used to make password in hash format
*
* @access public
* @param string $pass password of user
* @return hash
*/
public function makePassword($pass, $salt=null) {
return md5(md5($pass).md5($salt));
}
/**
* Used to make salt in hash format
*
* @access public
* @return hash
*/
public function makeSalt() {
$rand = mt_rand(0, 32);
$salt = md5($rand . time());
return $salt;
}
/**
* Used to maintain login session of user
*
* @access public
* @param mixed $type possible values 'guest', 'cookie', user array
* @param string $credentials credentials of cookie, default null
* @return array
*/
public function login($type = 'guest', $credentials = null) {
$user=array();
if (is_string($type) amp;amp; ($type=='guest' || $type=='cookie')) {
App::import("Model", "User");
$userModel = new User;
$user = $userModel->authsomeLogin($type, $credentials);
} elseif (is_array($type)) {
$user =$type;
}
Configure::write($this->configureKey, $user);
$this->Session->write('UserAuth', $user);
return $user;
}
/**
* Used to delete user session and cookie
*
* @access public
* @return void
*/
public function logout() {
$this->Session->delete('UserAuth');
Configure::write($this->configureKey, array());
$this->Cookie->delete(LOGIN_COOKIE_NAME);
}
/**
* Used to persist cookie for remember me functionality
*
* @access public
* @param string $duration duration of cookie life time on user's machine
* @return boolean
*/
public function persist($duration = '2 weeks') {
App::import("Model", "User");
$userModel = new User;
$token = $userModel->authsomePersist($this->getUserId(), $duration);
$token = $token.':'.$duration;
return $this->Cookie->write(
LOGIN_COOKIE_NAME,
$token,
true, // encrypt = true
$duration
);
}
/**
* Used to check user's session if user's session is not available then it tries to get login from cookie if it exist
*
* @access private
* @return array
*/
private function __getActiveUser() {
$user = Configure::read($this->configureKey);
if (!empty($user)) {
return $user;
}
$this->__useSession() || $this->__useCookieToken() || $this->__useGuestAccount();
$user = Configure::read($this->configureKey);
if (is_null($user)) {
throw new Exception(
'Unable to initilize user'
);
}
return $user;
}
/**
* Used to get user from session
*
* @access private
* @return boolean
*/
private function __useSession() {
$user = $this->getUser();
if (!$user) {
return false;
}
Configure::write($this->configureKey, $user);
return true;
}
/**
* Used to get login from cookie
*
* @access private
* @return boolean
*/
private function __useCookieToken() {
$token = $this->Cookie->read(LOGIN_COOKIE_NAME);
if (!$token) {
return false;
}
$user=false;
// Extract the duration appendix from the token
if(strpos($token, ":") !==false) {
$tokenParts = split(':', $token);
$duration = array_pop($tokenParts);
$token = join(':', $tokenParts);
$user = $this->login('cookie', compact('token', 'duration'));
// Delete the cookie once its been used
}
$this->Cookie->delete(LOGIN_COOKIE_NAME);
if (!$user) {
return;
}
$this->persist($duration);
return (bool)$user;
}
/**
* Used to get login as guest
*
* @access private
* @return array
*/
private function __useGuestAccount() {
return $this->login('guest');
}
}
И это User
Модель app/Mode/
, расположенная в старой кодовой базе.
App::uses('AppModel', 'Model');
App::uses('CakeEmail', 'Network/Email');
class User extends AppModel {
/**
* This model belongs to following models
*
* @var array
*/
var $belongsTo = array('UserGroup');
/**
* This model has following models
*
* @var array
*/
var $hasMany = array('LoginToken'=>array('className'=>'LoginToken','limit' =>1));
/**
* model validation array
*
* @var array
*/
var $validate = array();
/**
* UsetAuth component object
*
* @var object
*/
var $userAuth;
/**
* model validation array
*
* @var array
*/
function LoginValidate() {
$validate1 = array(
'email'=> array(
'mustNotEmpty'=>array(
'rule' => 'notEmpty',
'message'=> 'Please enter email or username')
),
'password'=>array(
'mustNotEmpty'=>array(
'rule' => 'notEmpty',
'message'=> 'Please enter password')
)
);
$this->validate=$validate1;
return $this->validates();
}
/**
* model validation array
*
* @var array
*/
function RegisterValidate() {
$validate1 = array(
//"user_group_id" => array(
//'rule' => array('comparison', '!=', 0),
//'message'=> 'Please select group'),
'school_name'=> array(
'mustNotEmpty'=>array(
'rule' => 'notEmpty',
'message'=> 'Please enter school name',
'last'=>true),
'mustUnique'=>array(
'rule' =>'isUnique',
'message' =>'This school name already taken',
'last'=>true),
// 'mustBeLonger'=>array(
// 'rule' => array('minLength', 4),
// 'message'=> 'Username must be greater than 3 characters',
// 'last'=>true),
),
'first_name'=> array(
'mustNotEmpty'=>array(
'rule' => 'notEmpty',
'message'=> 'Please enter administrator first name')
),
'last_name'=> array(
'mustNotEmpty'=>array(
'rule' => 'notEmpty',
'on' => 'create',
'message'=> 'Please enter administrator last name')
),
'email'=> array(
'mustNotEmpty'=>array(
'rule' => 'notEmpty',
'message'=> 'Please enter email',
'last'=>true),
'mustBeEmail'=> array(
'rule' => array('email'),
'message' => 'Please enter valid email',
'last'=>true),
'mustUnique'=>array(
'rule' =>'isUnique',
'message' =>'This email is already registered',
)
),
'oldpassword'=>array(
'mustNotEmpty'=>array(
'rule' => 'notEmpty',
'message'=> 'Please enter old password',
'last'=>true),
'mustMatch'=>array(
'rule' => array('verifyOldPass'),
'message' => 'Please enter correct old password'),
),
'password'=>array(
'mustNotEmpty'=>array(
'rule' => 'notEmpty',
'message'=> 'Please enter password',
'on' => 'create',
'last'=>true),
'mustBeLonger'=>array(
'rule' => array('minLength', 6),
'message'=> 'Password must be greater than 5 characters',
'on' => 'create',
'last'=>true),
'mustMatch'=>array(
'rule' => array('verifies'),
'message' => 'Both passwords must match'),
//'on' => 'create'
),
'number_of_student'=> array(
'mustNotEmpty'=>array(
'rule' => 'notEmpty',
'message'=> 'Please enter number of student')
),
'tracker'=> array(
'mustNotEmpty'=>array(
'rule' => 'notEmpty',
'message'=> 'Please enter tracker number')
),
'captcha'=>array(
'mustMatch'=>array(
'rule' => array('recaptchaValidate'),
'message' => ''),
)
);
$this->validate=$validate1;
return $this->validates();
}
/**
* Used to validate captcha
*
* @access public
* @return boolean
*/
public function recaptchaValidate() {
App::import("Vendor", "recaptcha/recaptchalib");
$recaptcha_challenge_field = (isset($_POST['recaptcha_challenge_field'])) ? $_POST['recaptcha_challenge_field'] : "";
$recaptcha_response_field = (isset($_POST['recaptcha_response_field'])) ? $_POST['recaptcha_response_field'] : "";
$resp = recaptcha_check_answer(PRIVATE_KEY_FROM_RECAPTCHA, $_SERVER['REMOTE_ADDR'], $recaptcha_challenge_field, $recaptcha_response_field);
$error = $resp->error;
if(!$resp->is_valid) {
$this->validationErrors['captcha'][0]=$error;
}
return true;
}
/**
* Used to match passwords
*
* @access public
* @return boolean
*/
public function verifies() {
return ($this->data['User']['password']===$this->data['User']['cpassword']);
}
/**
* Used to match old password
*
* @access public
* @return boolean
*/
public function verifyOldPass() {
$userId = $this->userAuth->getUserId();
$user = $this->findById($userId);
$oldpass=$this->userAuth->makePassword($this->data['User']['oldpassword'], $user['User']['salt']);
return ($user['User']['password']===$oldpass);
}
/**
* Used to send registration mail to user
*
* @access public
* @param array $user user detail array
* @return void
*/
public function sendRegistrationMail($user) {
// $user['User']['email'];
// send email to newly created user
$email = new CakeEmail();
$userId=$user['User']['id'];
$email->config('smtp');
$fromConfig = 'pradeep.singh@udaantechnologies.com';
$fromNameConfig = 'Pradeep';
$email->from(array( $fromConfig => $fromNameConfig));
$email->sender(array( $fromConfig => $fromNameConfig));
$email->to($user['User']['email']);
$email->subject('Your registration is complete');
//$email->transport('Debug');
$body="Welcome ".$user['User']['first_name'].", Thank you for your registration on ".'http://localhost/garmin/register'." nn Thanks,n".EMAIL_FROM_NAME;
try{
$result = $email->send($body);
} catch (Exception $ex) {
// we could not send the email, ignore it
$result="Could not send registration email to userid-".$userId;
}
$this->log($result, LOG_DEBUG);
}
/**
* Used to send email verification mail to user
*
* @access public
* @param array $user user detail array
* @return void
*/
public function sendVerificationMail($user) {
$userId=$user['User']['id'];
$email = new CakeEmail();
$email->config('smtp');
$fromConfig = 'pradeep.singh@udaantechnologies.com';
$fromNameConfig = 'Pradeep';
$email->from(array( $fromConfig => $fromNameConfig));
$email->sender(array( $fromConfig => $fromNameConfig));
$email->to($user['User']['email']);
$email->subject('Email Verification Mail');
$activate_key = $this->getActivationKey($user['User']['password']);
$link = Router::url("/userVerification?ident=$userIdamp;activate=$activate_key",true);
$body="Hi, Click the link below to complete your registration nn ".$link;
try{
$result = $email->send($body);
} catch (Exception $ex){
// we could not send the email, ignore it
$result="Could not send verification email to userid-".$userId;
}
$this->log($result, LOG_DEBUG);
}
/**
* Used to generate activation key
*
* @access public
* @param string $password user password
* @return hash
*/
public function getActivationKey($password) {
$salt = Configure::read ( "Security.salt" );
return md5(md5($password).$salt);
}
/**
* Used to send forgot password mail to user
*
* @access public
* @param array $user user detail
* @return void
*/
public function forgotPassword($user) {
$userId=$user['User']['id'];
$email = new CakeEmail();
$fromConfig = EMAIL_FROM_ADDRESS;
$fromNameConfig = EMAIL_FROM_NAME;
$email->from(array( $fromConfig => $fromNameConfig));
$email->sender(array( $fromConfig => $fromNameConfig));
$email->to($user['User']['email']);
$email->subject(EMAIL_FROM_NAME.': Request to Reset Your Password');
$activate_key = $this->getActivationKey($user['User']['password']);
$link = Router::url("/activatePassword?ident=$userIdamp;activate=$activate_key",true);
$body= "Welcome ".$user['User']['first_name'].", let's help you get signed in
You have requested to have your password reset on ".EMAIL_FROM_NAME.". Please click the link below to reset your password now :
".$link."
If above link does not work please copy and paste the URL link (above) into your browser address bar to get to the Page to reset password
Choose a password you can remember and please keep it secure.
Thanks,n".
EMAIL_FROM_NAME;
try{
$result = $email->send($body);
} catch (Exception $ex){
// we could not send the email, ignore it
$result="Could not send forgot password email to userid-".$userId;
}
$this->log($result, LOG_DEBUG);
}
/**
* Used to mark cookie used
*
* @access public
* @param string $type
* @param string $credentials
* @return array
*/
public function authsomeLogin($type, $credentials = array()) {
switch ($type) {
case 'guest':
// You can return any non-null value here, if you don't
// have a guest account, just return an empty array
return array();
case 'cookie':
$loginToken=false;
if(strpos($credentials['token'], ":") !==false) {
list($token, $userId) = split(':', $credentials['token']);
$duration = $credentials['duration'];
$loginToken = $this->LoginToken->find('first', array(
'conditions' => array(
'user_id' => $userId,
'token' => $token,
'duration' => $duration,
'used' => false,
'expires <=' => date('Y-m-d H:i:s', strtotime($duration)),
),
'contain' => false
));
}
if (!$loginToken) {
return false;
}
$loginToken['LoginToken']['used'] = true;
$this->LoginToken->save($loginToken);
$conditions = array(
'User.id' => $loginToken['LoginToken']['user_id']
);
break;
default:
return array();
}
return $this->find('first', compact('conditions'));
}
/**
* Used to generate cookie token
*
* @access public
* @param integer $userId user id
* @param string $duration cookie persist life time
* @return string
*/
public function authsomePersist($userId, $duration) {
$token = md5(uniqid(mt_rand(), true));
$this->LoginToken->create(array(
'user_id' => $userId,
'token' => $token,
'duration' => $duration,
'expires' => date('Y-m-d H:i:s', strtotime($duration)),
));
$this->LoginToken->save();
return "${token}:${userId}";
}
/**
* Used to get name by user id
*
* @access public
* @param integer $userId user id
* @return string
*/
public function getNameById($userId) {
$res = $this->findById($userId);
$name=(!empty($res)) ? ($res['User']['first_name'].' '.$res['User']['last_name']) : '';
return $name;
}
/**
* Used to check users by group id
*
* @access public
* @param integer $groupId user id
* @return boolean
*/
public function isUserAssociatedWithGroup($groupId) {
$res = $this->find('count', array('conditions'=>array('User.user_group_id'=>$groupId)));
if(!empty($res)) {
return true;
}
return false;
}
}
И это app/Controller
пользовательский контроллер, расположенный в старой кодовой базе.
С нетерпением жду ваших указаний. Заранее спасибо.
Комментарии:
1. Помимо того факта, что вам действительно больше не следует использовать устаревший компонент аутентификации, и что для устаревших алгоритмов хеширования, которые не привязаны к устаревшим приложениям, вам следует использовать запасной хэшер для медленного переноса хэшей в безопасный алгоритм … что означает » не работает «? Что именно происходит, и что именно вы ожидаете, что произойдет вместо этого?
2. @ndm Я знаю, что не должен использовать устаревшие алгоритмы хеширования, но пока я придерживаюсь его использования, поскольку база данных содержит пользователей с хэшированным паролем md5. Я перенесу его на алгоритм хеширования по умолчанию, но сейчас мне нужно, чтобы он работал с MD5. «не работает» означает, что согласно документации я использую класс LegacyPasswordHasher, но он не аутентифицирует пользователей и не регистрирует их.
3. Какую настройку хеширования использует старое приложение? Устанавливает ли он явно MD5 в качестве алгоритма по умолчанию? Использует ли он какой-либо пользовательский код аутентификации? И вы на 100% уверены, что в базе данных есть простые хэши MD5? Даже в 1.2 по умолчанию использовался соленый хэш, и
sha1
иsha256
были предпочтительнееmd5
.4. @ndm … Я считаю, что он использовал хэши md5 и использовал UserAuthComponent … вот изображение базы данных ibb.co/XzFQ8JH
5. Ну, это, конечно, похоже на соли пользователя / строки (и md5). Вы должны выяснить, так ли это, т.Е. Проверить, Что
UserAuthComponent
происходит с солями, и посмотреть, нужно ли вам / можете ли вы реплицировать его в своем приложении — с помощью хэшеров вам, скорее всего, придется обновлять базу данных, например, добавляя соль к паролю, чтобы у хэшера был доступ к обоими может соответствующим образом генерировать хэш для сравнения.