Added openid authentication

This commit is contained in:
Snoyman 2009-05-08 10:29:35 +03:00
parent 082e3241c7
commit d920705bde
3 changed files with 152 additions and 4 deletions

147
Web/Authenticate/OpenId.hs Normal file
View File

@ -0,0 +1,147 @@
{-# LANGUAGE FlexibleInstances #-}
---------------------------------------------------------
-- |
-- Module : Web.Authenticate.OpenId
-- Copyright : Michael Snoyman
-- License : BSD3
--
-- Maintainer : Michael Snoyman <michael@snoyman.com>
-- Stability : Unstable
-- Portability : portable
--
-- Provides functionality for being an OpenId consumer.
--
---------------------------------------------------------
module Web.Authenticate.OpenId
( Identifier (..)
, getForwardUrl
, authenticate
) where
import Data.Maybe (fromMaybe, fromJust)
import Network.HTTP.Wget
import Text.HTML.TagSoup
import Numeric (showHex)
-- | An openid identifier (ie, a URL).
data Identifier = Identifier { identifier :: String }
instance Monad (Either String) where
return = Right
fail = Left
(Left s) >>= _ = Left s
(Right x) >>= f = f x
-- | Returns a URL to forward the user to in order to login.
getForwardUrl :: Monad m
=> String -- ^ The openid the user provided.
-> String -- ^ The URL for this application\'s complete page.
-> IO (m String) -- ^ URL to send the user to.
getForwardUrl openid complete = do
bodyIdent' <- wget openid [] []
case bodyIdent' of
Left s -> return $ fail s
Right bodyIdent -> do
server <- getOpenIdVar "server" bodyIdent
let delegate = fromMaybe openid $ getOpenIdVar "delegate" bodyIdent
return $ return $ constructUrl server
[ ("openid.mode", "checkid_setup")
, ("openid.identity", delegate)
, ("openid.return_to", complete)
]
getOpenIdVar :: Monad m => String -> String -> m String
getOpenIdVar var content = do
let tags = parseTags content
let secs = sections (~== ("<link rel=openid." ++ var ++ ">")) tags
secs' <- mhead secs
secs'' <- mhead secs'
return $ fromAttrib "href" secs''
where
mhead [] = fail $ "Variable not found: openid." ++ var
mhead (x:_) = return x
constructUrl :: String -> [(String, String)] -> String
constructUrl url [] = url
constructUrl url args = url ++ "?" ++ queryString args
where
queryString [] = error "queryString with empty args cannot happen"
queryString [first] = onePair first
queryString (first:rest) = onePair first ++ "&" ++ queryString rest
onePair (x, y) = urlEncode x ++ "=" ++ urlEncode y
-- | Handle a redirect from an OpenID provider and check that the user
-- logged in properly. If it was successfully, 'return's the openid.
-- Otherwise, 'fail's an explanation.
authenticate :: Monad m => [(String, String)] -> IO (m Identifier)
authenticate req = do -- FIXME check openid.mode == id_res (not cancel)
authUrl' <- getAuthUrl req
case authUrl' of
Nothing -> return $ fail "Invalid parameters"
Just authUrl -> do
content' <- wget authUrl [] []
case content' of
Left s -> return $ fail s
Right content -> do
let isValid = contains "is_valid:true" content
if isValid
then return $
return $ Identifier
(fromJust $ lookup "openid.identity" req)
else return $ fail content
getAuthUrl :: [(String, String)] -> IO (Maybe String)
getAuthUrl req = do
let identity' = lookup "openid.identity" req
case identity' of
Nothing -> return Nothing
Just identity -> do
idContent <- wget identity [] []
case idContent of
Nothing -> return Nothing
Just x -> return $ helper x
where
helper :: String -> Maybe String
helper idContent = do
server <- getOpenIdVar "server" idContent
dargs <- mapM makeArg [
"assoc_handle",
"sig",
"signed",
"identity",
"return_to"
]
let sargs = [("openid.mode", "check_authentication")]
return $ constructUrl server $ dargs ++ sargs
makeArg :: String -> Maybe (String, String)
makeArg s = do
let k = "openid." ++ s
v <- lookup k req
return (k, v)
contains :: String -> String -> Bool
contains [] _ = True
contains _ [] = False
contains needle haystack =
begins needle haystack ||
(contains needle $ tail haystack)
begins :: String -> String -> Bool
begins [] _ = True
begins _ [] = False
begins (x:xs) (y:ys) = x == y && begins xs ys
urlEncode :: String -> String
urlEncode = concatMap urlEncodeChar
urlEncodeChar :: Char -> String
urlEncodeChar x
| safeChar (fromEnum x) = return x
| otherwise = '%' : showHex (fromEnum x) ""
safeChar :: Int -> Bool
safeChar x
| x >= fromEnum 'a' && x <= fromEnum 'z' = True
| x >= fromEnum 'A' && x <= fromEnum 'Z' = True
| x >= fromEnum '0' && x <= fromEnum '9' = True
| otherwise = False

View File

@ -1,5 +1,5 @@
---------------------------------------------------------
-- |
--
-- Module : Web.Authenticate.Rpxnow
-- Copyright : Michael Snoyman
-- License : BSD3

View File

@ -13,6 +13,7 @@ cabal-version: >= 1.2
build-type: Simple
library
build-depends: base, json, http-wget
exposed-modules: Web.Authenticate.Rpxnow
ghc-options: -Wall
build-depends: base, json, http-wget, tagsoup
exposed-modules: Web.Authenticate.Rpxnow,
Web.Authenticate.OpenId
ghc-options: -Wall -fno-warn-orphans