LDAP Authentication for Cakephp

This article is going to help you using LDAP to authenticate users rather than relying on a users table with a password column. I will be assuming you will be using cakephp 1.3 and that you have completed Auth and/or ACL setup on your application similar to the ACL tutorial on the cakephp book.

Because we want to control the logging in of the user ourselves and not leave it to the cake magic we need to override the auth component. To do this copy your auth.php from your CAKE_CORE/controllers/components/ to your APP/controllers/components/ folder. Next open it up and fine the login function. It should be around like 684. Once you find it comment out everything inside the fucntion (but leave the function intact. It should look something like this:

function login($data = null) {
    /*$this->__setDefaults();
    $this->_loggedIn = false;
    if (empty($data)) {$data = $this->data; }
    if ($user = $this->identify($data))
    { $this->Session->write($this->sessionKey, $user);
    $this->_loggedIn = true; }
return $this->_loggedIn;*/ }

Next open up your users controller and find your login function. Assuming you followed the guide or have implemented some basic auth you should have an empty login function.

I have written a LDAP helper that can easily be included as a LIB for Cakephp that will get some user data and validate the login, copy and paste it from below to APP/libs/ldap.php

<?php
class ldap{

    private $ldap = null;
    private $ldapServer = 'AD.domain.com';
    private $ldapPort = '389';
    public $suffix = '@domain.com';
    public $baseDN = 'dc=domain,dc=com';
    private $ldapUser = 'LDAPUser';
    private $ldapPassword = 'Pas5w0rd';

    public function  __construct() {
        $this->ldap = ldap_connect($this->ldapServer,$this->ldapPort);

        //these next two lines are required for windows server 03
        ldap_set_option($this->ldap, LDAP_OPT_REFERRALS, 0);
        ldap_set_option($this->ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
    }

    public function auth($user,$pass)
    {
        if (empty($user) or empty($pass))
        {
            return false;
        }
        @$good = ldap_bind($this->ldap,$user.$this->suffix,$pass);
        if( $good === true ){
            return true;
        }else{
            return false;
        }
    }

    public function __destruct(){
        ldap_unbind($this->ldap);
    }

    public function getInfo($user){
        $username = $user.$this->suffix;;
        $attributes = array('givenName','sn','mail','samaccountname','memberof');
        $filter = "(userPrincipalName=$username)";

        ldap_bind($this->ldap,$this->ldapUser.$this->suffix,$this->ldapPassword);
        $result = ldap_search($this->ldap, $this->baseDN, $filter,$attributes);
        $entries = ldap_get_entries($this->ldap, $result);

        return $this->formatInfo($entries);
    }

    private function formatInfo($array){
        $info = array();
        $info['first_name'] = $array[0]['givenname'][0];
        $info['last_name'] = $array[0]['sn'][0];
        $info['name'] = $info['first_name'] .' '. $info['last_name'];
        $info['email'] = $array[0]['mail'][0];
        $info['user'] = $array[0]['samaccountname'][0];
        $info['groups'] = $this->groups($array[0]['memberof']);

        return $info;
    }

    private function groups($array)
    {
        $groups = array();
        $tmp = array();

        foreach( $array as $entry )
        {
            $tmp = array_merge($tmp,explode(',',$entry));
        }

        foreach($tmp as $value) {
            if( substr($value,0,2) == 'CN' ){
                $groups[] = substr($value,3);
            }
        }

        return $groups;
    }
}
?>

Edit the variables at the tom of the file to reflect your setup.

Now we are going to make our Users->login function check the POST username and password against LDAP, and then if valid it will preform the login magic that auth used to do.

Fill your login function with this:

    function login() {
        App::import('Lib', 'ldap');
        if ($this->Session->read('Auth.User')) {
             $this->redirect(array('controller' => 'allocations', 'action' => 'index'));
        } elseif (!empty($this->data)) {
            $ldap = new ldap;
            if ($ldap->auth($this->Auth->data['User']['user'], $this->Auth->data['User']['password'])) {

                $userrow = $this->User->findByUsername($this->data['User']['user']);
                if (!$userrow) {
                    $ldap_info = $ldap->getInfo($this->data['User']['user']);
                    $this->data['User']['username'] = $this->data['User']['user'];
                    $this->data['User']['name'] = $ldap_info['name'];
                    $this->data['User']['group_id'] = 3; //sets the default group
                    $this->add();
                    $userrow = $this->User->findByUsername($this->data['User']['user']);
                }

                $user = $userrow['User'];

                $this->Auth->Session->write($this->Auth->sessionKey, $user);
                $this->Auth->_loggedIn = true;

                $this->Session->setFlash('You are logged in!');
                $this->redirect(array('controller' => 'allocations', 'action' => 'index'));
            } else {
                $this->Session->setFlash(__('Login Failed', true));
            }
        }
    }

To quickly summarize, it first checks to see if the user is logged in, if not, and post data is provided it will check the provided credentials with the LDAP Lib. If valid it then attempts to get the user from the users table, if the user does not exist it creates it with information provided from LDAP and defaults set in the function.

I built this to still work with a users table to allow relationships between other models and users. but it can be used without a users table, just remove the if(!$userrow) statement and the line before it.

NOTE: If you use a users table, you do not need, and should not have, a password column.

That should be it! You should now be using LDAP for user credential validation.


PHPRepo

This is about a piece of software I wrote over a year ago to fit a need I had at the time. It probably will not receive any updates but I have released the source to anyone is free to do as they please with it.

Background

PHPRepo is a PHP CMS for managing Debian package repositories. A while ago I wanted to start my own repository for some of my own packages, so I looked for an easy way to do this. I found none. At the time the only way to run and manage a Debian package repository was through apt at the command line, and since at the time I was learning PHP I decided to write my own software to fill this void. Thus I created PHPRepo. PHPRepo has very minimal requirements and can work alongside an existing repository that is managed with apt.

Installation

Installation is as easy as it gets for a PHP app. There are no databases to configure, as it used the Debian repository files as its database. Simply upload the phprepo files to the root of your web-server and edit the config file with a user name and password you wish to use.

Also, if you want the ability to manage the repository in addition to view it in your web browser then make sure the user on your server that the web-server is running under has read and write permission to the repository files.

Screenshot Tour

My screenshots are for a repository that already has a few packages in it. If you are making a repository from scratch you will not be able to see as much.

PHPrepo main screen

Above is the main screen. You can see a tree list structure of all of your repositories, components, and architectures.

PHPrepo repository list

Above you can see the list of all repositorys on the system.

PHPRepo repository detail

Clicking on a repository brings you to the repository page; shown above. It will list all of the packages in the repository.

PHPrepo search

One very nice feature is the ability to search from a web browser.

PHPrepo add upload

If you choose to use PHPRepo to manager your repository, the above screen will allow you to add/upload packages to your repository. Simply select the file, distribution, and component. If the distribution or component that you want does not exist you can create it. All details about the package such as name, arch, etc are read from the deb file upon upload. Its like magic!

PHPRepo package view

If you click on a package you will be taken to the screen above. This page lets you view the details of the package. You can also manually downland the deb file, or a Maemo .install file. You can also manage the file by deleting its entry in the repository or by deleting the entry and remove the actual file from the server.

PHPRepo Delete file from repo

If you choose to delete a file you will see the above screen asking if you are sure.

PHPrepo Delete entire repository

The above screen is for deleting an entire repository, and all packages associated with it.

Conclusion

As stated before, I made this program over a year ago to fill a void. And I was rather surprised that nothing like this already existed. In any case the program and its source code can be downloaded from its project hosted at Google Code.

PHPRepo at Google Code