Welcome Guest, Not a member yet? Register   Sign In
Login script: validating with own callback in library.
#1

[eluser]Maximilian Schoening[/eluser]
Hi,
I'm writing my own auth library at the moment and ran into a problem field validation. I have my own callbacks defined however they are ignored during validation. Can somebody tell me why? Here is the login function inside my Auth.php library. I call the login function inside my controller login().

Here's the code:

Code:
/** ----------------------------------------------------------------------
* Login
**/
function login() {
    $this->CI =& get_instance();
    
    $this->CI->load->library('form_validation');
    $this->CI->load->helper('form');
    
    $this->CI->form_validation->set_rules('email', 'Email', 'trim|required|valid_email|callback__login_validate_email|callback__login_validate_password');
    $this->CI->form_validation->set_rules('password', 'Password', 'trim|required');
    
    if ($this->CI->form_validation->run() == FALSE) {
        $this->CI->view('user/login');
    }
    else {
        $user = $this->get_user(set_value('email'));
        
        $this->CI->session->sess_destroy();
        $this->CI->session->sess_create();
        
        $user_data = array(
            'user_id' => $user['id'],
            'user' => $user['email'],
            'logged_in' => TRUE
        );
        
        $this->CI->session->set_userdata($user_data);
        
        redirect('user/profile');
    }
}

function _login_validate_email($email) {
    return $this->check_email($email);
}

function _login_validate_password($password) {
    if (set_value('email') == '' OR $password == '') {
        return TRUE;
    }
    else {
        return $this->check_password(set_value('email'), $password);
    }
}
// -----------------------------------------------------------------------

/** ----------------------------------------------------------------------
* Tools
**/
function check_email($email) {
    $query = $this->CI->db->get_where('users', array('email' => $email));
    
    if ($query->num_rows() == 0) {
        return FALSE;
    }
    else {
        return TRUE;
    }
}

function check_password($email, $password) {
    $this->CI->load->library('encrypt');
    
    $query = $this->CI->db->get_where('users', array('email' => $email));
    
    if ( ! $password != $this->encrypt->decode($query->row->password)) {
        return FALSE;
    }
    else {
        return TRUE;
    }
}

function get_user($email) {
    $query = $this->CI->db->get_where('users', array('email' => $email));
    
    return $query->row_array();
}
// -----------------------------------------------------------------------

Thanks,
Max
#2

[eluser]Aken[/eluser]
I can't explain exactly why this doesn't work, but I'm not surprised. CI is designed to look for callback functions inside the controller that the form_validation library is being used in. This is pretty much the same structure as a controller, though (which is why I can't explain exactly why it doesn't work). It may have to do with the underscore private function method also.

Personally it's really weird to use the form_validation tool inside your library, anyway. I would keep your Auth library code limited to things directly related to the Auth, and let your login() controller do the validation dirty work.
#3

[eluser]Maximilian Schoening[/eluser]
Well I did it this way so I could minimize the code needed to install it but I will probably go your way.
#4

[eluser]Aken[/eluser]
So include a login controller with it. Smile Will be almost the same amount of code, just another file.
#5

[eluser]Maximilian Schoening[/eluser]
Another reason why I did it with the validation is because I wanted to show the validation error message instead of a custom show_error() message. So you would suggest calling the _login_validate_email and _login_validate_password from the controller? And having the check_email and check_password inside the library?

Thanks,
Max
#6

[eluser]Pascal Kriete[/eluser]
Aken is right on the money, it looks for callbacks in the current controller. You can hack it to look elsewhere, but I wouldn't do that in most cases.

This all looks very complicated - let me suggest another approach.

For these frequently used callbacks I almost always extend the form validation library. So in this case I would add a validate_login function:
Code:
class MY_Form_validation extends CI_Form_validation {

    /**
     * Constructor
     *
     * @access    public
     */
    function MY_Form_validation()
    {
        parent::CI_Form_validation();
    }
    
    // --------------------------------------------------------------------

    /**
     * Validate login credentials
     *
     * @access    public
     * @return    bool
     */
    function validate_login()
    {
        $this->CI->load->library('access');
        
        // Validation already failed - don't bother
        if ( ! empty($this->_error_array))
        {
            return TRUE;
        }
        
        $email        = $this->CI->input->post('email');
        $password    = $this->CI->input->post('password');
        
        if ( ! $this->CI->access->login($email, $password))
        {
            $this->set_message('validate_login', 'Login FAIL!');
            return FALSE;
        }

        return TRUE;
    }
}

Then of course you need an authentication library. I like to call it access, reads very nicely when combined with a restrict method. $this->access->restrict('usergroup') - no comments needed.
Code:
class Access {

    var $CI;

    /**
     * Constructor
     *
     * @access    public
     */
    function Access()
    {
        $this->CI =& get_instance();
        $this->CI->load->library('session');
    }
    
    // --------------------------------------------------------------------

    /**
     * Restrict access
     *
     * @access    public
     */
    function restrict($user_group = FALSE)
    {
        if ( ! $this->CI->session->userdata('logged_in'))
        {
            redirect('login');
        }
        
        // Restrict  by user group? Another check
        if ($user_group)
        {
            if ($this->CI->session->get_userdata('user_group') != $user_group)
            {
                redirect('login');
            }
        }        
    }
    
    // --------------------------------------------------------------------
    
    /**
     * Login
     *
     * Authenticate the user and create a session
     *
     * @access    public
     */
    function login($email, $password)
    {
        $query = $this->CI->db->get_where('users', array('email' => $email), 1);
        
        if ($query->num_rows() != 1)
        {
            return FALSE;
        }
        
        // Do whatever needs doing to make it match the db version
        $password = sha1($password.'somethingrandomtosaltthehash');
        
        if ($query->row('password') != $password)
        {
            return FALSE;
        }
        
        // Everything seems to be in order - log them in
        $userdata = array(
            'user_id'    => $query->row('user_id'),
            'user'        => $query->row('email'),
            'logged_in'    => TRUE;
        );
        
        $this->CI->session->set_userdata($userdata);
        
        return TRUE;
    }
}

All you need to do now is add |validate_login to your validation rules and you're done.

Hope that helps a bit.

Disclaimer: obviously completely untested
#7

[eluser]Maximilian Schoening[/eluser]
You rock! Where do I place the file with the extended CI_Form_validation?

Thanks,
Max
#8

[eluser]Pascal Kriete[/eluser]
In application/libraries/MY_Form_validation, as per extending core classes.

-Pascal
#9

[eluser]Maximilian Schoening[/eluser]
Hi,
extending the Form_validation class works great but right now I am using the following code:

Code:
/** ----------------------------------------------------------------------
* Validate login
**/
function validate_login() {
    $this->CI->load->library('auth');
    
    if ( ! empty($this->_error_array)) {
        return TRUE;    
    }
    
    $email = $this->CI->input->post('email');
    $password = $this->CI->input->post('password');
    
    if ($email == FALSE or $password == FALSE) {
        return TRUE;
    }
    
    if ( ! $this->CI->acces->login($email, $password)) {
        $this->set_message('validate_login', 'The email/password combination you provided is wrong.');
        
        return FALSE;
    }
    
    return TRUE;
}
// -----------------------------------------------------------------------

Now the $email and $password fields are hardcoded. Is there a way to make it dynamic and make them use the field they have been associated to in the controller?

Thanks,
Max
#10

[eluser]Pascal Kriete[/eluser]
Assuming the rule is set on the password field (the last field you set at any rate, so that all values have been trimmed):
Code:
$this->CI->form_validation->set_rules('password', 'Password', 'trim|required|validate_login[email]');

The first parameter of a callback is always that field's value - the second parameter is the contents inside the brackets. So here I'm giving it "email" to pass along the name of the email field. You could even extend it to say [password|email] and then explode on the pipe to extract the field names. But if we know that the callback is on the password field, that's not really needed.

And then the callback becomes:
Code:
function validate_login($password, $email_field = '')
{
    $email = $this->CI->input->post($email_field);

Alternatively you could move grabbing the post values to the auth library and just use the extended form validation library to call that function. Then setup a setter in the auth library (something like set_credential_fields) that stores the field names in an instance variable, and call that before running the validation.

Lots of options here Wink .




Theme © iAndrew 2016 - Forum software by © MyBB