authenticate 0.8.0.1 → 0.8.1
raw patch · 2 files changed
+231/−2 lines, 2 filesdep +RSAdep +SHAdep +base64-bytestringPVP ok
version bump matches the API change (PVP)
Dependencies added: RSA, SHA, base64-bytestring, random, time, wai-extra
API changes (from Hackage documentation)
+ Web.Authenticate.OAuth: Credential :: [(ByteString, ByteString)] -> Credential
+ Web.Authenticate.OAuth: HMACSHA1 :: SignMethod
+ Web.Authenticate.OAuth: OAuth :: String -> String -> String -> String -> SignMethod -> ByteString -> ByteString -> Maybe ByteString -> OAuth
+ Web.Authenticate.OAuth: PLAINTEXT :: SignMethod
+ Web.Authenticate.OAuth: RSASHA1 :: PrivateKey -> SignMethod
+ Web.Authenticate.OAuth: authorizeUrl :: OAuth -> Credential -> String
+ Web.Authenticate.OAuth: data Credential
+ Web.Authenticate.OAuth: data OAuth
+ Web.Authenticate.OAuth: data SignMethod
+ Web.Authenticate.OAuth: delete :: ByteString -> Credential -> Credential
+ Web.Authenticate.OAuth: emptyCredential :: Credential
+ Web.Authenticate.OAuth: getAccessToken :: OAuth -> Credential -> IO Credential
+ Web.Authenticate.OAuth: getTemporaryCredential :: OAuth -> IO Credential
+ Web.Authenticate.OAuth: insert :: ByteString -> ByteString -> Credential -> Credential
+ Web.Authenticate.OAuth: inserts :: [(ByteString, ByteString)] -> Credential -> Credential
+ Web.Authenticate.OAuth: instance Data Credential
+ Web.Authenticate.OAuth: instance Data OAuth
+ Web.Authenticate.OAuth: instance Data OAuthException
+ Web.Authenticate.OAuth: instance Data PrivateKey
+ Web.Authenticate.OAuth: instance Data SignMethod
+ Web.Authenticate.OAuth: instance Eq Credential
+ Web.Authenticate.OAuth: instance Eq OAuth
+ Web.Authenticate.OAuth: instance Eq OAuthException
+ Web.Authenticate.OAuth: instance Eq PrivateKey
+ Web.Authenticate.OAuth: instance Eq SignMethod
+ Web.Authenticate.OAuth: instance Exception OAuthException
+ Web.Authenticate.OAuth: instance Ord Credential
+ Web.Authenticate.OAuth: instance Ord OAuth
+ Web.Authenticate.OAuth: instance Ord PrivateKey
+ Web.Authenticate.OAuth: instance Ord SignMethod
+ Web.Authenticate.OAuth: instance Read Credential
+ Web.Authenticate.OAuth: instance Read OAuth
+ Web.Authenticate.OAuth: instance Read PrivateKey
+ Web.Authenticate.OAuth: instance Read SignMethod
+ Web.Authenticate.OAuth: instance Show Credential
+ Web.Authenticate.OAuth: instance Show OAuth
+ Web.Authenticate.OAuth: instance Show OAuthException
+ Web.Authenticate.OAuth: instance Show SignMethod
+ Web.Authenticate.OAuth: instance Typeable Credential
+ Web.Authenticate.OAuth: instance Typeable OAuth
+ Web.Authenticate.OAuth: instance Typeable OAuthException
+ Web.Authenticate.OAuth: instance Typeable PrivateKey
+ Web.Authenticate.OAuth: instance Typeable SignMethod
+ Web.Authenticate.OAuth: oauthAccessTokenUri :: OAuth -> String
+ Web.Authenticate.OAuth: oauthAuthorizeUri :: OAuth -> String
+ Web.Authenticate.OAuth: oauthCallback :: OAuth -> Maybe ByteString
+ Web.Authenticate.OAuth: oauthConsumerKey :: OAuth -> ByteString
+ Web.Authenticate.OAuth: oauthConsumerSecret :: OAuth -> ByteString
+ Web.Authenticate.OAuth: oauthRequestUri :: OAuth -> String
+ Web.Authenticate.OAuth: oauthServerName :: OAuth -> String
+ Web.Authenticate.OAuth: oauthSignatureMethod :: OAuth -> SignMethod
+ Web.Authenticate.OAuth: paramEncode :: ByteString -> ByteString
+ Web.Authenticate.OAuth: signOAuth :: OAuth -> Credential -> Request -> IO Request
+ Web.Authenticate.OAuth: unCredential :: Credential -> [(ByteString, ByteString)]
Files
- Web/Authenticate/OAuth.hs +222/−0
- authenticate.cabal +9/−2
+ Web/Authenticate/OAuth.hs view
@@ -0,0 +1,222 @@+{-# LANGUAGE DeriveDataTypeable, OverloadedStrings, StandaloneDeriving #-}+{-# OPTIONS_GHC -Wall -fno-warn-orphans #-}+module Web.Authenticate.OAuth+ ( -- * Data types+ OAuth(..), SignMethod(..), Credential(..),+ -- * Operations for credentials+ emptyCredential, insert, delete, inserts,+ -- * Signature+ signOAuth,+ -- * Url & operation for authentication+ authorizeUrl, getAccessToken, getTemporaryCredential, + -- * Utility Methods+ paramEncode+ ) where+import Network.HTTP.Enumerator+import Web.Authenticate.Internal (qsUrl)+import Data.Data+import qualified Data.ByteString.Char8 as BS+import qualified Data.ByteString.Lazy.Char8 as BSL+import Data.Maybe+import Control.Applicative+import Network.Wai.Parse+import Control.Exception+import Control.Monad+import Data.List (sortBy)+import System.Random+import Data.Char+import Data.Digest.Pure.SHA+import Data.ByteString.Base64+import Data.Time+import Numeric+import Network.Wai (ResponseHeader)+import Codec.Crypto.RSA (rsassa_pkcs1_v1_5_sign, ha_SHA1, PrivateKey(..))+++-- | Data type for OAuth client (consumer).+data OAuth = OAuth { oauthServerName :: String -- ^ Service name+ , oauthRequestUri :: String -- ^ URI to request temporary credential+ , oauthAccessTokenUri :: String -- ^ Uri to obtain access token+ , oauthAuthorizeUri :: String -- ^ Uri to authorize+ , oauthSignatureMethod :: SignMethod -- ^ Signature Method+ , oauthConsumerKey :: BS.ByteString -- ^ Consumer key+ , oauthConsumerSecret :: BS.ByteString -- ^ Consumer Secret+ , oauthCallback :: Maybe BS.ByteString -- ^ Callback uri to redirect after authentication+ } deriving (Show, Eq, Ord, Read, Data, Typeable)+++-- | Data type for signature method.+data SignMethod = PLAINTEXT+ | HMACSHA1+ | RSASHA1 PrivateKey+ deriving (Show, Eq, Ord, Read, Data, Typeable)++deriving instance Typeable PrivateKey+deriving instance Data PrivateKey+deriving instance Read PrivateKey+deriving instance Ord PrivateKey+deriving instance Eq PrivateKey++-- | Data type for redential.+data Credential = Credential { unCredential :: [(BS.ByteString, BS.ByteString)] }+ deriving (Show, Eq, Ord, Read, Data, Typeable)++-- | Empty credential.+emptyCredential :: Credential+emptyCredential = Credential []++token, tokenSecret :: Credential -> BS.ByteString+token = fromMaybe "" . lookup "oauth_token" . unCredential+tokenSecret = fromMaybe "" . lookup "oauth_token_secret" . unCredential++data OAuthException = ProtocolException String+ deriving (Show, Eq, Data, Typeable)++instance Exception OAuthException++toStrict :: BSL.ByteString -> BS.ByteString+toStrict = BS.concat . BSL.toChunks++fromStrict :: BS.ByteString -> BSL.ByteString+fromStrict = BSL.fromChunks . return++-- | Get temporary credential for requesting acces token.+getTemporaryCredential :: OAuth -- ^ OAuth Application+ -> IO Credential -- ^ Temporary Credential (Request Token & Secret).+getTemporaryCredential oa = do+ let req = fromJust $ parseUrl (oauthRequestUri oa)+ req' <- signOAuth oa emptyCredential (req { method = "POST" }) + rsp <- httpLbs req'+ let dic = parseQueryString . toStrict . responseBody $ rsp+ return $ Credential dic++-- | URL to obtain OAuth verifier.+authorizeUrl :: OAuth -- ^ OAuth Application+ -> Credential -- ^ Temporary Credential (Request Token & Secret)+ -> String -- ^ URL to authorize+authorizeUrl oa cr = qsUrl (oauthAuthorizeUri oa) [("oauth_token", BS.unpack $ token cr)]++-- | Get Access token.+getAccessToken :: OAuth -- ^ OAuth Application+ -> Credential -- ^ Temporary Credential with oauth_verifier+ -> IO Credential -- ^ Token Credential (Access Token & Secret)+getAccessToken oa cr = do+ let req = (fromJust $ parseUrl $ oauthAccessTokenUri oa) { method = "POST" }+ rsp <- signOAuth oa cr req >>= httpLbs+ let dic = parseQueryString . toStrict . responseBody $ rsp+ return $ Credential dic++insertMap :: Eq a => a -> b -> [(a,b)] -> [(a,b)]+insertMap key val = ((key,val):) . filter ((/=key).fst)++deleteMap :: Eq a => a -> [(a,b)] -> [(a,b)]+deleteMap k = filter ((/=k).fst)++-- | Insert an oauth parameter into given 'Credential'.+insert :: BS.ByteString -- ^ Parameter Name+ -> BS.ByteString -- ^ Value+ -> Credential -- ^ Credential+ -> Credential -- ^ Result+insert k v = Credential . insertMap k v . unCredential++-- | Convenient method for inserting multiple parameters into credential.+inserts :: [(BS.ByteString, BS.ByteString)] -> Credential -> Credential+inserts = flip $ foldr (uncurry insert)++-- | Remove an oauth parameter for key from given 'Credential'.+delete :: BS.ByteString -- ^ Parameter name+ -> Credential -- ^ Credential+ -> Credential -- ^ Result+delete key = Credential . deleteMap key . unCredential++-- | Add OAuth headers & sign to 'Request'.+signOAuth :: OAuth -- ^ OAuth Application+ -> Credential -- ^ Credential+ -> Request -- ^ Original Request+ -> IO Request -- ^ Signed OAuth Request+signOAuth oa crd req = do+ crd' <- addTimeStamp =<< addNonce crd+ let tok = injectOAuthToCred oa crd'+ sign = genSign oa tok req+ return $ addAuthHeader (insert "oauth_signature" sign tok) req++baseTime :: UTCTime+baseTime = UTCTime day 0+ where+ day = ModifiedJulianDay 40587++showSigMtd :: SignMethod -> BS.ByteString+showSigMtd PLAINTEXT = "PLAINTEXT"+showSigMtd HMACSHA1 = "HMAC-SHA1"+showSigMtd (RSASHA1 _) = "RSA-SHA1"++addNonce :: Credential -> IO Credential+addNonce cred = do+ nonce <- replicateM 10 (randomRIO ('a','z'))+ return $ insert "oauth_nonce" (BS.pack nonce) cred++addTimeStamp :: Credential -> IO Credential+addTimeStamp cred = do+ stamp <- floor . (`diffUTCTime` baseTime) <$> getCurrentTime :: IO Integer+ return $ insert "oauth_timestamp" (BS.pack $ show stamp) cred++injectOAuthToCred :: OAuth -> Credential -> Credential+injectOAuthToCred oa cred = maybe id (insert "oauth_callback") (oauthCallback oa) $ + inserts [ ("oauth_signature_method", showSigMtd $ oauthSignatureMethod oa)+ , ("oauth_consumer_key", oauthConsumerKey oa)+ ] cred++genSign :: OAuth -> Credential -> Request -> BS.ByteString+genSign oa tok req =+ case oauthSignatureMethod oa of+ HMACSHA1 ->+ let text = getBaseString tok req+ key = BS.intercalate "&" $ map paramEncode [oauthConsumerSecret oa, tokenSecret tok]+ in encode $ toStrict $ bytestringDigest $ hmacSha1 (fromStrict key) text+ PLAINTEXT ->+ BS.intercalate "&" $ map paramEncode [oauthConsumerSecret oa, tokenSecret tok]+ RSASHA1 pr ->+ encode $ toStrict $ rsassa_pkcs1_v1_5_sign ha_SHA1 pr (getBaseString tok req)++addAuthHeader :: Credential -> Request -> Request+addAuthHeader (Credential cred) req =+ req { requestHeaders = insertMap "Authorization" (renderAuthHeader cred) $ requestHeaders req }++renderAuthHeader :: [(BS.ByteString, BS.ByteString)] -> BS.ByteString+renderAuthHeader = ("OAuth " `BS.append`). BS.intercalate "," . map (\(a,b) -> BS.concat [paramEncode a, "=\"", paramEncode b, "\""]) . filter ((`notElem` ["oauth_token_secret", "oauth_consumer_secret"]) . fst)++-- | Encode a string using the percent encoding method for OAuth.+paramEncode :: BS.ByteString -> BS.ByteString+paramEncode = BS.concatMap escape+ where+ escape c | isAlpha c || isDigit c || c `elem` "-._~" = BS.singleton c+ | otherwise = let num = map toUpper $ showHex (ord c) ""+ oct = '%' : replicate (2 - length num) '0' ++ num+ in BS.pack oct++getBaseString :: Credential -> Request -> BSL.ByteString+getBaseString tok req =+ let bsMtd = BS.map toUpper $ method req+ isHttps = secure req+ scheme = if isHttps then "https" else "http"+ bsPort = if (isHttps && port req /= 443) || (not isHttps && port req /= 80)+ then ':' `BS.cons` BS.pack (show $ port req) else ""+ bsURI = BS.concat [scheme, "://", host req, bsPort, path req]+ bsQuery = queryString req+ bsBodyQ = if isBodyFormEncoded $ requestHeaders req+ then parseQueryString (toStrict $ requestBody req) else []+ bsAuthParams = filter ((`notElem`["oauth_signature","realm","oauth_version", "oauth_token_secret"]).fst) $ unCredential tok+ allParams = bsQuery++bsBodyQ++bsAuthParams+ bsParams = BS.intercalate "&" $ map (\(a,b)->BS.concat[a,"=",b]) $ sortBy compareTuple+ $ map (\(a,b) -> (paramEncode a,paramEncode b)) allParams+ in BSL.intercalate "&" $ map (fromStrict.paramEncode) [bsMtd, bsURI, bsParams]++isBodyFormEncoded :: [(ResponseHeader, BS.ByteString)] -> Bool+isBodyFormEncoded = maybe False (=="application/x-www-form-urlencoded") . lookup "Content-Type"++compareTuple :: (Ord a, Ord b) => (a, b) -> (a, b) -> Ordering+compareTuple (a,b) (c,d) =+ case compare a c of+ LT -> LT+ EQ -> compare b d+ GT -> GT
authenticate.cabal view
@@ -1,5 +1,5 @@ name: authenticate-version: 0.8.0.1+version: 0.8.1 license: BSD3 license-file: LICENSE author: Michael Snoyman <michael@snoyman.com>@@ -25,10 +25,17 @@ utf8-string >= 0.3 && < 0.4, network >= 2.2.1 && < 2.4, xml >= 1.3.7 && < 1.4,- wai >= 0.3 && < 0.4+ wai >= 0.3 && < 0.4,+ RSA >= 1.0 && < 1.1,+ time >= 1.1 && < 1.2,+ base64-bytestring >= 0.1 && < 0.2,+ SHA >= 1.4 && < 1.5,+ random >= 1.0 && < 1.1,+ wai-extra >= 0.3 && < 0.4 exposed-modules: Web.Authenticate.Rpxnow, Web.Authenticate.OpenId, Web.Authenticate.OpenId.Providers,+ Web.Authenticate.OAuth, Web.Authenticate.Facebook other-modules: Web.Authenticate.Internal, OpenId2.Discovery,