module Network.OpenID.Authentication (
CheckIdMode(..)
, authenticationURI
, verifyAuthentication
) where
import Codec.Binary.Base64
import Network.OpenID.Association.Manager
import Network.OpenID.HTTP
import Network.OpenID.Types
import Network.OpenID.Utils
import qualified Data.Digest.OpenSSL.AlternativeHMAC as HMAC
import Data.List
import MonadLib
import Network.HTTP
import Network.URI
import Numeric
import qualified Data.ByteString as BS
data CheckIdMode = Immediate | Setup
instance Show CheckIdMode where
show Immediate = "checkid_immediate"
show Setup = "checkid_setup"
instance Read CheckIdMode where
readsPrec _ s | "checkid_immediate" `isPrefixOf` s = [(Immediate, drop 17 s)]
| "checkid_setup" `isPrefixOf` s = [(Setup, drop 13 s)]
| otherwise = []
macHash :: AssocType -> HMAC.CryptoHashFunction
macHash HmacSha1 = HMAC.sha1
macHash HmacSha256 = HMAC.sha256
parseProvider' :: ExceptionM m Error => String -> m Provider
parseProvider' = maybe err return . parseProvider
where err = raise (Error "unable to parse openid.op_endpoint")
getSignedFields :: ExceptionM m Error => [String] -> Params -> m Params
getSignedFields ks ps = maybe err return (mapM lkp ks)
where
err = raise (Error "not all signed parameters present")
lkp k = (,) k `fmap` lookup ("openid." ++ k) ps
authenticationURI :: AssociationManager am =>
am
-> CheckIdMode
-> Provider
-> Identifier
-> ReturnTo
-> Maybe Params
-> Maybe Realm
-> URI
authenticationURI am mode prov ident rt mb_exts mb_realm =
addParams params (providerURI prov)
where
params = [ ("openid.ns", openidNS)
, ("openid.mode", show mode)
, ("openid.claimed_id", getIdentifier ident)
, ("openid.identity", getIdentifier ident)
, ("openid.return_to", rt)
] ++ ah ++
maybe [] id mb_exts ++
maybe [] (\r -> [("openid.realm", r)]) mb_realm
ah = case findAssociation am prov of
Nothing -> []
Just a -> [("openid.assoc_handle", assocHandle a)]
verifyAuthentication :: (Monad m, AssociationManager am)
=> am -> Params -> ReturnTo -> Resolver m
-> m (Either Error ())
verifyAuthentication am ps rto resolve =
runExceptionT $ do
u <- lookupParam "openid.return_to" ps
unless (u == rto) $ raise $ Error $ "invalid return path: " ++ u
prov <- parseProvider' =<< lookupParam "openid.op_endpoint" ps
case findAssociation am prov of
Nothing -> verifyDirect ps prov resolve
Just assoc -> verifyWithAssociation ps assoc
verifyDirect :: Monad m
=> Params -> Provider -> Resolver m -> ExceptionT Error m ()
verifyDirect ps prov resolve = do
let body = formatParams
$ ("openid.mode","check_authentication")
: filter (\p -> fst p /= "openid.mode") ps
ersp <- lift $ resolve Request
{ rqURI = providerURI prov
, rqMethod = POST
, rqHeaders =
[ Header HdrContentLength $ show $ length body
, Header HdrContentType "application/x-www-form-urlencoded"
]
, rqBody = body
}
withResponse ersp $ \rsp -> do
let rps = parseDirectResponse (rspBody rsp)
case lookup "is_valid" rps of
Just "true" -> return ()
_ -> raise (Error "invalid authentication request")
verifyWithAssociation :: Monad m
=> Params -> Association -> ExceptionT Error m ()
verifyWithAssociation ps a = do
mode <- lookupParam "openid.mode" ps
unless (mode == "id_res") $ raise $ Error $ "unexpected openid.mode: " ++ mode
sigParams <- breaks (',' ==) `fmap` lookupParam "openid.signed" ps
sig <- decode `fmap` lookupParam "openid.sig" ps
sps <- getSignedFields sigParams ps
let h = macHash (assocType a)
msg = map (toEnum . fromEnum) (formatDirectParams sps)
mc = HMAC.unsafeHMAC h (BS.pack (assocMacKey a)) (BS.pack msg)
sig' = case readHex mc of
[(x,"")] -> unroll x
_ -> []
unless (sig' == sig) $ raise $ Error "invalid signature"