Roundcube Community Forum

Third Party Contributions => API Based Plugins => Topic started by: dchambel on October 26, 2012, 10:08:43 AM

Title: plugin Cas_Authn v0.8
Post by: dchambel on October 26, 2012, 10:08:43 AM
Hello
I have modified code of cas_authn plugin to work with phpcas v1.3.1 et rc v0.8.2
You need pam_cas on your imap server, and ssl enabled for your roundcube webserver.

Have fun


<?php

/**
 * CAS Authentication
 *
 * This plugin augments the RoundCube login page with the ability to authenticate
 * to a CAS server, which enables logging into RoundCube with identities
 * authenticated by the CAS server and acts as a CAS proxy to relay authenticated
 * credentials to the IMAP backend.
 * 
 * The vast majority of this plugin was written by Alex Li. David Warden modified
 * it to be an optional authentication method that can work with stock Roundcube
 * version 0.7.
 *
 * Version 0.8.0 add support of phpCAS v1.3.1 and roundcube v0.8.2
 * You need pam_cas on your imap server, and ssl enabled for your roundcube webserver.
 * 
 * @version 0.8.0
 * @author Dominique Chambelant ([email protected])
 * @author David Warden ([email protected])
 * @author Alex Li ([email protected])
 * 
 */

class cas_authn extends rcube_plugin {
    
    private 
$cas_inited;
    
    
/**
     * Initialize plugin
     *
     */
    
function init() {
        
// initialize plugin fields
        
$this->cas_inited false;
        
        
// load plugin configuration
        
$this->load_config();
        
        
// add application hooks
        
$this->add_hook('startup', array($this'startup'));
        
$this->add_hook('imap_connect', array($this'imap_connect'));
        
$this->add_hook('smtp_connect', array($this'smtp_connect'));
        
$this->add_hook('template_object_loginform', array($this'add_cas_login_html'));
    }

    
/**
     * Handle plugin-specific actions
     * These actions are handled at the startup hook rather than registered as
     * custom actions because the user session does not necessarily exist when
     * these actions need to be handled.
     *
     * @param array $args arguments from rcmail
     * @return array modified arguments
     */
    
function startup($args) {
        
// intercept PGT callback action from CAS server
        
if ($args['action'] == 'pgtcallback') {
            
// initialize CAS client
            
$this->cas_init();
            
            
// retrieve and store PGT if present
            
phpCAS::isAuthenticated();

            
// end script - once the PGT is stored we don't need to do anything else.
            
exit;
        }
        
        
// intercept logout action
        // We unfortunately cannot use the logout_after plugin hook because it is
        // executed after session is destroyed
        
else if ($args['task'] == 'logout') {
            
// initialize CAS client
            
$this->cas_init();

            
// Redirect to CAS logout action if user is logged in to CAS.
            // Also, do the normal Roundcube logout actions.
            
if(phpCAS::isSessionAuthenticated()) {
                
$RCMAIL rcmail::get_instance();
                
$RCMAIL->logout_actions();
                
$RCMAIL->kill_session();
                
$RCMAIL->plugins->exec_hook('logout_after'$userdata);
                
phpCAS::logout();
                exit;
            }

        }

        
// intercept CAS login
        
else if ($args['action'] == 'caslogin') {
            
// initialize CAS client
            
$this->cas_init();
            
            
// Look for _url GET variable and update FixedServiceURL if present to enable deep linking.
            
$query = array();
            if (
$url get_input_value('_url'RCUBE_INPUT_GET)) {
                
phpCAS::setFixedServiceURL($this->generate_url(array('action' => 'caslogin''_url' => $url)));
                
parse_str($url$query);
            }

            
// Force the user to log in to CAS, using a redirect if necessary.
            
phpCAS::forceAuthentication();

            
// If control reaches this point, user is authenticated to CAS.
            
$user phpCAS::getUser();
            
$pass '';
            
// retrieve credentials, either a Proxy Ticket or 'masteruser' password
            
$cfg rcmail::get_instance()->config->all();
            if (
$cfg['cas_proxy']) {
                
$_SESSION['cas_pt'][php_uname('n')] = phpCAS::retrievePT($cfg['cas_imap_name'], $err_code$output);
                
$pass $_SESSION['cas_pt'][php_uname('n')];
            }
            else {
                
$pass $cfg['cas_imap_password'];
            }
    
            
// Do Roundcube login actions
            
$RCMAIL rcmail::get_instance();
            
$RCMAIL->login($user$pass$RCMAIL->autoselect_host());
            
$RCMAIL->session->remove('temp');
            
$RCMAIL->session->regenerate_id(false);
            
$RCMAIL->session->set_auth_cookie();
     
            
// log successful login
            
rcmail_log_login();
         
            
// allow plugins to control the redirect url after login success
            
$redir $RCMAIL->plugins->exec_hook('login_after'$query + array('_task' => 'mail'));
            unset(
$redir['abort'], $redir['_err']);
         
            
// send redirect, otherwise control will reach the mail display and fail because the 
            // IMAP session was already started by $RCMAIL->login()
            
global $OUTPUT;
            
$OUTPUT->redirect($redir);
        }

        return 
$args;
    }
    
    
/**
     * Inject IMAP authentication credentials
     * If you are using this plugin in proxy mode, this will set the password 
     * to be used in RCMAIL->imap_connect to a Proxy Ticket for cas_imap_name.
     * If you are not using this plugin in proxy mode, it will do nothing. 
     * If you are using normal authentication, it will do nothing. 
     *
     * @param array $args arguments from rcmail
     * @return array modified arguments
     */
    
function imap_connect($args) {
        
// retrieve configuration
        
$cfg rcmail::get_instance()->config->all();
        
        
// RoundCube is acting as CAS proxy
        
if ($cfg['cas_proxy']) {
            
// a proxy ticket has been retrieved, the IMAP server caches proxy tickets, and this is the first connection attempt
            
if ($_SESSION['cas_pt'][php_uname('n')] && $cfg['cas_imap_caching'] && $args['attempt'] == 1) {
                
// use existing proxy ticket in session
                
$args['pass'] = $_SESSION['cas_pt'][php_uname('n')];
            }

            
// no proxy tickets have been retrieved, the IMAP server doesn't cache proxy tickets, or the first connection attempt has failed
            
else {
                
// initialize CAS client
                
$this->cas_init();

                
// if CAS session exists, use that.
                // retrieve a new proxy ticket and store it in session
                
if (phpCAS::isSessionAuthenticated()) {
                    
$_SESSION['cas_pt'][php_uname('n')] = phpCAS::retrievePT($cfg['cas_imap_name'], $err_code$output);
                    
$args['pass'] = $_SESSION['cas_pt'][php_uname('n')];
                }
            }
            
            
// enable retry on the first connection attempt only
            
if ($args['attempt'] <= 1) {
                
$args['retry'] = true;
            }
        }
        
        return 
$args;
    }
 
    
/**
     * Inject SMTP authentication credentials
     * If you are using this plugin in proxy mode, this will set the password 
     * to be used in RCMAIL->smtp->connect to a new Proxy Ticket for cas_smtp_name.
     * If you are not using this plugin in proxy mode, it will do nothing. 
     * If you are using normal authentication, it will do nothing. 
     *
     * @param array $args arguments from rcmail
     * @return array modified arguments
     */
    
function smtp_connect($args) {
        
// retrieve configuration
        
$cfg rcmail::get_instance()->config->all();
        
        
// RoundCube is acting as CAS proxy and performing SMTP authn
        
if ($cfg['cas_proxy'] && $args['smtp_user'] && $args['smtp_pass']) {
            
// initialize CAS client
            
$this->cas_init();

            
// retrieve a new proxy ticket and use it as SMTP password
            // Without forceAuthentication() then retrievePT() fails.
            
if (phpCAS::isSessionAuthenticated()) {
                
phpCAS::forceAuthentication();
                
$args['smtp_pass'] = phpCAS::retrievePT($cfg['cas_smtp_name'], $err_code$output);
            }
        }
            
        return 
$args;
    }

    
/**
    * Prepend link to CAS login above the Roundcube login form if the user would like to
    * login with CAS.
    */
    
function add_cas_login_html($args) {
        
$RCMAIL rcmail::get_instance();
        
$this->add_texts('localization');
        
// retrieve configuration
        
$cfg rcmail::get_instance()->config->all();
    
        
// Force CAS authn?
if($cfg["cas_force"]) {
            global 
$OUTPUT;
            
$OUTPUT->redirect(array('action' => 'caslogin'));
        }

        
$caslogin_content html::div(array(
                                
'style' => 'border-bottom: 1px dotted #000; text-align: center; padding-bottom: 1em; margin-bottom: 1em;'),
                                
html::a(array(
                                    
'href' => $this->generate_url(array('action' => 'caslogin')),
                                    
'title' => $this->gettext('casloginbutton')),
                                    
$this->gettext('casloginbutton')
                                )
                            );
        
$args['content'] = $caslogin_content $args['content'];

        return 
$args;
    }

    
/**
     * Initialize CAS client
     * 
     */
    
private function cas_init() {
        if (!
$this->cas_inited) {
            
// retrieve configuration
            
$cfg rcmail::get_instance()->config->all();

            
// include phpCAS
            
require_once('CAS.php');
            
            
// Uncomment the following line for phpCAS call tracing, helpful for debugging.
            
if ($cfg['cas_debug']) {
                
phpCAS::setDebug($cfg['cas_debug_file']);
            }

            
// initialize CAS client
            
if ($cfg['cas_proxy']) {
                
phpCAS::proxy(CAS_VERSION_2_0$cfg['cas_hostname'], $cfg['cas_port'], $cfg['cas_uri'], false);

                
// set URL for PGT callback
                
phpCAS::setFixedCallbackURL($this->generate_url(array('action' => 'pgtcallback')));
                
                
// set PGT storage                
                
phpCAS::setPGTStorageFile($cfg['cas_pgt_dir']);
            }
            else {
                
phpCAS::client(CAS_VERSION_2_0$cfg['cas_hostname'], $cfg['cas_port'], $cfg['cas_uri'], false);
            }

            
// set service URL for authorization with CAS server
            
phpCAS::setFixedServiceURL($this->generate_url(array('action' => 'caslogin'), 'on'));
            
// set SSL validation for the CAS server
            
if ($cfg['cas_validation'] == 'self') {
                
phpCAS::setCasServerCert($cfg['cas_cert']);
            }
            else if (
$cfg['cas_validation'] == 'ca') {
                
phpCAS::setCasServerCACert($cfg['cas_cert']);
            }
            else {
                
phpCAS::setNoCasServerValidation();
            }

            
// set login and logout URLs of the CAS server
            
phpCAS::setServerLoginURL($cfg['cas_login_url']);
            
phpCAS::setServerLogoutURL($cfg['cas_logout_url']);

            
$this->cas_inited true;
        }
phpCAS::isAuthenticated();
    }
    
    
/**
     * Build full URLs to this instance of RoundCube for use with CAS servers
     * 
     * @param array $params url parameters as key-value pairs
     * @return string full Roundcube URL
     */
    
private function generate_url($params$ssl 'off') {
        
$s = ($_SERVER['HTTPS'] == 'on') ? 's' '';
        if (
$ssl == 'on') {
            
$s 's';
        }
        
$protocol $this->strleft(strtolower($_SERVER['SERVER_PROTOCOL']), '/') . $s;
        
$port = (($_SERVER['SERVER_PORT'] == '80' && $_SERVER['HTTPS'] != 'on') ||
                 (
$_SERVER['SERVER_PORT'] == '443' && $_SERVER['HTTPS'] == 'on')) ? 
                
'' : (':' .$_SERVER['SERVER_PORT']);
        
$path $this->strleft($_SERVER['REQUEST_URI'], '?');
        
$parsed_params '';
        
$delm '?';
        foreach (
array_reverse($params) as $key => $val) {
            if (!empty(
$val)) {
                
$parsed_key $key[0] == '_' $key '_' $key;
                
$parsed_params .= $delm urlencode($parsed_key) . '=' urlencode($val);
                
$delm '&';
            }
        }
        return 
$protocol '://' $_SERVER['SERVER_NAME'] . $port $path $parsed_params;
    }

    private function 
strleft($s1$s2) {
        
$length strpos($s1$s2);
        if (
$length) {
            return 
substr($s10$length);
        }
        else {
            return 
$s1;
        }
    }
}

?>



And its config file

<?php
/**
 * CAS Authentication configuration file
 *
 */
// Active phpCAS debug log (default: false)
$rcmail_config['cas_debug'] = false;

// phpCAS debug file
$rcmail_config['cas_debug_file'] = '/tmp/cas_debug.log';

// whether to force all users to use CAS to authenticate. If set to true,
//     all users trying to load the login form will be redirected to
//     the CAS login URL. This means nobody will ever see the RC login page.
$rcmail_config['cas_force'] = false;

// whether to act as a CAS proxy. If set to true, a proxy ticket will be
//     retrieved from the CAS server to be used as password for logging into
//     the IMAP server. This is the preferred method of authenticating
//     to the IMAP backend.
//     If set to false, the IMAP password specified below will be used.
$rcmail_config['cas_proxy'] = true;

// directory where PGTs will be temporarily stored. Will only be used if
//     cas_proxy is set to true.
$rcmail_config['cas_pgt_dir'] = '/tmp';

// name of the IMAP service. Will only be used if cas_proxy is set to true.
//     This service name must be authorized to be used with the CAS server.
$rcmail_config['cas_imap_name'] = 'name_of_imap_service';

// name of the SMTP service. Will only be used if cas_proxy is set to true.
//     This service name must be authorized to be used with the CAS server.
$rcmail_config['cas_smtp_name'] = 'name_of_imap_service';

// whether the IMAP server caches proxy tickets it has received for subsequent
//    requests. Will only be used if cas_proxy is set to true. If set to true,
//    proxy tickets will be reused to connect to the IMAP server until an IMAP
//    connection fails, after which a new proxy ticket will be retrieved. If
//    set to false, a new proxy ticket will be retrieved before each IMAP
//    request. Setting this to true and enabling caching on the IMAP server
//    significantly reduces the number of requests made to the CAS server.
$rcmail_config['cas_imap_caching'] = true;

// password for logging into the IMAP server. Will only be used if cas_proxy
//     is set to false. The IMAP backend must accept this password for all
//     authorized users.
$rcmail_config['cas_imap_password'] = '';

// CAS server host name.
$rcmail_config['cas_hostname'] = 'address.of.cas.server';

// CAS server port number.
$rcmail_config['cas_port'] = 443;

// CAS service URI on the CAS server.
$rcmail_config['cas_uri'] = '';

// CAS server SSL validation: 'self' for self-signed certificate, 'ca' for
//     certificate from a CA, empty for no SSL validation.
$rcmail_config['cas_validation'] = 'ca';

// CAS server certificate in PEM format, used when CAS validation is set to
//     'self' or 'ca'.
$rcmail_config['cas_cert'] = '/path/to/cert/file';

// CAS service login URL.
$rcmail_config['cas_login_url'] = '';

// CAS service logout URL.
$rcmail_config['cas_logout_url'] = '';
?>