Blog

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

Magento Community 1.8 Update

The Magento Community Edition 1.8 was announced during the Imagine Conference in Las Vegas in April 2013. An alpha version was released and stay as it is until now. Finally, the release 1.8 has been published today. We can understand the situation when we are aware that the Magento Team concentrates itself on the Magento 2 version which is a big release and hopefully would come in 2014.

Although the community engaged its effort to fix a big amount of bugs with the collaboration of the Magento Team during the Hackathon in Zürich and Bugathon Los Angeles in March 2013, most of those fixes will be published in the version 1.8.1 later in Q4. So I may advise you to wait if you are not in hurry for your project to get this short coming soon release.

However for those who are not patient or would like to benefit quickly of the new features and enhancements, find here the list of the improvements:

Tax calculation

Definitely a subject which should enjoy enterprise having VAT. Several problems have been fixed: rounding errors calculation confusing buyers and merchants and discount calculation. You will find more information how to handle the configuration of your shop depending in which country you are, see the Magento Knowledge Base for more explanation.

I’d like to take the opportunity to inform you of a module which can help you to setup the tax calculation rules in a fast way when you are installing Magento. It is very helpful for European Countries, Russia and Switzerland. The module has been developed by the Firegento Association (a developer community based in Germany) and available for free on Github. You may find also an article explaining more in details the module.

Performance

Improvements:

  • of the speed of page loading during checkout process.
  • of the cache for single server, minimising the refreshment of caches when product is updated
  • of the backend when a store has huge amount of products or tax codes
  • Add Redis cache module from Colin Mollenhour

Functional Improvements

350 functional improvements that boost product quality in key areas, including the web store, shopping cart, admin order creation, import and export functionality, web API components, and payment methods.

Security

Fixed:

  • Session fixation vulnerability
  • Oauth attacks
  • Remote code execution
  • Block attackers to get access to billing information
  • Block web browsers to save user and password from backend
  • Additional CSRF protection
  • Session ID change after registration to prevent man in the middle
  • Cryptographic method to store password has been improved

Download the new release in the download section of Magento Website

Sources: Moses from Magento Inc. / 1.8 Release notes / Magento Blog

Zurich Hackathon March 2013

You always hear about this event. Since one month, everybody is tweeting, facebooking, googling plusing, blogging, chatting but for what the hell reasons should you come! They pushed you against the wall and told you: “You MUST go in Zürich on 8-10th March 2013! That’s the Magento Developer event of the year in Switzerland.”. Sad isn’t it! Find here some arguments to NOT come:

  1. Magento, what is it? I’ve never heard about that. I’m still using the dark side of the ecommerce solution, osCommerce, the best ever Open Source solution!
  2. I’m the best coder of the world. I need no friend, no tips, no network, no fun. I don’t want to share my knowledge with no one because I prefer to work alone, I’m more efficient. PHP is the bottleneck for Magento, I don’t want to improve it.
  3. I prefer to do a world tour. It’s more interesting. Where the hell is Matt? http://www.wherethehellismatt.com
  4. Achmed – The Dead Terrorist (http://www.youtube.com/watch?v=1uwOL4rB-go) didn’t allow me to go. He said if I go: “I kill you!”
  5. The last time I came, it was in Berlin, a Dutch opened all people’s beers with my shiny MacBook Air. It doesn’t work anymore. I’m shocked for all of my life.
  6. I’m not the sponsor. I’m sad and sulking!
  7. I still didn’t digest the sweets of the Munich Hackathon in last october. I cannot move anymore, sorry!

If unfortunately, after all these reasons, you still didn’t find yours. It seems you have no reason to not coming. ;-)

Visit the event website to enjoy with us and get some surprises in Zürich on 8-10 March 2013: http://www.magento-hackathon.ch

PS: if you have better, funnier or others arguments, please comment them :-)

talend

The problematic

You have SugarCRM as a CRM and you wish for example, export only some users’ information (lastname, firstname and email) to import them to your favorite/shiny newsletter system. By using the export function of SugarCRM, you get a CSV file with all users’ fields and which could be theoretically imported into Excel to remove the columns that you don’t need for your newsletter system importation. Unfortunately, a user field “description” has line breaks which prevent a clean import into Excel because each lines is recognized as a new row/entry which is wrong.

Solution

Use Talend Open Studio for Data Integration. A free ETL (Extract Tool) provided by Talend and which will help you to extract exactly the source data wished and export it to any target (example: csv file, database, other web service, etc).

It’s an eclipse-like which works with a Job Designer. You create a job, add input component (file, database, webservice, else), output component (files, database, webservice or else) and other interactions (data filtering, data sorting, etc).

Read more…

Magento Hackathon Logo

The Magento Hackathon is a Magento Developer Event which was located this last week end (26-28 October 2012), in Munich. It’s a good moment to share knowledge about the development process or some tips to improve his skills with Magento. This is also an opportunity to create some challenge or emulation with different projects and make some good friends :-)

The projects

In Munich, we were more than 24 developers from different companies based in Germany and Switzerland ;-) and did during this event some projects that you can find on Magento Hackathon Github  like:

  • Product sort order in categories via Drag’n drop: A good idea module, to help you to sort your products into your categories thanks to Drag’n drop action which can be used from the backend or the frontend of your online shop.
  • Login Provider Framework: A framework to provide other authentication methods to get access to the backend. It could be for example, to implement an authentication method with a LDAP server.
  • Honey Spam: A Magento module to block automatic process which try to use the forms of your shop to hack it for example.
  • Social Commerce: Do you want to make your shop more social? Thanks to this module some events which happens on your shop, can be published on social media automatically. While I’m writing these lines, a newly created and enabled product saved from the backend can be published automatically to twitter with its name and a shorten frontend link.
  • Magento Composer Installer: The purpose of this project is to enable composer to parse and execute modman files. Magento modules are installable as long as they have a valid modman file.

Read more…