Saturday 30 January 2010

VPN Client for Windows 7

I have to use Cisco VPN Client v4.6.* for work. It works fine on Windows XP. Recently, as one of my Windows XP laptops went kaput, I upgraded to Windows 7 and am loving it. However, the Cisco VPN client would not work anymore. I tried to download a newer version (v5.x) from Cisco, but for several months their online registration just would not work (hard to imagine such a giant company couldn’t get such a simple thing working). So I had to hunt for alternatives.

Now I am using ShrewSoft VPN Client. It is extremely simple and fast to install and configure. The whole experience from download, installation, configuration to use took less than 10 minutes. It supports importing of Cisco VPN connection profiles (.pcf files). It has good online documentation, and its free!

Tuesday 26 January 2010

Flash vs. JavaFX

I wrote the same eCards using JavaFX 1.0 in NetBeans and Flash 10 (with ActionScript 3) in CS4. The contents and algorithms of the eCards are the same but the outcome are quite different.

User Experience

  1. The Flash version has 200 snow flakes and the JavaFX one has 30. The result is that Flash eCard is very smooth and the JavaFX one is jerky.
  2. If your browser does not have the required Flash plug-in, you can still view the rest of the page; but JavaFX forces your browser to go to Sun to download the plugin and you cannot see the other contents of the page.
  3. The first time you view the pages, the .swf file and .jar files will be downloaded to your computer respectively. The time taken to download them are not that much different with Flash version slightly shorter. However, if you view them afterwards, the cached Flash eCard will be played instantaneously without having to download again even after you restart the browser or computer. But the JavaFX version will be downloaded again every time.

Designer Experience

  1. The CS4 environment is a lot easier to use for designers. You can do things graphically or programmatically. In NetBeans you have no choice but to write code for everything – even to manage timelines!
  2. CS4 generates deployment code/files for HTML and AIR. It is really easy to deploy .swf files and the process is simple, robust and mature. I can’t say the same thing about Java Web Start, which is nowhere near production quality.
  3. If you are like me, who wants to learn things from free web resources, then JavaFX is easier since there is only one way to do things – by writing code (this could be a bad thing for some). Most of the tutorials and sample codes work. But the different ActionScript releases are not backward-compatible. Many of the code samples were written in AS 1.0 or 2.0 (since they have been out there for many years) – they do not work in 3.0.

Related Post:

Friday 22 January 2010

Rise of the Droid

Traffic to this blog had been pretty flat in the last couple of months. It was not surprising considering the holiday season. However, I noticed an unusual surge of traffic in the last three weeks creating new record highs. I was a bit baffled. The only thing mildly interesting (to me) that I wrote after the New Year was about hacking LinkedIn API. I don't think many netizens are that interested in LinkedIn, let alone LinkedIn API.

A little digging in my custom reports in Google Analytics revealed that my No. 1 visited blog was Consumer Web Services from Android that I wrote in 2008 when the Android 0.9 SDK Beta was released. For about 12 months, that post was not among the top 5 list. Yet soon after Google announced its launch of Android phone people are are beefed up about development for Android platform.  Judging from the developer community’s enthusiasm, it won’t take long before tons of Android applications to be available, helping Android’s market share. So I expect to see Android to catch up with iPhone in the next 12-24 months. It will be interesting to see how the multi-vendor strategy work out against Apple’s iPhone.

Kudos to Google!

Friday 15 January 2010

Happy New Year 2010

This is a Flash port of my eCard first written in JavaFX last year.

The snow fall script is stolen from http://r3dux.org/2010/01/actionscript-3-0-particle-systems-2-snow-effect/

The algorithm of the snow fall between the the JavaFX version and this one are pretty similar. The background photo was taken in Darling Harbour on Christmas eve 2009.

Related Post:

Saturday 2 January 2010

Hacking LinkedIn API

Note [2011-03-24]: As promised, I have updated the source code. Now it works! Please see my other post which supercedes this one - Hacking LinkedIn API - Take 2.

Note [2010-03-12]:LinkedIn has changed the login page(s) so that the HTML scraping code below no longer works. However, I do believe that the approach still works. I will publish an update when I have some time after hours. Stay tuned.

I was quite excited to discover that LinkedIn was exposing APIs for 3rd-party applications to tap into its data. I downloaded the LinkedIn-J 0.1 SNAPSHOT-2009-12-30 which is a beta java wrapper of the APIs from Google Code and tried the example posted at LinkedIn Developer Network forum. The sample worked quite well as intended.

However, the paradigm of the oauth procedure which is adopted by LinkedIn APIs is convoluted and the LinkedIn authorisation process assumes your application is a web application. As part of the authorisation process, it forces the user to login directly to LinkedIn web page (shown below) to get a PIN and feed the PIN to the application to continue the process.

Once logged in successfully, a PIN is given in the next HTML page:

This is no good for machine-to-machine type of integration. I just want to retrieve LinkedIn data from the backend without forcing the user to be a LinkedIn member or login twice. So I decided to bypass this extra login.

Looking (view source) at the above login form it is a simple HTML FORM:

...
         
...

Note that the FORM has the following INPUT elements (including the hidden ones and the submit button):

  1. email – use my login to LinkedIn
  2. password – use my password to LinkedIn
  3. duration – set to 0
  4. access – set to –3
  5. agree – set to true
  6. extra – empty
  7. authorize – set to Grant Access

We can easily populate this form and submit it in Java.

...
DataOutputStream dataOut;
      
        try {
            URL url = new URL(authUrl);
            //URL url = new URL("https://api.linkedin.com/uas/oauth/authorize");
            HttpURLConnection con = (HttpURLConnection)url.openConnection();
            con.setRequestMethod("POST");
            con.setUseCaches(false);
            con.setDoInput(true);
            con.setDoOutput(true);
            con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

            dataOut = new DataOutputStream(con.getOutputStream());

            dataOut.writeBytes("email=myname%40mycompany.com&password=mypassword&duration=0&access=-3&agree=true&extra=&authorize=Grant Access");
            dataOut.flush();
            dataOut.close();

            //SSLException thrown here if server certificate is invalid
            String returnedHtml=convertStreamToString(con.getInputStream());
            System.out.println(returnedHtml);
...

If successful, the returnedHTML will contain the PIN (as shown in screenshot above). So it is simply a matter of scraping that HTML string to get the 5-digit PIN and feed it to the next step of the authorisation process. The following is the modified Beta Java SignPost Sample Code to bypass the LinkedIn login page:

// LinkedIn SignPost Sample Code
// Adapted by Taylor Singletary
// from Twitter SignPost Sample Code ( http://oauth-signpost.googlecode.com/files/OAuthTwitterExample.zip )
// Tested and Functional with attached SignPost JAR
// YMMV

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

import java.net.HttpURLConnection;
import java.net.URL;
import java.util.logging.Level;

import oauth.signpost.OAuth;
import oauth.signpost.OAuthConsumer;
import oauth.signpost.OAuthProvider;
import oauth.signpost.basic.DefaultOAuthConsumer;
import oauth.signpost.basic.DefaultOAuthProvider;
import oauth.signpost.signature.SignatureMethod;


public class Main {
    static public String getPin(String authUrl) {
        DataOutputStream dataOut;
      
        try {
            URL url = new URL(authUrl);
            //URL url = new URL("https://api.linkedin.com/uas/oauth/authorize");
            HttpURLConnection con = (HttpURLConnection)url.openConnection();
            con.setRequestMethod("POST");
            con.setUseCaches(false);
            con.setDoInput(true);
            con.setDoOutput(true);
            con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

            dataOut = new DataOutputStream(con.getOutputStream());

            dataOut.writeBytes("email=myname%40mycompany.com&password=mypassword&duration=0&access=-3&agree=true&extra=&authorize=Grant Access");
            dataOut.flush();
            dataOut.close();

            //SSLException thrown here if server certificate is invalid
            String returnedHtml=convertStreamToString(con.getInputStream());
            System.out.println(returnedHtml);
            /* extract the pin from the html string. the block looks like this
        <div class="content">
          You have successfully authorized MyApplication
          Please enter the following security code to enable full access

          <p align="center"><b>12345</b></p>
        </div>
             * It turns out that the whole html string only contains one 'p align="center"'
             * also it seems that the pin is always 5-digit long,
             * so we will just crudely detect that string and get the pin out.
             * A proper HTML parser should be used in a real application.
             */
            int i=returnedHtml.indexOf("center\"><b>");
            String pin = returnedHtml.substring(i+11, i+11+5);
            System.out.println("pin="+pin);
            return pin;
        } catch (IOException ex) {
            ex.printStackTrace();
            return null;
        }
    }
    public static void main(String[] args) throws Exception {
        OAuthConsumer consumer = new DefaultOAuthConsumer(
                "YourConsumerKey",
                "YourConsumerSecret",
                SignatureMethod.HMAC_SHA1);

        OAuthProvider provider = new DefaultOAuthProvider(consumer,
                "https://api.linkedin.com/uas/oauth/requestToken",
                "https://api.linkedin.com/uas/oauth/accessToken",
                "https://api.linkedin.com/uas/oauth/authorize");

        System.out.println("Fetching request token from LinkedIn...");

        // we do not support callbacks, thus pass OOB
        String authUrl = provider.retrieveRequestToken(OAuth.OUT_OF_BAND);

        System.out.println("Request token: " + consumer.getToken());
        System.out.println("Token secret: " + consumer.getTokenSecret());
/*
        System.out.println("Now visit:\n" + authUrl
                + "\n... and grant this app authorization");
        System.out.println("Enter the PIN code and hit ENTER when you're done:");

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String pin = br.readLine();
*/
        System.out.println("Now getting PIN from:\n" + authUrl);
      
        String pin = getPin(authUrl);
        System.out.println("Fetching access token from LinkedIn...");

        provider.retrieveAccessToken(pin);

        System.out.println("Access token: " + consumer.getToken());
        System.out.println("Token secret: " + consumer.getTokenSecret());

        URL url = new URL("http://api.linkedin.com/v1/people/~:(id,first-name,last-name,picture-url,headline)");
        HttpURLConnection request = (HttpURLConnection) url.openConnection();

        consumer.sign(request);

        System.out.println("Sending request to LinkedIn...");
        request.connect();
        String responseBody = convertStreamToString(request.getInputStream());

        System.out.println("Response: " + request.getResponseCode() + " "
                + request.getResponseMessage() + "\n\n" + responseBody);
       
    }

    // Stolen liberally from http://www.kodejava.org/examples/266.html
    public static String convertStreamToString(InputStream is) {
        /*
         * To convert the InputStream to String we use the BufferedReader.readLine()
         * method. We iterate until the BufferedReader return null which means
         * there's no more data to read. Each line will appended to a StringBuilder
         * and returned as String.
         */
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        StringBuilder sb = new StringBuilder();

        String line = null;
        try {
            while ((line = reader.readLine()) != null) {
                sb.append(line + "\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return sb.toString();
    }
}