module Network.OpenID.Discovery (
discover
) where
import Network.OpenID.Types
import Text.XRDS
import Data.Char
import Data.List
import Data.Maybe
import MonadLib
import Network.HTTP
import Network.URI
type M = ExceptionT Error
discover :: Monad m
=> Resolver m -> Identifier -> m (Either Error (Provider,Identifier))
discover resolve ident = do
res <- runExceptionT (discoverYADIS resolve ident Nothing)
case res of
Right {} -> return res
_ -> runExceptionT (discoverHTML resolve ident)
discoverYADIS :: Monad m
=> Resolver m -> Identifier -> Maybe String
-> M m (Provider,Identifier)
discoverYADIS resolve ident mb_loc = do
let err = raise . Error
uri = fromMaybe (getIdentifier ident) mb_loc
case parseURI uri of
Nothing -> err "Unable to parse identifier as a URI"
Just u -> do
estr <- lift $ resolve Request
{ rqMethod = GET
, rqURI = u
, rqHeaders = []
, rqBody = ""
}
case estr of
Left e -> err $ "HTTP request error: " ++ show e
Right rsp -> case rspCode rsp of
(2,0,0) -> case findHeader (HdrCustom "X-XRDS-Location") rsp of
Just loc -> discoverYADIS resolve ident (Just loc)
_ -> do
let e = err "Unable to parse YADIS document"
doc <- maybe e return $ parseXRDS $ rspBody rsp
parseYADIS ident doc
_ -> err $ "HTTP request error: unexpected response code "++show (rspCode rsp)
parseYADIS :: ExceptionM m Error
=> Identifier -> XRDS -> m (Provider,Identifier)
parseYADIS ident = handleError . listToMaybe . mapMaybe isOpenId . concat
where
handleError = maybe e return
where e = raise (Error "YADIS document doesn't include an OpenID provider")
isOpenId svc = do
let tys = serviceTypes svc
localId = maybe ident Identifier $ listToMaybe $ serviceLocalIDs svc
f (x,y) | x `elem` tys = Just y
| otherwise = mzero
lid <- listToMaybe $ mapMaybe f
[ ("http://specs.openid.net/auth/2.0/server", ident)
, ("http://specs.openid.net/auth/2.0/signon", localId)
, ("http://openid.net/signon/1.0" , localId)
, ("http://openid.net/signon/1.1" , localId)
]
uri <- parseProvider =<< listToMaybe (serviceURIs svc)
return (uri,lid)
discoverHTML :: Monad m
=> Resolver m -> Identifier -> M m (Provider,Identifier)
discoverHTML resolve ident = do
let err = raise . Error
case parseURI (getIdentifier ident) of
Nothing -> err "Unable to parse identifier as a URI"
Just uri -> do
estr <- lift $ resolve Request
{ rqMethod = GET
, rqURI = uri
, rqHeaders = []
, rqBody = ""
}
case estr of
Left e -> err $ "HTTP request error: " ++ show e
Right rsp -> case rspCode rsp of
(2,0,0) -> maybe (err "Unable to find identifier in HTML") return
$ parseHTML ident $ rspBody rsp
_ -> err $ "HTTP request error: unexpected response code "++show (rspCode rsp)
parseHTML :: Identifier -> String -> Maybe (Provider,Identifier)
parseHTML ident = resolve
. filter isOpenId
. linkTags
. htmlTags
where
isOpenId (rel,_) = "openid" `isPrefixOf` rel
resolve ls = do
prov <- parseProvider =<< lookup "openid2.provider" ls
let lid = maybe ident Identifier $ lookup "openid2.local_id" ls
return (prov,lid)
linkTags :: [String] -> [(String,String)]
linkTags = mapMaybe f . filter p
where
p = ("link " `isPrefixOf`)
f xs = do
let ys = unfoldr splitAttr (drop 5 xs)
x <- lookup "rel" ys
y <- lookup "href" ys
return (x,y)
htmlTags :: String -> [String]
htmlTags [] = []
htmlTags xs = case break (== '<') xs of
(as,_:bs) -> fmt as : htmlTags bs
(as,[]) -> [as]
where
fmt as = case break (== '>') as of
(bs,_) -> bs
splitAttr :: String -> Maybe ((String,String),String)
splitAttr xs = case break (== '=') xs of
(_,[]) -> Nothing
(key,_:'"':ys) -> f key (== '"') ys
(key,_:ys) -> f key isSpace ys
where
f key p cs = case break p cs of
(_,[]) -> Nothing
(value,_:rest) -> Just ((key,value), dropWhile isSpace rest)