views:

740

answers:

1

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;
}
+2  A: 

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).

deizel
Thank you deizel - exactly what I was looking for!
BWelfel