{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
module Web.Rails3.Session
(
decodeEither
, decode
, lookupUserIds
)
where
import Crypto.Hash as Hash
import Crypto.MAC.HMAC as HMAC
import Data.ByteString
import Data.ByteString as BS
import qualified Data.ByteString.Base16 as B16
import qualified Data.ByteString.Base64 as B64
import qualified Data.Map.Strict as Map
import Data.Ruby.Marshal as Marshal hiding (decode, decodeEither)
import qualified Data.Ruby.Marshal as Marshal (decodeEither)
import Data.Ruby.Marshal.RubyObject
import Web.Rails.Session.Types
import Network.HTTP.Types (urlDecode)
import Data.List.NonEmpty as NE
import Data.List as DL
import Prelude (Either(..), (>>=), (.), (==), ($), Maybe(..), return, Num(..), Int, fromIntegral, Bool(..), fst, String, either, id, const)
maybeToEither :: a -> Maybe b -> Either a b
maybeToEither :: forall a b. a -> Maybe b -> Either a b
maybeToEither a
_ (Just b
b) = b -> Either a b
forall a b. b -> Either a b
Right b
b
maybeToEither a
a Maybe b
Nothing = a -> Either a b
forall a b. a -> Either a b
Left a
a
decode :: Secret -> Cookie -> Maybe RubyObject
decode :: Secret -> Cookie -> Maybe RubyObject
decode Secret
s Cookie
c = (String -> Maybe RubyObject)
-> (RubyObject -> Maybe RubyObject)
-> Either String RubyObject
-> Maybe RubyObject
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either (Maybe RubyObject -> String -> Maybe RubyObject
forall a b. a -> b -> a
const Maybe RubyObject
forall a. Maybe a
Nothing) RubyObject -> Maybe RubyObject
forall a. a -> Maybe a
Just (Either String RubyObject -> Maybe RubyObject)
-> Either String RubyObject -> Maybe RubyObject
forall a b. (a -> b) -> a -> b
$ Secret -> Cookie -> Either String RubyObject
decodeEither Secret
s Cookie
c
decodeEither :: Secret -> Cookie -> Either String RubyObject
decodeEither :: Secret -> Cookie -> Either String RubyObject
decodeEither (Secret ByteString
cookieSecret) (Cookie ByteString
x) =
Either String (Digest SHA1)
extractChecksum
Either String (Digest SHA1)
-> (Digest SHA1 -> Either String ByteString)
-> Either String ByteString
forall a b.
Either String a -> (a -> Either String b) -> Either String b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Digest SHA1 -> Either String ByteString
compareChecksum
Either String ByteString
-> (ByteString -> Either String RubyObject)
-> Either String RubyObject
forall a b.
Either String a -> (a -> Either String b) -> Either String b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= ByteString -> Either String RubyObject
Marshal.decodeEither
where
extractChecksum :: Either String (Digest SHA1)
extractChecksum :: Either String (Digest SHA1)
extractChecksum = do
ByteString
decoded <- ByteString -> Either String ByteString
B16.decode ByteString
hexChecksum
String -> Maybe (Digest SHA1) -> Either String (Digest SHA1)
forall a b. a -> Maybe b -> Either a b
maybeToEither String
"[Rails3 Cookie] Illegal checksum in cookie. Wasn't able to extract a valid HMAC checksum out of it."
(ByteString -> Maybe (Digest SHA1)
forall a ba.
(HashAlgorithm a, ByteArrayAccess ba) =>
ba -> Maybe (Digest a)
Hash.digestFromByteString ByteString
decoded)
compareChecksum :: Digest SHA1 -> Either String ByteString
compareChecksum :: Digest SHA1 -> Either String ByteString
compareChecksum Digest SHA1
checksum = if (Digest SHA1
computedChecksum Digest SHA1 -> Digest SHA1 -> Bool
forall a. Eq a => a -> a -> Bool
== Digest SHA1
checksum) then (ByteString -> Either String ByteString
forall a b. b -> Either a b
Right (ByteString -> Either String ByteString)
-> ByteString -> Either String ByteString
forall a b. (a -> b) -> a -> b
$ ByteString -> ByteString
B64.decodeLenient ByteString
b64) else (String -> Either String ByteString
forall a b. a -> Either a b
Left String
"[Rails3 Cookie] Checksum doesn't match")
computedChecksum :: Digest SHA1
computedChecksum :: Digest SHA1
computedChecksum = HMAC SHA1 -> Digest SHA1
forall a. HMAC a -> Digest a
HMAC.hmacGetDigest (ByteString -> ByteString -> HMAC SHA1
forall key message a.
(ByteArrayAccess key, ByteArrayAccess message, HashAlgorithm a) =>
key -> message -> HMAC a
HMAC.hmac ByteString
cookieSecret ByteString
b64 :: HMAC SHA1)
(ByteString
b64, ByteString
hexChecksum) = let (ByteString
a, ByteString
b) = (ByteString -> ByteString -> (ByteString, ByteString)
breakSubstring ByteString
delimiter (ByteString -> (ByteString, ByteString))
-> ByteString -> (ByteString, ByteString)
forall a b. (a -> b) -> a -> b
$ Bool -> ByteString -> ByteString
urlDecode Bool
False ByteString
x)
in (ByteString
a, Int -> ByteString -> ByteString
BS.drop (ByteString -> Int
BS.length ByteString
delimiter) ByteString
b)
delimiter :: ByteString
delimiter = ByteString
"--"
safeHead :: [a] -> Maybe a
safeHead :: forall a. [a] -> Maybe a
safeHead [] = Maybe a
forall a. Maybe a
Nothing
safeHead (a
x:[a]
_) = a -> Maybe a
forall a. a -> Maybe a
Just a
x
lookupKey :: (Rubyable a) => (BS.ByteString, RubyStringEncoding) -> RubyObject -> Maybe a
lookupKey :: forall a.
Rubyable a =>
(ByteString, RubyStringEncoding) -> RubyObject -> Maybe a
lookupKey (ByteString, RubyStringEncoding)
key RubyObject
robj = (RubyObject
-> Maybe (Map (ByteString, RubyStringEncoding) RubyObject)
forall a. Rubyable a => RubyObject -> Maybe a
fromRuby RubyObject
robj :: Maybe (Map.Map (BS.ByteString, RubyStringEncoding) RubyObject))
Maybe (Map (ByteString, RubyStringEncoding) RubyObject)
-> (Map (ByteString, RubyStringEncoding) RubyObject
-> Maybe RubyObject)
-> Maybe RubyObject
forall a b. Maybe a -> (a -> Maybe b) -> Maybe b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (ByteString, RubyStringEncoding)
-> Map (ByteString, RubyStringEncoding) RubyObject
-> Maybe RubyObject
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup (ByteString, RubyStringEncoding)
key
Maybe RubyObject -> (RubyObject -> Maybe a) -> Maybe a
forall a b. Maybe a -> (a -> Maybe b) -> Maybe b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= RubyObject -> Maybe a
forall a. Rubyable a => RubyObject -> Maybe a
fromRuby
lookupUserIds :: (Num a) => RubyObject -> Maybe (NonEmpty a)
lookupUserIds :: forall a. Num a => RubyObject -> Maybe (NonEmpty a)
lookupUserIds RubyObject
robj =
(ByteString, RubyStringEncoding) -> RubyObject -> Maybe RubyObject
forall a.
Rubyable a =>
(ByteString, RubyStringEncoding) -> RubyObject -> Maybe a
lookupKey (ByteString
"warden.user.user.key", RubyStringEncoding
UTF_8) RubyObject
robj
Maybe RubyObject
-> (RubyObject -> Maybe [RubyObject]) -> Maybe [RubyObject]
forall a b. Maybe a -> (a -> Maybe b) -> Maybe b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (\RubyObject
x -> RubyObject -> Maybe [RubyObject]
forall a. Rubyable a => RubyObject -> Maybe a
fromRuby RubyObject
x :: Maybe [RubyObject])
Maybe [RubyObject]
-> ([RubyObject] -> Maybe RubyObject) -> Maybe RubyObject
forall a b. Maybe a -> (a -> Maybe b) -> Maybe b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= [RubyObject] -> Maybe RubyObject
forall a. [a] -> Maybe a
safeHead
Maybe RubyObject -> (RubyObject -> Maybe [Int]) -> Maybe [Int]
forall a b. Maybe a -> (a -> Maybe b) -> Maybe b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (\RubyObject
x -> RubyObject -> Maybe [Int]
forall a. Rubyable a => RubyObject -> Maybe a
fromRuby RubyObject
x :: Maybe [Int])
Maybe [Int] -> ([Int] -> Maybe [a]) -> Maybe [a]
forall a b. Maybe a -> (a -> Maybe b) -> Maybe b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (\[Int]
xs -> [a] -> Maybe [a]
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return ([a] -> Maybe [a]) -> [a] -> Maybe [a]
forall a b. (a -> b) -> a -> b
$ (Int -> a) -> [Int] -> [a]
forall a b. (a -> b) -> [a] -> [b]
DL.map Int -> a
forall a b. (Integral a, Num b) => a -> b
fromIntegral [Int]
xs)
Maybe [a] -> ([a] -> Maybe (NonEmpty a)) -> Maybe (NonEmpty a)
forall a b. Maybe a -> (a -> Maybe b) -> Maybe b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= [a] -> Maybe (NonEmpty a)
forall a. [a] -> Maybe (NonEmpty a)
NE.nonEmpty