Search code examples
phpauthenticationcakephpcontains

cakephp 2.2 Using contain with Auth not working


I have a working example of Auth set up with Users & Groups, based on the cookbook tutorial. I have an additional Locations table, which is associated with my Groups. Location hasMany Group. Group belongsTo Locations. In 2.2 I think I should be able to get location data back for a User, but am not able to.

// App controller
public function beforeFilter() {

    $this->Auth->authenticate = array(
        'all' => array (
            'scope' => array('User.status' => 1)
        ),
        'Form' => array(
                'contain'   => array('Group', 'Location'),
                'fields' => array('Location.name')
        )
  );

The above code only works if I create a direct association between User and Location. Is it possible to use contain here with a Group to Location association?


Solution

  • Problem of BaseAuthenticate is how it returns user info: return $result[$model];.
    So when I need contains I'm using alternative component placed in app/Controller/Auth:

    App::uses('FormAuthenticate', 'Controller/Component/Auth');
    
    class FormAndContainableAuthenticate extends FormAuthenticate {
    
        protected function _findUser($username, $password) {
            if (empty($this->settings['contain'])) {    //< deafult
                $userData = parent::_findUser($username, $password);
            } else {    //< with contains
                $userModel = $this->settings['userModel'];
                list($plugin, $model) = pluginSplit($userModel);
                $fields = $this->settings['fields'];
                $conditions = array(
                    $model . '.' . $fields['username'] => $username,
                    $model . '.' . $fields['password'] => $this->_password($password),
                );
                if (!empty($this->settings['scope'])) {
                    $conditions = array_merge($conditions, $this->settings['scope']);
                }
                $modelObj = ClassRegistry::init($userModel);
                $modelObj->contain($this->settings['contain']);
                $result = $modelObj->find('first', array(
                    'conditions' => $conditions
                ));
                if (empty($result) || empty($result[$model])) {
                    return false;
                }
                foreach($result as $modelName => $modelData) {
                    if ($modelName !== $model) {
                        $result[$model][$modelName] = $modelData;
                    }
                }
                $userData = $result[$model];
            }
            // remove dangerous fields like password
            unset($userData[$this->settings['fields']['password']]);
            if (!empty($this->settings['exclude'])) {
                foreach ($this->settings['exclude'] as $fieldName) {
                    unset($userData[$fieldName]);
                }
            }
            return $userData;
        }
    }
    

    As you can see - it uses parent Component when no contains provided.

    Also some bonus: you can provide a set of fields to remove from resulting array. Just pass field names via 'exclude' key

    How to use Component:

        public $components = array(
            'Auth' => array(
                'authenticate' => array(
                    'FormAndContainable' => array(
                        'fields' => array(
                            'username' => 'username',
                            'password' => 'password',
                        ),
                        'userModel' => 'Staff',
                        'contain' => array('StaffPermission'),
                        'exclude' => array('plain_password')
                    )
                ),
            ),
        );