univlorraine / limesurvey-cas

CAS Authentication plugin for limesurvey (based on phpCAS)
GNU General Public License v2.0
23 stars 22 forks source link

Some Question When Using #4

Closed rccoder closed 9 years ago

rccoder commented 9 years ago

Hi, I'm very glad to visit this repo.

I download the plugin and installed as README.md.

But some question hit me. Now, If I was create a user which ID is same as CAS ID in front of active this plugin, I can login and logout. But if I was not create user previous, I can't login and there is not create a user auto, the http status is 500.

Could you help me? Thanks

goyome commented 9 years ago

Hi,

Happy to see that this plugin is useful to others. I assume that you add the parameter auth_cas_autocreate_permissions in config array of your config.php :

... // Use the following config variable to set modified optional settings copied from config-defaults.php 'config'=>array( 'auth_cas_autocreate_permissions' => array( 'surveys' => array('create'=>true) ),

// debug: Set this to 1 if you are looking for errors. If you still get no errors after enabling this // then please check your error-logs - either in your hosting provider admin panel or in some /logs directory ...

Where do you get the information about the user ? LDAP or CAS attributes ? If LDAP, did you define a filter to restrict the type of user allowed to create an account automatically ?

It would be helpful to have more information about your configuration to help find the origin of the problem.

rccoder commented 9 years ago

yep, the config of my file is

// Use the following config variable to set modified optional settings copied from config-defaults.php
    'config'=>array(
        'auth_cas_autocreate_permissions' => array (
            'surveys' => array('create' => true)
        ),
        // debug: Set this to 1 if you are looking for errors. If you still get no errors after enabling this
        // then please check your error-logs - either in your hosting provider admin panel or in some /logs directory
        // on your webspace.
        // LimeSurvey developers: Set this to 2 to additionally display STRICT PHP error messages and get full access to standard templates
                'debug'=>0,
                'debugsql'=>0 // Set this to 1 to enanble sql logging, only active when debug = 2
    ),

and for the plugin, AuthCAS.php is is:

<?php

class AuthCAS extends AuthPluginBase
{

    protected $storage = 'DbStorage';
    static protected $description = 'Core: CAS authentication';
    static protected $name = 'CAS';
    protected $settings = array(
        'casAuthServer' => array(
            'type' => 'string',
            'label' => 'The servername of the CAS Server without protocol',
            'default' => 'localhost',
        ),
        'casAuthPort' => array(
            'type' => 'int',
            'label' => 'CAS Server listening Port',
            'default' => 8443,
        ),
        'casAuthUri' => array(
            'type' => 'string',
            'label' => 'Relative uri from CAS Server to cas workingdirectory',
            'default' => '/cas',
        ),
        'autoCreate' => array(
            'type' => 'select',
            'label' => 'Enable automated creation of user from LDAP ?',
            'options' => array("0" => "No, don't create user automatically", "1" => "User creation on the first connection", "2" => "User creation from CAS attributes"),
            'default' => '0',
            'submitonchange' => true
        ),
        'casLoginAttr' => array(
            'type' => 'string',
            'label' => 'CAS attribute for login',
            'default' => 'uid'
        ),
        'casFullnameAttr' => array(
            'type' => 'string',
            'label' => 'CAS attribute for fullname',
            'default' => 'displayName'
        ),
        'server' => array(
            'type' => 'string',
            'label' => 'Ldap server e.g. ldap://ldap.mydomain.com or ldaps://ldap.mydomain.com'
        ),
        'ldapport' => array(
            'type' => 'string',
            'label' => 'Port number (default when omitted is 389)'
        ),
        'ldapversion' => array(
            'type' => 'select',
            'label' => 'LDAP version',
            'options' => array('2' => 'LDAPv2', '3' => 'LDAPv3'),
            'default' => '2',
            'submitonchange' => true
        ),
        'ldapoptreferrals' => array(
            'type' => 'boolean',
            'label' => 'Select true if referrals must be followed (use false for ActiveDirectory)',
            'default' => '0'
        ),
        'ldaptls' => array(
            'type' => 'boolean',
            'label' => 'Check to enable Start-TLS encryption When using LDAPv3',
            'default' => '0'
        ),
        'searchuserattribute' => array(
            'type' => 'string',
            'label' => 'Attribute to compare to the given login can be uid, cn, mail, ...'
        ),
        'usersearchbase' => array(
            'type' => 'string',
            'label' => 'Base DN for the user search operation'
        ),
        'extrauserfilter' => array(
            'type' => 'string',
            'label' => 'Optional extra LDAP filter to be ANDed to the basic (searchuserattribute=username) filter. Don\'t forget the outmost enclosing parentheses'
        ),
        'binddn' => array(
            'type' => 'string',
            'label' => 'Optional DN of the LDAP account used to search for the end-user\'s DN. An anonymous bind is performed if empty.'
        ),
        'bindpwd' => array(
            'type' => 'string',
            'label' => 'Password of the LDAP account used to search for the end-user\'s DN if previoulsy set.'
        )
    );

    public function __construct(PluginManager $manager, $id)
    {
        parent::__construct($manager, $id);

        /**
         * Here you should handle subscribing to the events your plugin will handle
         */
        $this->subscribe('beforeLogin');
        $this->subscribe('newUserSession');
        $this->subscribe('beforeLogout');
    }

    /**
     * Modified getPluginSettings since we have a select box that autosubmits
     * and we only want to show the relevant options.
     *
     * @param boolean $getValues
     * @return array
     */
    public function getPluginSettings($getValues = true)
    {
        $aPluginSettings = parent::getPluginSettings($getValues);
        if ($getValues)
        {
            $ldapver = $aPluginSettings['ldapversion']['current'];
            $autoCreate = $aPluginSettings['autoCreate']['current'];

            // If it is a post request, it could be an autosubmit so read posted
            // value over the saved value
            if (App()->request->isPostRequest)
            {
                $ldapver = App()->request->getPost('ldapversion', $ldapver);
                $aPluginSettings['ldapversion']['current'] = $ldapver;
                $autoCreate = App()->request->getPost('autoCreate', $autoCreate);
                $aPluginSettings['autoCreate']['current'] = $autoCreate;
            }

            if ($autoCreate != 1)
            {
                // Don't create user. Hide unneeded ldap settings
                unset($aPluginSettings['server']);
                unset($aPluginSettings['ldapport']);
                unset($aPluginSettings['ldapversion']);
                unset($aPluginSettings['ldapoptreferrals']);
                unset($aPluginSettings['ldaptls']);
                unset($aPluginSettings['searchuserattribute']);
                unset($aPluginSettings['usersearchbase']);
                unset($aPluginSettings['extrauserfilter']);
                unset($aPluginSettings['binddn']);
                unset($aPluginSettings['bindpwd']);
            } else
            {
                if ($ldapver == '2')
                {
                    unset($aPluginSettings['ldaptls']);
                }
            }
            //不是2的情况下销毁
            if ($autoCreate != 2)
            {
                unset($aPluginSettings['casFullnameAttr']);
                unset($aPluginSettings['casLoginAttr']);
                unset($aPluginSettings['casMailAttr']);
            }
        }

        return $aPluginSettings;
    }

    public function beforeLogin()
    {
        // configure phpCAS
        $cas_host = $this->get('casAuthServer');
        $cas_context = $this->get('casAuthUri');
        $cas_port = (int) $this->get('casAuthPort');

        // import phpCAS lib
        $basedir=dirname(__FILE__);
        Yii::setPathOfAlias('myplugin', $basedir);
        Yii::import('myplugin.third_party.CAS.*');
        require_once('third_party/CAS/CAS.php');
        // Initialize phpCAS
        phpCAS::client(CAS_VERSION_2_0, $cas_host, $cas_port, $cas_context, false);
        // disable SSL validation of the CAS server
        phpCAS::setNoCasServerValidation();
        //force CAS authentication
        phpCAS::forceAuthentication();

        $this->setUsername(phpCAS::getUser());
        $oUser = $this->api->getUserByName($this->getUserName());
        if ($oUser || $this->get('autoCreate') > 0)
        {
            // User authenticated and found. Cas become the authentication system
            $this->getEvent()->set('default', get_class($this));
            $this->setAuthPlugin(); // This plugin handles authentication, halt further execution of auth plugins
        } elseif ($this->get('is_default', null, null))
        {
            // Fall back to another authentication mecanism
            throw new CHttpException(401, 'Wrong credentials for LimeSurvey administration.');
        }
    }

    public function newUserSession()
    {
        // Do nothing if this user is not AuthCAS type
        $identity = $this->getEvent()->get('identity');
        if ($identity->plugin != 'AuthCAS')
        {
            return;
        }

        $sUser = $this->getUserName();

        $oUser = $this->api->getUserByName($sUser);
        if (is_null($oUser))
        {
            //LD
            if ((int) $this->get('autoCreate') === 1)
            {
                // auto-create
                // Get configuration settings:
                $ldapserver = $this->get('server');
                $ldapport = $this->get('ldapport');
                $ldapver = $this->get('ldapversion');
                $ldaptls = $this->get('ldaptls');
                $ldapoptreferrals = $this->get('ldapoptreferrals');
                $searchuserattribute = $this->get('searchuserattribute');
                $extrauserfilter = $this->get('extrauserfilter');
                $usersearchbase = $this->get('usersearchbase');
                $binddn = $this->get('binddn');
                $bindpwd = $this->get('bindpwd');

                $username = $sUser;

                if (empty($ldapport))
                {
                    $ldapport = 389;
                }

                // Try to connect
                $ldapconn = ldap_connect($ldapserver, (int) $ldapport);
                if (false == $ldapconn)
                {
                    $this->setAuthFailure(1, gT('Could not connect to LDAP server.'));
                    return;
                }

                // using LDAP version
                if ($ldapver === null)
                {
                    // If the version hasn't been set, default = 2
                    $ldapver = 2;
                }
                ldap_set_option($ldapconn, LDAP_OPT_PROTOCOL_VERSION, $ldapver);
                ldap_set_option($ldapconn, LDAP_OPT_REFERRALS, $ldapoptreferrals);

                if (!empty($ldaptls) && $ldaptls == '1' && $ldapver == 3 && preg_match("/^ldaps:\/\//", $ldapserver) == 0)
                {
                    // starting TLS secure layer
                    if (!ldap_start_tls($ldapconn))
                    {
                        $this->setAuthFailure(100, ldap_error($ldapconn));
                        ldap_close($ldapconn); // all done? close connection
                        return;
                    }
                }

                // We first do a LDAP search from the username given
                // to find the userDN and then we procced to the bind operation
                if (empty($binddn))
                {
                    // There is no account defined to do the LDAP search,
                    // let's use anonymous bind instead
                    $ldapbindsearch = @ldap_bind($ldapconn);
                } else
                {
                    // An account is defined to do the LDAP search, let's use it
                    $ldapbindsearch = @ldap_bind($ldapconn, $binddn, $bindpwd);
                }
                if (!$ldapbindsearch)
                {
                    $this->setAuthFailure(100, ldap_error($ldapconn));
                    ldap_close($ldapconn); // all done? close connection
                    return;
                }
                // Now prepare the search filter
                if ($extrauserfilter != "")
                {
                    $usersearchfilter = "(&($searchuserattribute=$username)$extrauserfilter)";
                } else
                {
                    $usersearchfilter = "($searchuserattribute=$username)";
                }
                // Search for the user
                $dnsearchres = ldap_search($ldapconn, $usersearchbase, $usersearchfilter, array($searchuserattribute, "displayname", "mail"));
                $rescount = ldap_count_entries($ldapconn, $dnsearchres);
                if ($rescount == 1)
                {
                    $userentry = ldap_get_entries($ldapconn, $dnsearchres);
                    $userdn = $userentry[0]["dn"];

                    $oUser = new User;
                    $oUser->users_name = $username;
                    $oUser->password = hash('sha256', createPassword());
                    $oUser->full_name = $userentry[0]["displayname"][0];
                    $oUser->parent_id = 1;
                    $oUser->email = $userentry[0]["mail"][0];

                    if ($oUser->save())
                    {
                        $permission = new Permission;
                        $permission->setPermissions($oUser->uid, 0, 'global', $this->api->getConfigKey('auth_cas_autocreate_permissions'), true);

                        // read again user from newly created entry
                        $this->setAuthSuccess($oUser);
                        return;
                    } else
                    {
                        $this->setAuthFailure(self::ERROR_USERNAME_INVALID);
                        throw new CHttpException(401, 'User not saved : ' . $userentry[0]["mail"][0] . " / " . $userentry[0]["displayName"]);
                        return;
                    }
                } else
                {
                    // if no entry or more than one entry returned
                    // then deny authentication
                    $this->setAuthFailure(100, ldap_error($ldapconn));
                    ldap_close($ldapconn); // all done? close connection
                    throw new CHttpException(401, 'No authorized user found for login "' . $username . '"');
                    return;
                }
            }
            //PHPCAS auto
            else if((int) $this->get('autoCreate') === 2)
            {
               try {
                    // import phpCAS lib
                    $basedir=dirname(__FILE__);
                    Yii::setPathOfAlias('myplugin', $basedir);
                    Yii::import('myplugin.third_party.CAS.*');
                    require_once('CAS.php');
                    $cas_host = $this->get('casAuthServer');
                    $cas_context = $this->get('casAuthUri');
                    $cas_port = (int) $this->get('casAuthPort');
                    // Initialize phpCAS
                    //phpCAS::client($cas_version, $cas_host, $cas_port, $cas_context, false);
                    // disable SSL validation of the CAS server
                    //phpCAS::setNoCasServerValidation();
                    $cas_fullname = phpCAS::getAttribute($this->get('casFullnameAttr'));
                    $cas_login = phpCAS::getAttribute($this->get('casLoginAttr'));
                }
                catch (Exception $e)
                {
                    $this->setAuthFailure(self::ERROR_USERNAME_INVALID);
                    throw new CHttpException(401, 'Cas attributes not found for "' . $username . '"');
                    return;
                }
                $oUser = new User;
                $oUser->users_name = phpCAS::getUser();
                $oUser->password = hash('sha256', createPassword());
                $oUser->full_name = $cas_fullname;
                $oUser->parent_id = 1;
                $oUser->email = 'example'.$cas_fullname.'@example.com';
                if ($oUser->save())
                {
                    if ($this->api->getConfigKey('auth_cas_autocreate_permissions'))
                    {
                        $permission = new Permission;
                        $permission->setPermissions($oUser->uid, 0, 'global', $this->api->getConfigKey('auth_cas_autocreate_permissions'), true);
                    }
                    $this->setAuthSuccess($oUser);
                    return;
                } else
                {
                    $this->setAuthFailure(self::ERROR_USERNAME_INVALID);
                    throw new CHttpException(401, 'User not saved : ' . $sUser .' / '  . $cas_fullname);
                    return;
                }
            }
        } else
        {
            $this->setAuthSuccess($oUser);
            return;
        }
    }

    public function beforeLogout()
    {
        // configure phpCAS
        $cas_host = $this->get('casAuthServer');
        $cas_context = $this->get('casAuthUri');
        $cas_port = (int) $this->get('casAuthPort');
        // import phpCAS lib
        $basedir=dirname(__FILE__);
        Yii::setPathOfAlias('myplugin', $basedir);
        Yii::import('myplugin.third_party.CAS.*');
        require_once('third_party/CAS/CAS.php');
        // Initialize phpCAS
        phpCAS::client(CAS_VERSION_2_0, $cas_host, $cas_port, $cas_context, false);
        // disable SSL validation of the CAS server
        phpCAS::setNoCasServerValidation();
        // logout from CAS
        phpCAS::logout();
    }

}

because my cas server can't give me the email ,so I drop the email

And I get information from CAS attributes

the information is :

user:1234567
*******************
attributes:
array(5) {
  ["containerId"]=>
  string(3) "bks"
  ["binduserlist"]=>
  string(0) ""
  ["cn"]=>
  string(9) "张三"
  ["eduPersonOrgDN"]=>
  string(27) "计算机科学与工程系"
  ["uid"]=>
  string(10) "1234567"
}
goyome commented 9 years ago

I just made a commit today that you didn't apply on your file. Try to replace these lines (3 modifications) :

                Yii::import('myplugin.third_party.CAS.*');
                require_once('CAS.php');

by

                Yii::import('myplugin.third_party.CAS.CAS',true);

About the email, I assume that this shouldn't be a problem but I can't test it here. You may have problem depending on the user's cn. You may try to use the uid instead :

            $oUser->email = 'example'.$cas_login.'@example.com';
rccoder commented 9 years ago

I used you new plugin but there is a new Error.

Error 401

User not saved...

How to solve it? May be it caused by System Permissions? My server is ubuntu server

goyome commented 9 years ago

As you can see in the code, this error is generated by the plugin when the user can't be saved

   throw new CHttpException(401, 'User not saved : ' . $sUser .' / '  . $cas_fullname);

The truly odd thing in your error message is that you don't get : and / in it. Even if your id and username are empty you should have these symbols. Could you provide the diff between your file and the last version of this repo to see why this happen ?

rccoder commented 9 years ago

Sorroy, I am not provide all of the message

The error is :

Error 401

User not saved : twtest / twtest / 团委测试

The above error occurred when the Web server was processing your request. 

If you think this is a server error, please contact the webmaster. 

Thank you. 

2015-08-27 18:24:55 

What I used is you new plugin

As you said, The user can't be saved

rccoder commented 9 years ago

Oh, Thanks

The bug caused By email. I have solved it! Thank you very much