PHP Automation issues with handling of Errors and Authentication checking

Dear community,

While using the Nuxeo PHP Automation Client I ran into some issues.

  1. When not using PHP Composer, loading and using the Automation Client is troublesome. *[SOLVED] by implementing the PSR-0 loader file as listed below. *

  2. I was wondering how to test if a login was successful or not. I could find no description in blogs, documentation or example code (I also checked clients in other languages) how to do this. See my various attempts below. Either you get an error or regardless of the correctness of credentials you get the a similar object returned. QUESTION : how can I check this please?

  3. client versus webbrowser issue: At first I assumed calling a getSession($user,$pass) would suffice, but that seemed not to be the case, so I issued: $answer = $session->newRequest(“User.Get”) This request would match with the commandline call: lynx http://localhost:8080/nuxeo/site/automation/User.Get (any webbrowser locally or remotely will do). The webbrowser will provide a JSON object, the Automation Client provides an error. QUESTION: why? Are properties needed, why, which ones?

  4. error handling in general: QUESTION: how are errors handled? Can I tackle them from code? I see no exceptions being raised, or status calls being returned, or status variables in objects. The subject itself seems also not to be documented or being demonstrated in examples. Am I missing something here?

autoloader_no_composer.php

<?php
  /*
   *  PSR-0 loader
   *  auto_loader function when not using Composer
   *  see: http://stackoverflow.com/questions/22388647/how-to-use-a-php-library-with-namespacing-without-composer-as-dependency
   */
  function __autoload($className)
  {
    $className = ltrim($className, '\\');
    $fileName  = '';
    $namespace = '';
    if ($lastNsPos = strripos($className, '\\')) {
      $namespace = substr($className, 0, $lastNsPos);
      $className = substr($className, $lastNsPos + 1);
      $fileName  = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
    }
    $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
    // $fileName .= $className . '.php'; //sometimes you need a custom structure
    //require_once "library/class.php"; //or include a class manually
//echo "Attempting to load ".$fileName.PHP_EOL;
?>

test.php :

<?php

/*
 *
 * https://doc.nuxeo.com/display/NXDOC/PHP+Automation+Client
 *
 * http://nuxeo.github.io/api-playground/#/commands/User.Get
 * http://explorer.nuxeo.com/nuxeo/site/distribution/Nuxeo%20DM-7.10/viewOperation/User.Get
 *
 * curl -X POST 'http://demo.nuxeo.com/nuxeo/site/automation/User.Get' -H 'Nuxeo-Transaction-Timeout: 3' -H 'X-NXproperties: *' -H 'X-NXRepository: default' -H 'X-NXVoidOperation: false' -d '{"params":{},"context":{}}'
 * lynx http://localhost:8080/nuxeo/site/automation/User.Get
 *
 */

require_once "autoloader_no_composer.php";

require_once 'Log.php';

$logdebug = true;

$logger = &Log::singleton('console', '',
                          'PHP',
                          array('buffering' => true),
                          PEAR_LOG_DEBUG);

use Nuxeo\Automation\Client;

function testconnection($user, $pass) {
    $nuxurl = 'http://localhost:8080/nuxeo/site/automation' ;
    $nuxusr = $user ; // prevent SQL injection somehow !!!
    $nuxpwd = $pass ; // idem

    global $logdebug,$logger;

    if ($logdebug) {
        $logger->log("login attempt with credentials: ".$nuxusr." | ".$nuxpwd );
    }

    try {
        $client = new \Nuxeo\Automation\Client\NuxeoPhpAutomationClient($nuxurl);
    }
    catch (Exception $e) {
        //echo $e->getMessage(), "\n";
        //$logger->log("Nuxeo REST error: ".$e->getMessage());
    }

    $session = $client->getSession($nuxusr, $nuxpwd);

    /*
     *  detect if the login was successful or not !
     *  yes : start session
     *  no  : offer try again or offer to create new account
     */

    $answer = $session->newRequest("User.Get")

                        ->sendRequest();

//                      ->set('input', 'void')
    //print_r($answer);

    # http 200 : 200 Ok
    # http 401 : 401 Unauthorized (RFC 7235)

    //if (!isset($answer)) {
    //if ($answer != 200) {

    //if (empty($session)) {
    if (!isset($_SERVER['PHP_AUTH_USER'])) {
    //$statuscode = $session->getStatusCode();
    //if ($statuscode != 200) {
        $success = false ;
        if ($logdebug) {
            $logger->log("Nuxeo REST login failed due to wrong credentials");
            //print "Nuxeo REST login failed due to wrong credentials".PHP_EOL;
            //$logger->log("Session: ".$session);
            //print_r($session);
        }
    }
    else
    {
        $success = true ;
        if ($logdebug) {
            //$logger->log("Nuxeo REST login successful");
            print "Nuxeo REST login successful".PHP_EOL ;
            //$logger->log("Session: ".$session);
            //print_r($session);
        }
    }
    return($success);
}

function amisuccessful($status) {
    if ($status) {
        print "Connection successful";
    }
    else
    {
        print "Connection failed";
    }
}

amisuccessful(testconnection("Administrator", "Administrator"));
amisuccessful(testconnection("jdfsfds", "dsfdsfskdhfk"));

?>
0 votes

6 answers

4455 views

ANSWER

Forgot to mention: The code should be executed from commandline. Ie: php test.php
04/11/2016



Hi,

Thanks for sharing.

About your 500 error on /nuxeo/site/automation/login, make sure you perform a POST as it's an automation command.

I had a 500 error too when performing a GET.

0 votes



Unfortunately the /login REST API endpoint seems not to be available although it is recommended in this thread and in the documentation / blogs. I checked this on my local (7.10) Nuxeo server as well as on the API playground server on the net. After some research I found a working alternative and used that in my code. Now authentication works fine. Although the code is still in beta/POC state I found it only to be fair to share it with you. So:

<?php

/*
 *
 * https://doc.nuxeo.com/display/NXDOC/PHP+Automation+Client
 *
 * http://nuxeo.github.io/api-playground/#/commands/User.Get
 * http://explorer.nuxeo.com/nuxeo/site/distribution/Nuxeo%20DM-7.10/viewOper
ation/User.Get
 *
 * curl -X POST 'http://demo.nuxeo.com/nuxeo/site/automation/User.Get' -H 'Nu
xeo-Transaction-Timeout: 3' -H 'X-NXproperties: *' -H 'X-NXRepository: defaul
t' -H 'X-NXVoidOperation: false' -d '{"params":{},"context":{}}'
 * lynx http://localhost:8080/nuxeo/site/automation/User.Get
 *

 * lynx http://localhost:8080/nuxeo/site/automation/login <--- HTTP 500

 */

require_once "autoloader_no_composer.php";

use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\FirePHPHandler;

use Guzzle\Http\Client as GuzzleClient;
use Guzzle\Plugin\Cookie\Cookie;
use Guzzle\Plugin\Cookie\CookiePlugin;
use Guzzle\Plugin\Cookie\CookieJar\ArrayCookieJar;

$logdebug = true;

use Nuxeo\Automation\Client;
er->pushHandler(new StreamHandler('php://stdout', Logger::DEBUG));
//$logger->pushHandler(new StreamHandler(__DIR__.'nuxeophp.log', Logger::DEBU
G));
$logger->pushHandler(new FirePHPHandler());

function whatisavailable($url, $user, $pass) {

    global $logdebug,$logger;

    $nsClient = new Guzzle\Http\Client();
    $request = $nsClient->get($url, array(), array(
        'auth' => array($user, $pass, 'Basic')));
    $response = "";

    try {
        $response = $request->send();
    }

    catch (Guzzle\Http\Exception\ClientErrorResponseException $e) {

            $req = $e->getRequest();
            $resp =$e->getResponse();
            $logger->addError("PHP-Nuxeo ClientErrorResponse : ".$req." ".$resp);
        }
        catch (Guzzle\Http\Exception\ServerErrorResponseException $e) {

            $req = $e->getRequest();
            $resp =$e->getResponse();
            $logger->addError("PHP-Nuxeo ServerErrorResponse : ".$req." ".$resp);
        }
        catch (Guzzle\Http\Exception\BadResponseException $e) {

            $req = $e->getRequest();
            $resp =$e->getResponse();
            $logger->addError("PHP-Nuxeo BadErrorResponse : ".$req." ".$resp);
        }
        catch( Exception $e){
            echo "AGH!";
            $logger->addError("PHP-Nuxeo unknown error : ".$e->getMessage());
        }
    if ($logdebug) {
        $json = json_encode($response, JSON_PRETTY_PRINT) ;
        //print $json;
        $logger->AddInfo('Availability response: '.$json);
    }
}

function testconnection($user, $pass) {

    global $logdebug,$logger;

    $nuxurl = 'http://localhost:8080/nuxeo/site/automation' ;
    $nuxusr = $user ; // prevent SQL injection somehow !!!
    $nuxpwd = $pass ; // idem

    /*
     * see:
     *   https://doc.nuxeo.com/display/NXDOC/Command+Endpoint
     *   http://stackoverflow.com/questions/30970736/how-do-i-do-http-basic-authentication-using-guzzle
     */
    //$url = $nuxurl."/login" ;
    $url = $nuxurl."/Auth.LoginAs" ;
//    if ($logdebug) {echo "\n" . $url ."\n";}
    //$nsClient = new GuzzleClient($nuxurl."/login"); // only the 'login' end point is required
    $nsClient = new Guzzle\Http\Client();

    //$request = $nsClient->setAuth($nuxuser, $nuxpwd);
    $request = $nsClient->get($url, array(), array(
        'auth' => array($nuxusr, $nuxpwd, 'Basic')));
    try {
        $response = $request->send();
    }

        // see: http://stackoverflow.com/questions/17658283/catching-exceptions-from-guzzle

    catch (Guzzle\Http\Exception\ClientErrorResponseException $e) {

            $req = $e->getRequest();
            $resp =$e->getResponse();
            $sc = $e->getResponse()->getStatusCode();
//echo "\n SC=".$sc."\n";
            if ($sc != 401) {
                $logger->addError("PHP-Nuxeo ClientErrorResponse : ".$req." ".$resp);
            }
            else {
                $logger->AddInfo("PHP-Nuxeo 401 : Authorisation failed.");
            }
        }
        catch (Guzzle\Http\Exception\ServerErrorResponseException $e) {

            $req = $e->getRequest();
            $resp =$e->getResponse();
            $logger->addError("PHP-Nuxeo ServerErrorResponse : ".$req." ".$resp);
        }
        catch (Guzzle\Http\Exception\BadResponseException $e) {

            $req = $e->getRequest();
            $resp =$e->getResponse();
            $logger->addError("PHP-Nuxeo BadErrorResponse : ".$req." ".$resp);
        }
        catch( Exception $e){
            echo "AGH!";
            $logger->addError("PHP-Nuxeo unknown error : ".$e->getMessage());
        }


    if ( (!empty($response)) && ($logdebug) ) {
        $logger->AddInfo('Auth response: '.$response);
    }

    if (!empty($response)) {
        $statuscode = $response->getStatusCode();
        $success = ($statuscode === 200) ;
        # http 200 : 200 Ok
        # http 401 : 401 Unauthorized (RFC 7235)
    }
    else {
        $success = false ;
    }
    return($success);
}

function amisuccessful($status) {
    if ($status) {
        echo "\n" . "Connection successful" . "\n" ;
    }
    else
    {
        echo "\n" . "Connection failed" . "\n" ;
    }
}

whatisavailable("http://localhost:8080/nuxeo/site/automation", "Administrator", "Administrator") ;
amisuccessful(testconnection("Administrator", "Administrator"));
amisuccessful(testconnection("jdfsfds", "dsfdsfskdhfk"));

?>
0 votes



The initial value of $isauthenticated was set to True, next my code did set a variable with a name close to it (due to the typo) to False followed by a check of the value. Messy. Indeed I am after an initial session authentication check for a webfrontend which will, once granted access, allow users to get documents from Nuxeo using the REST API. So I will have a go with Guzzle as suggested. Thanks for the 401 session clarification. It is still strange that you and I get a different result from the code. I assume it might have to do with the fact that I am using an ancient version of Guzzle. (the one that comes with Debian Jessie).

0 votes



Hi, For the record this client is an API client to the Nuxeo Automation REST Api, the NuxeoSession object is used to store the credentials that shall be given as the Authorization header for each call to the API.

That's the reason why you can't know if the credentials are correct until you perform a request to the API and get a 401.

If what you're trying to achieve is granting access to a frontend service by authenticating the user, maybe you could use the /nuxeo/site/automation/login as described in the Nuxeo Automation REST Api documentation (note: write a simple call with GuzzleHTTP, the API client is meant for retrieving documents and will fail for responses with an empty body ATM)

About your code, I just ran it successfully (you have a typo on `$isauthenticted = false ;` BTW) but the message indicates

login attempt with credentials: jdfsfds | dsfdsfsk

So, I think the problem comes from you end.

0 votes



Thanks a lot! The new code is a major improvement. However when playing around with the new code I ran into some things again. To me it looks a bit weird to see that being successful or not seems to depend not on the initialization of a session, but on the call made after it. QUESTION: Or is this incorrect? As I played around with the code I decided to use User.Get instead of Docyment.Query as the request to check authentication.

    try {
        $client = new \Nuxeo\Automation\Client\NuxeoPhpAutomationClient($nuxurl);
    }
    catch (Exception $e) {
        //echo $e->getMessage(), "\n";
        $logger->log("Nuxeo REST error: ".$e->getMessage());
    }

    $session = $client->getSession($nuxusr, $nuxpwd);

    /*
     *  detect if the login was successful or not !
     *  yes : start session
     *  no  : offer try again or offer to create new account
     */
    try {
        $answer = $session->newRequest("User.Get")
                           ->sendRequest();
    } catch(NuxeoClientException $ex) {
        $previousex = $ex->getPrevious();
        if ($previousex instanceof BadResponseException && 401 === $previousex->getResponse()->getStatusCode()) {
            if ($logdebug) {
                $logger->log("Nuxeo Authentication failed.");
            }
            $isauthenticted = false ;
        }
        else {
            //throw $ex;
            $logger->log("Nuxeo REST error: ".$ex->getMessage());
        }
    }
    # http 200 : 200 Ok
    # http 401 : 401 Unauthorized (RFC 7235)

    if (!$isauthenticated) {
        $success = false ;
        if ($logdebug) {
            $logger->log("Nuxeo REST login failed due to wrong credentials");
            //print "Nuxeo REST login failed due to wrong credentials".PHP_EOL;
        }
    }
    else
    {
        $success = true ;
        if ($logdebug) {
            //$logger->log("Nuxeo REST login successful");
            print "Nuxeo REST login of user '".$nuxusr."' successful".PHP_EOL ;
        }
    }
}

So I edited my original code and applied above changes. Next I ran it:

 php test2.php
Nuxeo REST login of user 'Administrator' successful
Connection successfulPHP Fatal error:  Uncaught exception 'Guzzle\Http\Exception\ClientErrorResponseException' with message 'Client error response
[status code] 401
[reason phrase] Unauthorized
[url] http://localhost:8080/nuxeo/site/automation/User.Get' in /usr/share/php/Guzzle/Http/Exception/BadResponseException.php:43
Stack trace:
#0 /usr/share/php/Guzzle/Http/Message/Request.php(145): Guzzle\Http\Exception\BadResponseException::factory(Object(Guzzle\Http\Message\EntityEnclosingRequest), Object(Guzzle\Http\Message\Response))
#1 [internal function]: Guzzle\Http\Message\Request::onRequestError(Object(Guzzle\Common\Event))
#2 /usr/share/php/Symfony/Component/EventDispatcher/EventDispatcher.php(164): call_user_func(Array, Object(Guzzle\Common\Event))
#3 /usr/share/php/Symfony/Component/EventDispatcher/EventDispatcher.php(53): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'request.error', Object(Guzzle\Common\Event))
#4 /usr/share/php/Guzzle/Http/Message/Request.php(589): Symfony\Component\EventDispatcher\EventDispatcher->dispa in /var/www/html/bs3/Nuxeo/Automation/Client/Utilities/NuxeoRequest.php on line 227
Apr 18 15:04:08 PHP [info] login attempt with credentials: jdfsfds | dsfdsfsk

QUESTION: what goes wrong here please? Or does authentication only works with a Document.Query ?

0 votes



Hi,

For 2 and 4 (general exception handling), there were indeed no exceptions thrown. I released a new version (https://git.io/vwJ2F) to fix that.

To check for authentication failure, you can now do something like this :

try {  
  $answer = $session->newRequest("Document.Query")
    ->set('params', 'query', "SELECT * FROM Document WHERE ecm:path = '/default-domain/workspaces/Default Workspace/Hello World'")
    ->sendRequest();
} catch(NuxeoClientException $ex) {  
  $previous = $ex->getPrevious();  
  if($previous instanceof BadResponseException && 401 === $previous->getResponse()->getStatusCode()) {
    echo "Auth failed";
  } else {
    throw $ex;
  }}

About 3 there was a bug in the Client API (fixed in the new release) for empty-body queries like User.Get.

About 1, composer is now a must-have for PHP development, I highly recommend you to use it.

0 votes



Thank you very much! Looks promising. I will give it a spin next Monday.
04/15/2016