SHARE
TWEET

CakePHP PasswordableBehavior

a guest Jul 3rd, 2013 152 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. <!-- File: /app/Controller/UsersController.php -->
  2. <?php
  3.  
  4. class UsersController extends AppController {
  5.  
  6.     public function passchange($id = null) {
  7.         if ($this->request->is('post') || $this->request->is('put')) {
  8.             // attach the behavior and force the user to enter the current password for security purposes
  9.             $this->User->Behaviors->attach('Tools.Passwordable', array('current'=>true));
  10.             $this->request->data['User']['id'] = $this->Session->read('Auth.User.id');
  11.             if ($this->User->save($this->request->data, true, array('pwd', 'pwd_repeat', 'id'))) {
  12.                 //SUCCESS flash/redirect
  13.                 $this->Session->setFlash(__('user password has been saved'));
  14.                 $this->redirect(array('action' => 'index'));
  15.             }
  16.             $this->Session->setFlash(__('Err saving password.'));
  17.          
  18.             // pw should not be passed to the view again for security reasons
  19.             unset($this->request->data['User']['pwd']);
  20.             unset($this->request->data['User']['pwd_repeat']);
  21.         }
  22.     }
  23.  
  24. (...)
  25.  
  26. #################################################################
  27. <!-- File: /app/View/Users/passchange.ctp -->
  28.  
  29. <div class="reveal-modal" style="visibility: visible !important" >
  30. <h1>Change password</h1>
  31. <?php echo $this->Form->create(); ?>
  32.  
  33. <?php
  34. echo $this->Form->input('id',array('type'=>'hidden'));
  35. echo $this->Form->input('pwd_current',array('type'=>'password', 'value'=>'', 'autocomplete'=>'off','label'=>array('text'=>'Enter your password')));
  36. echo $this->Form->input('pwd',array('type'=>'password', 'value'=>'', 'autocomplete'=>'off','label'=>array('text'=>'Enter your new password')));
  37. echo $this->Form->input('pwd_repeat',array('type'=>'password', 'value'=>'', 'autocomplete'=>'off','label'=>array('text'=>'Confirm your new password')));
  38. ?>
  39.  
  40. <button type="submit">Save</button>
  41.  
  42. <?php echo $this->Form->end(); ?>
  43. <a class="close-reveal-modal" id="cancela">&#215;</a>
  44. </div>
  45. <div id="loading">Loading...</div>
  46. <div id="respostaAjax"></div>
  47.  
  48. #################################################################
  49. <!-- File: /app/Model/User.php -->
  50.  
  51. <?php
  52. App::uses('PasswordableBehavior', 'Tools.Model/Behavior');
  53. class User extends AppModel {
  54.     public $validate = array(
  55.         'username' => array(
  56.             'required' => array(
  57.                 'rule' => array('notEmpty'),
  58.                 'message' => 'A username is required'
  59.             )
  60.         ),
  61.         'password' => array(
  62.             'required' => array(
  63.                 'rule' => array('notEmpty'),
  64.                 'message' => 'A password is required'
  65.             )
  66.         ),
  67.         'role' => array(
  68.             'valid' => array(
  69.                 'rule' => array('inList', array('admin', 'user')),
  70.                 'message' => 'Please enter a valid role',
  71.                 'allowEmpty' => false
  72.             )
  73.         ),
  74.     );
  75.  
  76.      public function beforeSave($options = array()) {
  77.         parent::beforeSave($options);
  78.         if(isset($this->data[$this->alias]['pwd'])) {
  79.             $this->data[$this->alias]['password'] = Security::hash($this->data[$this->alias]['pwd'], 'blowfish');
  80.             $this->request->data[$this->alias]['password'] = '';
  81.         }
  82.         return true;
  83.     }  
  84. }
  85.  
  86. <!-- Passwordablebehavior.php -->
  87.  
  88. <?php
  89. App::uses('ModelBehavior', 'Model');
  90. App::uses('Router', 'Routing');
  91. App::uses('CakeRequest', 'Network');
  92. App::uses('CakeResponse', 'Network');
  93. App::uses('Security', 'Utility');
  94.  
  95. if (!defined('PWD_MIN_LENGTH')) {
  96.         define('PWD_MIN_LENGTH', 3);
  97. }
  98. if (!defined('PWD_MAX_LENGTH')) {
  99.         define('PWD_MAX_LENGTH', 20);
  100. }
  101.  
  102. /**
  103.  * A cakephp2 behavior to work with passwords the easy way
  104.  * - complete validation
  105.  * - hashing of password
  106.  * - requires fields (no tempering even without security component)
  107.  * - usable for edit forms (allowEmpty=>true for optional password update)
  108.  *
  109.  * usage: do NOT add it via $actAs = array()
  110.  * attach it dynamically in only those actions where you actually change the password like so:
  111.  * $this->User->Behaviors->load('Tools.Passwordable', array(SETTINGSARRAY));
  112.  * as first line in any action where you want to allow the user to change his password
  113.  * also add the two form fields in the form (pwd, pwd_confirm)
  114.  * the rest is cake automagic :)
  115.  *
  116.  * now also is capable of:
  117.  * - require current password prior to altering it (current=>true)
  118.  * - don't allow the same password it was before (allowSame=>false)
  119.  *
  120.  * TODO: allowEmpty and nonEmptyToEmpty - maybe with checkbox "set_new_pwd"
  121.  * feel free to help me out
  122.  *
  123.  * @version 1.6 (renamed from ChangePassword to Passwordable)
  124.  * @author Mark Scherer
  125.  * @link http://www.dereuromark.de/2011/08/25/working-with-passwords-in-cakephp
  126.  * @license MIT
  127.  * 2012-08-18 ms
  128.  */
  129. class PasswordableBehavior extends ModelBehavior {
  130.  
  131.         /**
  132.          * @access protected
  133.          */
  134.         protected $_defaults = array(
  135.                 'field' => 'password',
  136.                 'confirm' => true, # set to false if in admin view and no confirmation (pwd_repeat) is required
  137.                 'allowEmpty' => false, # if password must be provided or be changed (set to true for update sites)
  138.                 'current' => true, # expect the current password for security purposes
  139.                 'formField' => 'pwd',
  140.                 'formFieldRepeat' => 'pwd_repeat',
  141.                 'formFieldCurrent' => 'pwd_current',
  142.                 'hashType' => 'blowfish',
  143.                 'hashSalt' => false,
  144.                 'auth' => null, # which component (defaults to AuthComponent),
  145.                 'allowSame' => true, # dont allow the old password on change,
  146.                 'minLength' => PWD_MIN_LENGTH,
  147.                 'maxLength' => PWD_MAX_LENGTH
  148.         );
  149.  
  150.         /**
  151.          * @access protected
  152.          */
  153.         protected $_validationRules = array(
  154.                 'formField' => array(
  155.                         'between' => array(
  156.                                 'rule' => array('between', PWD_MIN_LENGTH, PWD_MAX_LENGTH),
  157.                                 'message' => array('valErrBetweenCharacters %s %s', PWD_MIN_LENGTH, PWD_MAX_LENGTH),
  158.                                 'allowEmpty' => null,
  159.                                 'last' => true,
  160.                         )
  161.                 ),
  162.                 'formFieldRepeat' => array(
  163.                         'between' => array(
  164.                                 'rule' => array('between', PWD_MIN_LENGTH, PWD_MAX_LENGTH),
  165.                                 'message' => array('valErrBetweenCharacters %s %s', PWD_MIN_LENGTH, PWD_MAX_LENGTH),
  166.                                 'allowEmpty' => null,
  167.                                 'last' => true,
  168.                         ),
  169.                         'validateIdentical' => array(
  170.                                 'rule' => array('validateIdentical', 'formField'),
  171.                                 'message' => 'valErrPwdNotMatch',
  172.                                 'allowEmpty' => null,
  173.                                 'last' => true,
  174.                         )
  175.                 ),
  176.                 'formFieldCurrent' => array(
  177.                         'notEmpty' => array(
  178.                                 'rule' => array('notEmpty'),
  179.                                 'message' => 'valErrProvideCurrentPwd',
  180.                                 'allowEmpty' => null,
  181.                                 'last' => true,
  182.                         ),
  183.                         'validateCurrentPwd' => array(
  184.                                 'rule' => 'validateCurrentPwd',
  185.                                 'message' => 'valErrCurrentPwdIncorrect',
  186.                                 'allowEmpty' => null,
  187.                                 'last' => true,
  188.                         )
  189.                 )
  190.         );
  191.  
  192.         /**
  193.          * if not implemented in AppModel
  194.          * @throws CakeException
  195.          * @return bool $success
  196.          * 2011-07-22 ms
  197.          */
  198.         public function validateCurrentPwd(Model $Model, $data) {
  199.  
  200.                 debug(func_get_args());
  201.  
  202.                 if (is_array($data)) {
  203.                         $pwd = array_shift($data);
  204.                 } else {
  205.                         $pwd = $data;
  206.                 }
  207.  
  208.                 $uid = null;
  209.                 if ($Model->id) {
  210.                         $uid = $Model->id;
  211.                 } elseif (!empty($Model->data[$Model->alias]['id'])) {
  212.                         $uid = $Model->data[$Model->alias]['id'];
  213.                 } else {
  214.                         trigger_error('No user id given');
  215.                         return false;
  216.                 }
  217.  
  218.                 $auth = 'Auth';
  219.                 if (empty($this->settings[$Model->alias]['auth']) && class_exists('AuthExtComponent')) {
  220.                         $auth = 'AuthExt';
  221.                 } elseif ($this->settings[$Model->alias]['auth']) {
  222.                         $auth = $this->settings[$Model->alias]['auth'];
  223.                 }
  224.                 $authClass = $auth . 'Component';
  225.                 if (!class_exists($authClass)) {
  226.                         throw new CakeException('No Authentication class found (' . $authClass. ')');
  227.                 }
  228.  
  229.                 $this->Auth = new $authClass(new ComponentCollection());
  230.  
  231.                 # easiest authenticate method via form and (id + pwd)
  232.                 $this->Auth->authenticate = array(
  233.                         'Form' => array(
  234.                                 'fields' => array('username' => 'id', 'password' => $this->settings[$Model->alias]['field'])
  235.                         )
  236.                 );
  237.                 $request = Router::getRequest();
  238.                 $request->data['User'] = array('id' => $uid, 'password' => $pwd);
  239.                 $response = new CakeResponse();
  240.                 return (bool)$this->Auth->identify($request, $response);
  241.         }
  242.  
  243.         /**
  244.          * if not implemented in AppModel
  245.          * @return bool $success
  246.          * 2011-07-22 ms
  247.          */
  248.         public function validateIdentical(Model $Model, $data, $compareWith = null) {
  249.  
  250.  
  251.                 if (is_array($data)) {
  252.                         $value = array_shift($data);
  253.                 } else {
  254.                         $value = $data;
  255.                 }
  256.                 $compareValue = $Model->data[$Model->alias][$compareWith];
  257.                 return ($compareValue === $value);
  258.         }
  259.  
  260.         /**
  261.          * if not implemented in AppModel
  262.          * @return bool $success
  263.          * 2011-11-10 ms
  264.          */
  265.         public function validateNotSame(Model $Model, $data, $field1, $field2) {
  266.                 $value1 = $Model->data[$Model->alias][$field1];
  267.                 $value2 = $Model->data[$Model->alias][$field2];
  268.                 return ($value1 !== $value2);
  269.         }
  270.  
  271.         /**
  272.          * if not implemented in AppModel
  273.          * @return bool $success
  274.          * 2011-11-10 ms
  275.          */
  276.         public function validateNotSameHash(Model $Model, $data, $formField) {
  277.                 $field = $this->settings[$Model->alias]['field'];
  278.                 $type = $this->settings[$Model->alias]['hashType'];
  279.                 $salt = $this->settings[$Model->alias]['hashSalt'];
  280.  
  281.                 if (!isset($Model->data[$Model->alias][$Model->primaryKey])) {
  282.                         return true;
  283.                 }
  284.                 $primaryKey = $Model->data[$Model->alias][$Model->primaryKey];
  285.                 $value = Security::hash($Model->data[$Model->alias][$formField], $type, $salt);
  286.                 $dbValue = $Model->field($field, array($Model->primaryKey => $primaryKey));
  287.                 if (!$dbValue) {
  288.                         return true;
  289.                 }
  290.                 return ($value !== $dbValue);
  291.         }
  292.  
  293.         /**
  294.          * Adding validation rules
  295.          * also adds and merges config settings (direct + configure)
  296.          *
  297.          * @return void
  298.          * 2011-08-24 ms
  299.          */
  300.         public function setup(Model $Model, $config = array()) {
  301.                 $defaults = $this->_defaults;
  302.                 if ($configureDefaults = Configure::read('Passwordable')) {
  303.                         $defaults = Set::merge($defaults, $configureDefaults);
  304.                 }
  305.                 $this->settings[$Model->alias] = Set::merge($defaults, $config);
  306.  
  307.                 $formField = $this->settings[$Model->alias]['formField'];
  308.                 $formFieldRepeat = $this->settings[$Model->alias]['formFieldRepeat'];
  309.                 $formFieldCurrent = $this->settings[$Model->alias]['formFieldCurrent'];
  310.  
  311.                 $rules = $this->_validationRules;
  312.  
  313.                 # add the validation rules if not already attached
  314.                 if (!isset($Model->validate[$formField])) {
  315.                         $Model->validator()->add($formField, $rules['formField']);
  316.                 }
  317.                 if (!isset($Model->validate[$formFieldRepeat])) {
  318.                         $ruleSet = $rules['formFieldRepeat'];
  319.                         $ruleSet['validateIdentical']['rule'][1] = $formField;
  320.                         $Model->validator()->add($formFieldRepeat, $ruleSet);
  321.                 }
  322.  
  323.                 if ($this->settings[$Model->alias]['current'] && !isset($Model->validate[$formFieldCurrent])) {
  324.                         $Model->validator()->add($formFieldCurrent, $rules['formFieldCurrent']);
  325.  
  326.                         if (!$this->settings[$Model->alias]['allowSame']) {
  327.                                 $Model->validator()->add($formField, 'validateNotSame', array(
  328.                                         'rule' => array('validateNotSame', $formField, $formFieldCurrent),
  329.                                         'message' => 'valErrPwdSameAsBefore',
  330.                                         'allowEmpty' => $this->settings[$Model->alias]['allowEmpty'],
  331.                                         'last' => true,
  332.                                 ));
  333.                         }
  334.                 } elseif (!isset($Model->validate[$formFieldCurrent])) {
  335.                         # try to match the password against the hash in the DB
  336.                         if (!$this->settings[$Model->alias]['allowSame']) {
  337.                                 $Model->validator()->add($formField, 'validateNotSame', array(
  338.                                         'rule' => array('validateNotSameHash', $formField),
  339.                                         'message' => 'valErrPwdSameAsBefore',
  340.                                         'allowEmpty' => $this->settings[$Model->alias]['allowEmpty'],
  341.                                         'last' => true,
  342.                                 ));
  343.                         }
  344.                 }
  345.         }
  346.  
  347.         /**
  348.          * Preparing the data
  349.          *
  350.          * @return bool $success
  351.          * 2011-07-22 ms
  352.          */
  353.         public function beforeValidate(Model $Model) {
  354.                 $formField = $this->settings[$Model->alias]['formField'];
  355.                 $formFieldRepeat = $this->settings[$Model->alias]['formFieldRepeat'];
  356.                 $formFieldCurrent = $this->settings[$Model->alias]['formFieldCurrent'];
  357.  
  358.                 # make sure fields are set and validation rules are triggered - prevents tempering of form data
  359.                 if (!isset($Model->data[$Model->alias][$formField])) {
  360.                         $Model->data[$Model->alias][$formField] = '';
  361.                 }
  362.                 if ($this->settings[$Model->alias]['confirm'] && !isset($Model->data[$Model->alias][$formFieldRepeat])) {
  363.                         $Model->data[$Model->alias][$formFieldRepeat] = '';
  364.                 }
  365.                 if ($this->settings[$Model->alias]['current'] && !isset($Model->data[$Model->alias][$formFieldCurrent])) {
  366.                         $Model->data[$Model->alias][$formFieldCurrent] = '';
  367.                 }
  368.  
  369.                 # check if we need to trigger any validation rules
  370.                 if ($this->settings[$Model->alias]['allowEmpty']) {
  371.                         $current = !empty($Model->data[$Model->alias][$formFieldCurrent]);
  372.                         $new = !empty($Model->data[$Model->alias][$formField]) || !empty($Model->data[$Model->alias][$formFieldRepeat]);
  373.                         if (!$new && !$current) {
  374.                                 //$Model->validator()->remove($formField); // tmp only!
  375.                                 //unset($Model->validate[$formField]);
  376.                                 unset($Model->data[$Model->alias][$formField]);
  377.                                 if ($this->settings[$Model->alias]['confirm']) {
  378.                                         //$Model->validator()->remove($formFieldRepeat); // tmp only!
  379.                                         //unset($Model->validate[$formFieldRepeat]);
  380.                                         unset($Model->data[$Model->alias][$formFieldRepeat]);
  381.                                 }
  382.                                 if ($this->settings[$Model->alias]['current']) {
  383.                                         //$Model->validator()->remove($formFieldCurrent); // tmp only!
  384.                                         //unset($Model->validate[$formFieldCurrent]);
  385.                                         unset($Model->data[$Model->alias][$formFieldCurrent]);
  386.                                 }
  387.                                 return true;
  388.                         }
  389.                 }
  390.  
  391.                 # add fields to whitelist!
  392.                 $whitelist = array($this->settings[$Model->alias]['formField'], $this->settings[$Model->alias]['formFieldRepeat']);
  393.                 if ($this->settings[$Model->alias]['current']) {
  394.                         $whitelist[] = $this->settings[$Model->alias]['formFieldCurrent'];
  395.                 }
  396.                 if (!empty($Model->whitelist)) {
  397.                         $Model->whitelist = array_merge($Model->whitelist, $whitelist);
  398.                 }
  399.  
  400.                 return true;
  401.         }
  402.  
  403.  
  404.         /**
  405.          * Hashing the password and whitelisting
  406.          *
  407.          * @return bool $success
  408.          * 2011-07-22 ms
  409.          */
  410.         public function beforeSave(Model $Model) {
  411.  
  412.                 //debug(func_get_args());
  413.  
  414.                 $formField = $this->settings[$Model->alias]['formField'];
  415.                 $field = $this->settings[$Model->alias]['field'];
  416.                 $type = $this->settings[$Model->alias]['hashType'];
  417.                 $salt = $this->settings[$Model->alias]['hashSalt'];
  418.  
  419.                 if (isset($Model->data[$Model->alias][$formField])) {
  420.                         $Model->data[$Model->alias][$field] = Security::hash($Model->data[$Model->alias][$formField], $type, $salt);
  421.                         unset($Model->data[$Model->alias][$formField]);
  422.                         if ($this->settings[$Model->alias]['confirm']) {
  423.                                 $formFieldRepeat = $this->settings[$Model->alias]['formFieldRepeat'];
  424.                                 unset($Model->data[$Model->alias][$formFieldRepeat]);
  425.                         }
  426.                         if ($this->settings[$Model->alias]['current']) {
  427.                                 $formFieldCurrent = $this->settings[$Model->alias]['formFieldCurrent'];
  428.                                 unset($Model->data[$Model->alias][$formFieldCurrent]);
  429.                         }
  430.                         # update whitelist
  431.                         if (!empty($Model->whitelist)) {
  432.                                 $Model->whitelist = array_merge($Model->whitelist, array($field));
  433.                         }
  434.                 }
  435.  
  436.                 return true;
  437.         }
  438.  
  439. }
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top