packages feed

hMollom-0.3.1: src/Network/Mollom/OAuth.hs

{-
 - (C) 2012, Andy Georges
 -
 - This modules takes care of preparing the OAuth 1.0 information that is to be sent 
 - along with each request to the Mollom service.
 -
 - For documentation about OAuth, see RFC 5849 (http://tools.ietf.org/html/rfc5849).
 -}

 module Network.Mollom.OAuth
    ( OAuthSignatureMethod (..) 
    , getMollomNonce
    , getMollomTime
    , oauthHeader
    , oauthHeaderValues
    , oauthSignature
    ) where

import Codec.Binary.Base64 (encode)
import Data.ByteString.Internal (c2w, w2c)
import Data.ByteString.Lazy.Char8 (pack)
import Data.Digest.Pure.MD5 (md5)
import Data.HMAC (hmac_sha1)
import Data.List (intercalate)
import Network.HTTP (RequestMethod, urlEncode)
import Network.HTTP.Headers (HasHeaders, HeaderName(HdrAuthorization), HeaderSetter, replaceHeader)
import System.IO.Unsafe (unsafePerformIO)
import System.Locale (defaultTimeLocale)
import System.Random
import System.Time

import Network.Mollom.Types

data OAuthSignatureMethod = OAuthHmacSha1 
                          | OAuthRsaSha1 
                          | OAuthPlainText 
                          deriving (Eq)

instance Show OAuthSignatureMethod where
    show OAuthHmacSha1  = "HMAC-SHA1"
    show OAuthRsaSha1   = "RSA-SHA1"
    show OAuthPlainText = "PLAINTEXT"

oauthVersion = "1.0"

-- | Obtain a nonce for use in the request
getMollomNonce :: String -- ^ unique string across requests
getMollomNonce = show . md5 . pack . show $ (+) (getMollomTime) $ (unsafePerformIO $ getStdRandom (randomR (1,100000)) :: Integer)

-- | Obtain the system time
getMollomTime :: Integer -- ^current time in the specified format
getMollomTime = let TOD s p = unsafePerformIO $ getClockTime
                in s


-- | Compute the hashed and base64 encoded value for a (key, string) pair.
hash :: String -- ^ key
     -> String -- ^ data to hash
     -> String -- ^ resulting hashed value
hash key s = encode $ hmac_sha1 (map c2w key) (map c2w s)


-- | The headers for the OAuth protocol in the HTTP
--   Authorization field. See 3.5.1
oauthHeaderValues :: String               -- ^ Public key
                  -> OAuthSignatureMethod -- ^ Signature method
                  -> [(String, String)]   -- ^ Name-value pairs (we need to be able to sort these later)
oauthHeaderValues key sig =
    [ ("oauth_consumer_key", key)
--    , ("oauth_token", "")
    , ("oauth_signature_method", show sig)
    , ("oauth_timestamp", show $ getMollomTime)
    , ("oauth_nonce", getMollomNonce)
    , ("oauth_version", "1.0")
    ]

-- | Format the OAuth header, so it can be added to the
--   HTTP request header.
oauthHeader :: HasHeaders a       -- ^ We must be applied to something that has headers
            => [(String, String)] -- ^ Name-value pairs that need to be set in the header
            -> (a -> a)           -- ^ Continuation for changing the headers
oauthHeader pairs = 
    replaceHeader HdrAuthorization $ "OAuth " ++ oas
  where oas = intercalate ","
            $ map (\(n, v) -> intercalate "=" [ urlEncode n, "\"" ++ urlEncode v ++ "\"" ]) pairs


-- | Determine the signature for the OAuth headers in the 
--   HTTP request sent to the Mollom server. See Section
--   3.4 of RFC 5849.
oauthSignature :: OAuthSignatureMethod -- ^ Signing type
               -> String         -- ^ Private key
               -> RequestMethod  -- ^ HTTP method
               -> String         -- ^ server URI
               -> String         -- ^ request path
               -> String         -- ^ request parameters
               -> String
oauthSignature sig privateKey method server path request = 
    -- The base string consists of the HTTP method (POST, GET, ...)
    -- concatenated via &-symbols with the URI of the request and 
    -- the encoded request parameters. See 3.4.1.
    let baseString = intercalate "&" 
                   $ [ show method
                     , urlEncode (server ++ "/" ++ path)
                     , urlEncode request
                     ]
    in case sig of
        -- The signature must use the token secret, even if it is empty. 
        -- See 3.4.2.
        OAuthHmacSha1 -> let key = intercalate "&"
                                 $ [ urlEncode privateKey
                                   , urlEncode ""
                                   ]
                         in hash key baseString