LinkedIn OAuth in F#

First of all great thanks to Luke Hoban for his ‘Twitter OAuth in F#‘. His OAuth implementation works fine for LinkedIn as well.

All you need to do is to switch the requestTokenURI/accessTokenURI/authorizeURI addresses from Twitter on LinkedIn.

Additional materials about LinkedIn public API you found at the LinkedIn developers site: https://developer.linkedin.com/

LinkedIn OAuth implementation:

open System
open System.IO
open System.Net
open System.Security.Cryptography
open System.Text

// LinkedIn OAuth Constants
let consumerKey : string = failwith "Must provide the consumerKey for an app registered at https://www.linkedin.com/secure/developer?newapp="
let consumerSecret : string = failwith "Must provide the consumerSecret for an app registered at https://www.linkedin.com/secure/developer?newapp="
let requestTokenURI = "https://api.linkedin.com/uas/oauth/requestToken"
let accessTokenURI = "https://api.linkedin.com/uas/oauth/accessToken"
let authorizeURI = "https://api.linkedin.com/uas/oauth/authorize"

// Utilities
let unreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
let urlEncode str =
    String.init (String.length str) (fun i ->
        let symbol = str.[i]
        if unreservedChars.IndexOf(symbol) = -1 then
            "%" + String.Format("{0:X2}", int symbol)
        else
            string symbol)

// Core Algorithms
let hmacsha1 signingKey str =
    let converter = new HMACSHA1(Encoding.ASCII.GetBytes(signingKey : string))
    let inBytes = Encoding.ASCII.GetBytes(str : string)
    let outBytes = converter.ComputeHash(inBytes)
    Convert.ToBase64String(outBytes)

let compositeSigningKey consumerSecret tokenSecret =
    urlEncode(consumerSecret) + "&" + urlEncode(tokenSecret)

let baseString httpMethod baseUri queryParameters =
    httpMethod + "&" +
    urlEncode(baseUri) + "&" +
      (queryParameters
       |> Seq.sortBy (fun (k,v) -> k)
       |> Seq.map (fun (k,v) -> urlEncode(k)+"%3D"+urlEncode(v))
       |> String.concat "%26")

let createAuthorizeHeader queryParameters =
    let headerValue =
        "OAuth " +
        (queryParameters
         |> Seq.map (fun (k,v) -> urlEncode(k)+"\x3D\""+urlEncode(v)+"\"")
         |> String.concat ",")
    headerValue

let currentUnixTime() = floor (DateTime.UtcNow - DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalSeconds

/// Request a token from LinkedIn and return:
/// oauth_token, oauth_token_secret, oauth_callback_confirmed
let requestToken() =
    let signingKey = compositeSigningKey consumerSecret ""

    let queryParameters =
        ["oauth_callback", "oob";
         "oauth_consumer_key", consumerKey;
         "oauth_nonce", System.Guid.NewGuid().ToString().Substring(24);
         "oauth_signature_method", "HMAC-SHA1";
         "oauth_timestamp", currentUnixTime().ToString();
         "oauth_version", "1.0"]

    let signingString = baseString "POST" requestTokenURI queryParameters
    let oauth_signature = hmacsha1 signingKey signingString

    let realQueryParameters = ("oauth_signature", oauth_signature)::queryParameters

    let req = WebRequest.Create(requestTokenURI, Method="POST")
    let headerValue = createAuthorizeHeader realQueryParameters
    req.Headers.Add(HttpRequestHeader.Authorization, headerValue)

    let resp = req.GetResponse()
    let stream = resp.GetResponseStream()
    let txt = (new StreamReader(stream)).ReadToEnd()

    let parts = txt.Split('&')
    (parts.[0].Split('=').[1],
     parts.[1].Split('=').[1],
     parts.[2].Split('=').[1] = "true")

/// Get an access token from LinkedIn and returns:
/// oauth_token, oauth_token_secret
let accessToken token tokenSecret verifier =
    let signingKey = compositeSigningKey consumerSecret tokenSecret

    let queryParameters =
        ["oauth_consumer_key", consumerKey;
         "oauth_nonce", System.Guid.NewGuid().ToString().Substring(24);
         "oauth_signature_method", "HMAC-SHA1";
         "oauth_token", token;
         "oauth_timestamp", currentUnixTime().ToString();
         "oauth_verifier", verifier;
         "oauth_version", "1.0"]

    let signingString = baseString "POST" accessTokenURI queryParameters
    let oauth_signature = hmacsha1 signingKey signingString

    let realQueryParameters = ("oauth_signature", oauth_signature)::queryParameters

    let req = WebRequest.Create(accessTokenURI, Method="POST")
    let headerValue = createAuthorizeHeader realQueryParameters
    req.Headers.Add(HttpRequestHeader.Authorization, headerValue)

    let resp = req.GetResponse()
    let stream = resp.GetResponseStream()
    let txt = (new StreamReader(stream)).ReadToEnd()

    let parts = txt.Split('&')
    (parts.[0].Split('=').[1],
     parts.[1].Split('=').[1])

/// Compute the 'Authorization' header for the given request data
let authHeaderAfterAuthenticated url httpMethod token tokenSecret queryParams =
    let signingKey = compositeSigningKey consumerSecret tokenSecret

    let queryParameters =
        ["oauth_consumer_key", consumerKey;
         "oauth_nonce", System.Guid.NewGuid().ToString().Substring(24);
         "oauth_signature_method", "HMAC-SHA1";
         "oauth_token", token;
         "oauth_timestamp", currentUnixTime().ToString();
         "oauth_version", "1.0"]

    let signingQueryParameters =
        List.append queryParameters queryParams

    let signingString = baseString httpMethod url signingQueryParameters
    let oauth_signature = hmacsha1 signingKey signingString
    let realQueryParameters = ("oauth_signature", oauth_signature)::queryParameters
    let headerValue = createAuthorizeHeader realQueryParameters
    headerValue

/// Add an Authorization header to an existing WebRequest
let addAuthHeaderForUser (webRequest : WebRequest) token tokenSecret queryParams =
    let url = webRequest.RequestUri.ToString()
    let httpMethod = webRequest.Method
    let header = authHeaderAfterAuthenticated url httpMethod token tokenSecret queryParams
    webRequest.Headers.Add(HttpRequestHeader.Authorization, header)

type System.Net.WebRequest with
    /// Add an Authorization header to the WebRequest for the provided user authorization tokens and query parameters
    member this.AddOAuthHeader(userToken, userTokenSecret, queryParams) =
        addAuthHeaderForUser this userToken userTokenSecret queryParams

let testing() =
    // Compute URL to send user to to allow our app to connect with their credentials,
    // then open the browser to have them accept
    let oauth_token'', oauth_token_secret'', oauth_callback_confirmed = requestToken()
    let url = authorizeURI + "?oauth_token=" + oauth_token''
    System.Diagnostics.Process.Start("iexplore.exe", url)

    // *******NOTE********:
    // Get the 7 digit number from the web page, pass it to the function below to get oauth_token
    // Sample result if things go okay:
    // val oauth_token_secret' : string = "9e571e13-d054-44e6-956a-415ab3ee6d23"
    // val oauth_token' : string = "044da520-0edc-4083-a061-74e115712b61"
    let oauth_token, oauth_token_secret = accessToken oauth_token'' oauth_token_secret'' ("78846")

    // Test 1: Get your profile details
    let streamSampleUrl2 = "http://api.linkedin.com/v1/people/~"
    let req = WebRequest.Create(streamSampleUrl2)
    req.AddOAuthHeader(oauth_token, oauth_token_secret, [])
    let resp = req.GetResponse()
    let strm = resp.GetResponseStream()
    let text = (new StreamReader(strm)).ReadToEnd()
    text

    // Test 2: Get a connections list
    let streamSampleUrl3 = "http://api.linkedin.com/v1/people/~/connections"
    let req = WebRequest.Create(streamSampleUrl3)
    req.AddOAuthHeader(oauth_token, oauth_token_secret, [])
    let resp = req.GetResponse()
    let strm = resp.GetResponseStream()
    let text = (new StreamReader(strm)).ReadToEnd()
    text

How to integrate F# and Notepad++

People a faced with challenges trying to install F# interactive plugin for Notepad++ (example). I am not an exception, going through all of this I want to share my experience.

  1. First of all download latest version of the Notepad++ and install it (default installation settings). For today it is Notepad++ 6.1.5 (Jul 16 2012)
  2. Download NPPFSIPlugin Version 0.1.1.
  3. Extract dll from achieve and copy it to Notepad++\plugins\ folder. On the my Win7 64bit machine it is c:\Program Files (x86)\Notepad++\plugins\.
  4. Open or re-open you Notepad++ application.
  5. Go to Plugins\F# Interactive\Options menu item.
  6. Specify Binary Path to fsi.exe file.
    For example:
    F# 2.0C:\Program Files (x86)\Microsoft F#\v4.0\fsi.exe
    F# 3.0 – C:\Program Files (x86)\Microsoft SDKs\F#\3.0\Framework\v4.0\fsi.exe
    Before use this paths check that you you have installed appropriate version of F# in that 
  7. Save you changes. Now it should work.
    Alt+T to open F# interactive
    Alt+Enter to to send selected text to F# Interactive

It is also very nice to have a F# syntax highlighting.

  1. Download xml file with user definition language for Notepad++ from here or here.
  2. Rename file to userDefineLang.xml.
  3. Replace  ext=”fs”  to  ext=”fs fsi fsx”  in the file.
  4. Copy this file to %APPDATA%\Notepad++\ folder. (for more details go here )
    Path should be like this : C:\Users\User_Name\AppData\Roaming\Notepad++
  5. Restart Notepad++.

Note: If you already have such file open both. (instructions is copied from here)

  1. Select all of the new file, copy, and paste at the end of the current file
  2. Delete  </NotepadPlus><NotepadPlus> pair in the middle (remove 2 lines)

P.S. Post moved from http://sergey-tihon.blogspot.com/