SAP container for PEAR::Auth

September 1st, 2006. Tagged: PEAR, SAP

PEAR::Auth is a package that allows you to abstract the user authentication from the main part of your application and not worry about it. What is good about the package is that it comes with different "containers" that allows you to authenticate users against different storages, I mean you can store users data in a database and use the PEAR::DB or PEAR::MDB2 containers, or you can use flat files, IMAP servers, SOAP and what not. And the package is easily extensible. So I played around with creating an SAP container that allows you to check users against your company's SAP system and for example build a section of your Internet (or Extranet) page that is only accessible for people and partners that exist as users in the SAP system.

In order to connect to an SAP system with PHP you need the SAPRFC PHP extension. Get it here. Then you use the function saprfc_open() (more docs here) to establish a connection. You provide some info about the SAP system as well as your username/password. Once connected, a so-called "SSO ticket" is generated for you. This is just a long string, like a session ID. For consecutive connections you can use this SSO ticket instead of providing username/password every time. BTW, SSO stands for Single Sign-On.

Now, with my little SAP container you can benefit from the PEAR and PEAR::Auth infrastructure to do the logins. The way to do an authentication is simple (example stolen in parts from this PEAR manual entry). You pass the connection options (such as hostname). Then, once the user is authenticated, the container retrieves the SSO session ID and sticks into the Auth session data, so that it's reusable for consecutive connections within the same session. If you need to do more with the SAP system, apart from authenticating users, you can get back the updated connection options and just pass them to saprfc_open(). Here's an example:

<?php
// get Auth lib
require_once "Auth.php";
 
// SAP connection options
$options = array (
    'ASHOST'    => 'hostname'
);
 // create Auth object using the SAP container
$a = new Auth("SAP", $options);
 

$a->start();
 
// check
if ($a->checkAuth()) {

 
    // authorised! You can do the protected stuff here
 
    // For example open a connection to the SAP system
    // using the stored authentication data
    $rfc = saprfc_open($a->getAuthData('sap'));
 
    // show sapinfo if you will
    echo '<pre>';
    print_r(saprfc_attributes($rfc));
    echo '</pre>';

}
?>

And here's the actual Auth_Contaner_SAP class, should be placed in a file called SAP.php in your_pear_dir/Auth/Container/

<?php
require_once 'PEAR.php';
require_once 'Auth/Container.php';
/**
 * Performs authentication against an SAP system
 * using the SAPRFC PHP extension.
 *
 * When the option GETSSO2 is TRUE (default)
 * the Single Sign-On (SSO) ticket is retrieved
 * and stored as an Auth attribute called 'sap'
 * in order to be reused for consecutive connections.
 *
 * @author Stoyan Stefanov <ssttoo@gmail.com>
 * @package Auth
 * @see http://saprfc.sourceforge.net/
 */
class Auth_Container_SAP extends Auth_Container {
    /**
     * @var array Default options
     */
    var $options = array(
        'CLIENT'    => '000',
        'LANG'      => 'EN',
        'GETSSO2'   => true,
    );
 
    /**
     * Class constructor. Checks that required options
     * are present and that the SAPRFC extension is loaded
     *
     * Options that can be passed and their defaults:
     * <pre>
     * array(
     *   'ASHOST' => "",
     *   'SYSNR'    => "",
     *   'CLIENT' => "000",
     *   'GWHOST' =>"",
     *   'GWSERV' =>"",
     *   'MSHOST' =>"",
     *   'R3NAME' =>"",
     *   'GROUP'    =>"",
     *   'LANG'     =>"EN",
     *   'TRACE'    =>"",
     *   'GETSSO2'=> true
     * )
     * </pre>
     *
     * @var array array of options.
     */
    function Auth_Container_SAP($options)
    {
        $saprfc_loaded = PEAR::loadExtension('saprfc');
        if (!$saprfc_loaded) {
            return PEAR::raiseError('Cannot use SAP authentication, '
                    .'SAPRFC extension not loaded!');
        }
        if (empty($options['R3NAME']) && empty($options['ASHOST'])) {
            return PEAR::raiseError('R3NAME or ASHOST required for authentication');
        }
        $this->options = array_merge($this->options, $options);
    }

    /**
     * Performs username and password check
     *
     * @var string Username
     * @var string Password
     * @return boolean TRUE on success (valid user), FALSE otherwise
     */        
    function fetchData($username, $password)
    {
        $connection_options = $this->options;
        $connection_options['USER'] = $username;
        $connection_options['PASSWD'] = $password;
        $rfc = saprfc_open($connection_options);
        if (!$rfc) {
            $message = "Couldn't connect to the SAP system.";
            $error = $this->getError();
            if ($error['message']) {
                $message .= ': ' . $error['message'];
            }
            PEAR::raiseError($message, null, null, null, @$erorr['all']);
            return false;
        } else {
            if (!empty($this->options['GETSSO2'])) {
                if ($ticket = @saprfc_get_ticket($rfc)) {
                    $this->options['MYSAPSSO2'] = $ticket;
                    unset($this->options['GETSSO2']);
                    $this->_auth_obj->setAuthData('sap', $this->options);
                } else {
                    PEAR::raiseError("SSO ticket retrieval failed");
                }
            }
            @saprfc_close($rfc);
            return true;
        }

     }
    /**
     * Retrieves the last error from the SAP connection
     * and returns it as an array.
     *
     * @return array Array of error information
     */
    function getError()
    {

        $error = array();
        $sap_error = saprfc_error();
        if (empty($err)) {
            return $error;
        }
        $err = explode("\n", $sap_error);
        foreach ($err AS $line) {
            $item = split(':', $line);
            $error[strtolower(trim($item[0]))] = trim($item[1]);
        }
        $error['all'] = $sap_error;
        return $error;
    }
}
?>

Tell your friends about this post: Facebook, Twitter, Google+

8 Responses

  1. Very nice, and quite a convient method of working with it, so what about when you have 136 SAP systems?

    You know you are also quite welcomed as a blogger on SDN (http://sdn.sap.com), we’d love to have you on board there! Looking forward to meeting you in Las Vegas.

    Craig

  2. 136 SAP systems? Well, you probably know better then me, but I believe you can identify the one you want with all those options. Auth_Container_SAP constructor (and therefore Auth constructor) accepts all the connection options you’d normally pass to saprfc_open()

    I want to post to SDN too, I just don’t know how to start. Maybe I should try with “Hi” ;)

    Looking forward to meeting you in person, and thanks for the link to your site, it’s really nice!

  3. Oh I think we’ll have you posting on SDN before the end of the SDN Day in Vegas :-)

    I’ll also go through a few things on multiple systems and connection – be sure to bring this with you!

  4. phpied.com: SAP container for PEAR::Auth…

  5. [...] I also contributed one new container for the PEAR::Auth package, it allows you to authenticate users against an SAP system in your PHP app. [...]

  6. Hello, very useful information, thanx. Just one question, how can you accomplish SSO with logon ticket between PHP and SAP.

    Thanks again, regards!

  7. Hey JV I don’t remember if I was ever able to do SSO.
    (it’s been a while, took me some time to even remember what SSO stands for ;)

  8. [...] SAP container for PEAR::Auth / Stoyan's phpied.comHello, very useful information, thanx. Just one question, how can you accomplish SSO with logon ticket between PHP and SAP. Thanks again, regards Stoyan Says: February 4th, 2009 at 4:23 pm. Hey JV I don't remember if I was ever able to . [...]

Leave a Reply