All posts tagged oroplatform

Rest-api

Summary

At the time I’m writing those lines, OroCRM is not yet officially released but Akeneo has just been released. Both applications use OroPlatform which has the capability to be accessible by external third parties via REST API. Some documentation is still missing about this part (thanks to alsma, a cookbook is available on github), so I think it will be very useful for those interesting by this topic.

The REST API will allow you to add (POST request), update (PUT request), delete (DELETE request) or get (GET request) information from or into the OroPlatform application. In this article, we will do an example by adding a contact to OroCRM via the API.

Authentication

To use the REST API of OroPlatform you will need to authenticate a user by using the WSSE Authentication protocol. To do the authentication, some information of an API user will be necessary:

  • a username
  • an api key
  • the user salt (until the release v.1.0 will be published, after that it won’t be needed)

You will get the API Key from the User Management. When viewing the user profile, there is a button “Generate Key” click on it and you will get the API key:

User ApiKey

To get the User Salt, you will have to find it directly from the database. Table ‘oro_user‘, column ‘salt‘. This information is true until the Oro Team release the version 1.0 because the salt should not be used from external applications…

table_orouser

The WSSE protocol ask you to send an Authorization WSSE header and a custom X-WSSE header. This last header should have keys generated for each request sent. You can generate those headers thanks to the command:

/var/www/orocrm$ php app/console oro:wsse:generate-header admin
To use WSSE authentication add following headers to the request:
Authorization: WSSE profile="UsernameToken"
X-WSSE: UsernameToken Username="admin", PasswordDigest="U6wg4zpzy9C4goBdapNeS18Z/WQ=", Nonce="MTQ4YWVkNTg1NmRlMmRlYg==", Created="2014-03-22T23:53:39+01:00"

There are some problems here if you want to use those information:

  • There is a lifetime of 5 minutes. It’s important to know it when you do tests or when the clock of your computer or virtual machine is not sync with the one of your third party script. I had the experience…
  • You can use it only once. You have to generate for each new request
  • Your third party script may not have access to the CLI of the OroPlatform. In this case you need to implement yourself the algorithm to generate those keys. You will find the solution below :-)

Test WSSE Authentification

If you want to do a test to see if it works, you can do a test via curl in CLI

curl 'http://orocrm.diglin.local/app_dev.php/api/rest/latest/users/1' \
-H 'Authorization: WSSE profile="UsernameToken"' \
-H 'X-WSSE: UsernameToken Username="admin", PasswordDigest="U6wg4zpzy9C4goBdapNeS18Z/WQ=", Nonce="MTQ4YWVkNTg1NmRlMmRlYg==", Created="2014-03-22T23:53:39+01:00"'

The returned content is a json string

{"id":1,"username":"admin","email":"admin@localhost.com","namePrefix":null,"firstName":"admin","middleName":null,"lastName":"admin","nameSuffix":null,"birthday":null,"enabled":true,"lastLogin":"2014-03-21T16:48:59+00:00","loginCount":9,"createdAt":"2014-03-01T17:55:39+00:00","updatedAt":"2014-03-21T16:50:22+00:00","owner":{"id":1,"name":"Acme, General"},"roles":[{"id":3,"role":"ROLE_ADMINISTRATOR","label":"Administrator"}],"groups":[],"emails":[],"businessUnits":[{"id":1,"name":"Acme, General"}],"imagePath":null}

API Routes

Once you have all necessary information, you are ready to use the REST API. You can get a list of all API routes available for the application by using the command line from the application root folder. In the path /api/rest/{version}/, {version} can have the value ‘latest’ or ‘v1′.

php app/console router:debug | grep api
oro_api_delete_contact                                    DELETE   ANY    ANY  /api/rest/{version}/contacts/{id}.{_format}
oro_api_get_contact                                       GET      ANY    ANY  /api/rest/{version}/contacts/{id}.{_format}
oro_api_get_contacts                                      GET      ANY    ANY  /api/rest/{version}/contacts.{_format}
oro_api_post_contact                                      POST     ANY    ANY  /api/rest/{version}/contact.{_format}
oro_api_put_contact                                       PUT      ANY    ANY  /api/rest/{version}/contacts/{id}.{_format}
oro_api_delete_contactgroup                               DELETE   ANY    ANY  /api/rest/{version}/contactgroups/{id}.{_format}
oro_api_get_contactgroup                                  GET      ANY    ANY  /api/rest/{version}/contactgroups/{id}.{_format}
oro_api_get_contactgroups                                 GET      ANY    ANY  /api/rest/{version}/contactgroups.{_format}
oro_api_post_contactgroup                                 POST     ANY    ANY  /api/rest/{version}/contactgroup.{_format}
oro_api_put_contactgroup                                  PUT      ANY    ANY  /api/rest/{version}/contactgroups/{id}.{_format}
oro_api_delete_contact_address                            DELETE   ANY    ANY  /api/rest/{version}/contacts/{contactId}/addresses/{addressId}.{_format}
oro_api_get_contact_address                               GET      ANY    ANY  /api/rest/{version}/contacts/{contactId}/addresses/{addressId}.{_format}
oro_api_get_contact_address_by_type                       GET      ANY    ANY  /api/rest/{version}/contacts/{contactId}/addresses/{typeName}/by/type.{_format}
oro_api_get_contact_address_primary                       GET      ANY    ANY  /api/rest/{version}/contacts/{contactId}/address/primary.{_format}
oro_api_get_contact_addresses                             GET      ANY    ANY  /api/rest/{version}/contacts/{contactId}/addresses.{_format}
oro_api_get_contact_phone_primary                         GET      ANY    ANY  /api/rest/{version}/contacts/{contactId}/phone/primary.{_format}
oro_api_get_contact_phones                                GET      ANY    ANY  /api/rest/{version}/contacts/{contactId}/phones.{_format}

To add a contact into OroCRM for example, you may use the url http://www.mydomain.tld/api/rest/latest/contact/

Add a contact via the API to OroCRM

Before to add a contact, you need to know the structure of the data to send via POST request. By getting a contact information, you will have the structure to send to OroCRM. Here is an example:

curl 'http://orocrm.diglin.local/app_dev.php/api/rest/latest/contacts/1' \
>-H 'Authorization: WSSE profile="UsernameToken"' \
>-H 'X-WSSE: UsernameToken Username="admin", PasswordDigest="sgFfs7dM0kKL8TbaUxpKTvvywSU=", Nonce="MDY3ODk4MzBmNjVmOTRhMA==", Created="2014-03-23T00:10:59+01:00"'

{"id":1,"namePrefix":null,"firstName":"Ramona","middleName":null,"lastName":"Venters","nameSuffix":null,"gender":null,"birthday":null,"description":"Sure Save","jobTitle":null,"fax":null,"skype":null,"twitter":null,"facebook":null,"googlePlus":null,"linkedIn":null,"createdAt":"2014-03-01T17:56:01+00:00","updatedAt":"2014-03-01T17:56:01+00:00","email":null,"source":null,"method":null,"owner":15,"assignedTo":null,"reportsTo":null,"emails":{},"phones":{},"addresses":[{"primary":true,"id":1,"label":"Primary Address","street":"873 John Avenue","street2":null,"city":"Jackson","postalCode":"49201","organization":null,"regionText":null,"namePrefix":null,"firstName":"Ramona","middleName":null,"lastName":"Venters","nameSuffix":null,"types":[],"country":"United States","region":"Michigan"}],"groups":[],"accounts":[],"createdBy":15,"updatedBy":15}

Now if you want to create a new contact, you can send POST data via curl with this example string

curl -X POST 'http://orocrm.diglin.local/app_dev.php/api/rest/latest/contact' -d "contact[firstName]=Sylvain;contact[lastName]=Rayé;contact[jobTitle]=CTO;"

The same example with a PHP script:

$username = 'admin';
$apiUserKey = '';
$userSalt = ''; // Will be removed in version 1.0 of OroCRM
$url = 'http://orocrm.diglin.local/app_dev.php/api/rest/latest/contact';

$data = array(
    'contact[firstName]' => 'Sylvain',
    'contact[lastName]' => 'Rayé',
    'contact[jobTitle]' => 'Ingénieur'
);

$oroWsse = new OroWsseAuthentification($username, $apiUserKey, $userSalt);

$ch = curl_init();

if (!empty($data)) {
    $array = array(
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => $data
    );
    curl_setopt_array($ch, $array);
}

$headers = $oroWsse->getHeaders();

print_r($headers);

$array = array(
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => $headers,
    CURLOPT_HEADER => 0,
    CURLOPT_FAILONERROR => true,
    CURLOPT_URL => $url
);

curl_setopt_array($ch, $array);

$result = curl_exec($ch);

if ( $result === false) {
    echo curl_error($ch);
} else {
    echo ($result) . "\n";
}

curl_close($ch);

PHP Class for OroWsseAuthentification

Because you need to generate the WSSE headers for each request done to the OroPlatform API, you will be more than happy to have the algorithm generated directly from your application. You will find below the class which will help you to generate these headers for you. The algorithm is taken from OroPlatform in the class ‘Oro\Bundle\UserBundle\Command\GenerateWSSEHeaderCommand’. In future versions of OroPlatform, it can still change.

class OroWsseAuthentification
{
    protected $_username;
    protected $_apiKey;
    protected $_userSalt;

    /**
     * @param $username
     * @param $apiUserKey
     * @param string $userSalt
     */
    public function __construct ($username, $apiUserKey, $userSalt = '')
    {
        $this->_username = $username;
        $this->_apiKey = $apiUserKey;
        $this->_userSalt = $userSalt; // deprecated in OroCRM v1.0
    }

    /**
     * @param $raw
     * @param $salt
     * @return string
     */
    private function _encodePassword($raw, $salt)
    {
        $salted = $this->_mergePasswordAndSalt($raw, $salt);
        $digest = hash('sha1', $salted, true);

        return base64_encode($digest);
    }

    /**
     * @param $password
     * @param $salt
     * @return string
     * @throws InvalidArgumentException
     */
    private function _mergePasswordAndSalt($password, $salt)
    {
        if (empty($salt)) {
            return $password;
        }

        if (false !== strrpos($salt, '{') || false !== strrpos($salt, '}')) {
            throw new \InvalidArgumentException('Cannot use { or } in salt.');
        }

        return $password.'{'.$salt.'}';
    }

    /**
     * @return array
     */
    public function getHeaders ()
    {
        $prefix = gethostname();
        $created = date('c');
        $nonce  = base64_encode(substr(md5(uniqid($prefix . '_', true)), 0, 16));

        $passwordDigest = $this->_encodePassword(base64_decode($nonce) . $created . $this->_apiKey, $this->_userSalt);

        $wsseProfile = sprintf(
            'X-WSSE: UsernameToken Username="%s", PasswordDigest="%s", Nonce="%s", Created="%s"',
            $this->_username,
            $passwordDigest,
            $nonce,
            $created
        );

        return array(
            'Authorization: WSSE profile="UsernameToken"',
            $wsseProfile
        );
    }
}

Conclusion

I hope that it was for you helpful, feel free to add some comments and feedbacks. It’s definitely a good start for any further development. The complete code of this article is available at https://gist.github.com/diglin/9716109. Thanks to the comment of alsma, you will find also the official documentation on github: https://github.com/orocrm/documentation/blob/master/cookbook/how_to_use_wsse_authentication.rst