packages feed

rails-session 0.1.0.0 → 0.1.1.0

raw patch · 4 files changed

+124/−44 lines, 4 filesdep +filepathdep +transformersPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: filepath, transformers

API changes (from Hackage documentation)

- Web.Rails.Session: instance Eq Cookie
- Web.Rails.Session: instance Eq DecryptedData
- Web.Rails.Session: instance Eq EncryptedData
- Web.Rails.Session: instance Eq InitVector
- Web.Rails.Session: instance Eq Salt
- Web.Rails.Session: instance Eq SecretKey
- Web.Rails.Session: instance Eq SecretKeyBase
- Web.Rails.Session: instance Ord Cookie
- Web.Rails.Session: instance Ord DecryptedData
- Web.Rails.Session: instance Ord EncryptedData
- Web.Rails.Session: instance Ord InitVector
- Web.Rails.Session: instance Ord Salt
- Web.Rails.Session: instance Ord SecretKey
- Web.Rails.Session: instance Ord SecretKeyBase
- Web.Rails.Session: instance Show Cookie
- Web.Rails.Session: instance Show DecryptedData
- Web.Rails.Session: instance Show EncryptedData
- Web.Rails.Session: instance Show InitVector
- Web.Rails.Session: instance Show Salt
- Web.Rails.Session: instance Show SecretKey
- Web.Rails.Session: instance Show SecretKeyBase
+ Web.Rails.Session: csrfToken :: RubyObject -> Maybe ByteString
+ Web.Rails.Session: data DecryptedData
+ Web.Rails.Session: instance GHC.Classes.Eq Web.Rails.Session.Cookie
+ Web.Rails.Session: instance GHC.Classes.Eq Web.Rails.Session.DecryptedData
+ Web.Rails.Session: instance GHC.Classes.Eq Web.Rails.Session.EncryptedData
+ Web.Rails.Session: instance GHC.Classes.Eq Web.Rails.Session.InitVector
+ Web.Rails.Session: instance GHC.Classes.Eq Web.Rails.Session.Salt
+ Web.Rails.Session: instance GHC.Classes.Eq Web.Rails.Session.SecretKey
+ Web.Rails.Session: instance GHC.Classes.Eq Web.Rails.Session.SecretKeyBase
+ Web.Rails.Session: instance GHC.Classes.Ord Web.Rails.Session.Cookie
+ Web.Rails.Session: instance GHC.Classes.Ord Web.Rails.Session.DecryptedData
+ Web.Rails.Session: instance GHC.Classes.Ord Web.Rails.Session.EncryptedData
+ Web.Rails.Session: instance GHC.Classes.Ord Web.Rails.Session.InitVector
+ Web.Rails.Session: instance GHC.Classes.Ord Web.Rails.Session.Salt
+ Web.Rails.Session: instance GHC.Classes.Ord Web.Rails.Session.SecretKey
+ Web.Rails.Session: instance GHC.Classes.Ord Web.Rails.Session.SecretKeyBase
+ Web.Rails.Session: instance GHC.Show.Show Web.Rails.Session.Cookie
+ Web.Rails.Session: instance GHC.Show.Show Web.Rails.Session.DecryptedData
+ Web.Rails.Session: instance GHC.Show.Show Web.Rails.Session.EncryptedData
+ Web.Rails.Session: instance GHC.Show.Show Web.Rails.Session.InitVector
+ Web.Rails.Session: instance GHC.Show.Show Web.Rails.Session.Salt
+ Web.Rails.Session: instance GHC.Show.Show Web.Rails.Session.SecretKey
+ Web.Rails.Session: instance GHC.Show.Show Web.Rails.Session.SecretKeyBase
+ Web.Rails.Session: sessionId :: RubyObject -> Maybe ByteString
+ Web.Rails.Session: unwrapDecryptedData :: DecryptedData -> ByteString

Files

+ CHANGELOG.md view
@@ -0,0 +1,10 @@+## 0.1.1.0++- Export DecryptedData type.+- Add function to unwrap DecryptedData.+- Add two additional helper functions.+- Improve documentation.++## 0.1.0.0++- Initial release.
rails-session.cabal view
@@ -1,5 +1,5 @@ name:                rails-session-version:             0.1.0.0+version:             0.1.1.0 synopsis:            Decrypt Ruby on Rails sessions in Haskell description:         Please see README.md homepage:            http://github.com/iconnect/rails-session#readme@@ -11,6 +11,7 @@ category:            Web build-type:          Simple cabal-version:       >=1.10+extra-source-files:  CHANGELOG.md  Source-repository head   type: git@@ -38,14 +39,12 @@   main-is:             Spec.hs   build-depends:       base                      , bytestring+                     , filepath                      , rails-session                      , ruby-marshal                      , tasty                      , tasty-hspec+                     , transformers                      , vector   ghc-options:         -threaded -rtsopts -with-rtsopts=-N   default-language:    Haskell2010--source-repository head-  type:     git-  location: https://github.com/iconnect/haskell-oss/rails-interop
src/Web/Rails/Session.hs view
@@ -10,6 +10,8 @@   -- * Decrypting   , decrypt   -- * Utilities+  , csrfToken+  , sessionId   , lookupString   , lookupFixnum   -- * Lifting weaker types into stronger types@@ -19,6 +21,8 @@   , mkSalt   , SecretKeyBase   , mkSecretKeyBase+  , DecryptedData+  , unwrapDecryptedData   ) where  import              Control.Applicative ((<$>))@@ -43,49 +47,68 @@  -- TYPES +-- | Wrapper around data after it has been decrypted. newtype DecryptedData =   DecryptedData ByteString   deriving (Show, Ord, Eq) +-- | Wrapper around data before it has been decrypted. newtype EncryptedData =   EncryptedData ByteString   deriving (Show, Ord, Eq) +-- | Wrapper around initialisation vector. newtype InitVector =   InitVector ByteString   deriving (Show, Ord, Eq) +-- | Wrapper around raw cookie. newtype Cookie =   Cookie ByteString   deriving (Show, Ord, Eq) +-- | Wrapper around salt. newtype Salt =   Salt ByteString   deriving (Show, Ord, Eq) +-- | Wrapper around secret. newtype SecretKey =   SecretKey ByteString   deriving (Show, Ord, Eq) +-- | Wrapper around secret key base. newtype SecretKeyBase =   SecretKeyBase ByteString   deriving (Show, Ord, Eq)  -- SMART CONSTRUCTORS +-- | Lift a cookie into a richer type. mkCookie :: ByteString -> Cookie mkCookie = Cookie +-- | Lift salt into a richer type. mkSalt :: ByteString -> Salt mkSalt = Salt +-- | Lifts secret into a richer type. mkSecretKeyBase :: ByteString -> SecretKeyBase mkSecretKeyBase = SecretKeyBase +-- SMART DESTRUCTORS++unwrapDecryptedData :: DecryptedData -> ByteString+unwrapDecryptedData (DecryptedData deData) =+  deData+ -- EXPORTS  -- | Decode a cookie encrypted by Rails.-decode :: Maybe Salt -> SecretKeyBase -> Cookie -> Maybe RubyObject+decode :: Maybe Salt+       -> SecretKeyBase+       -> Cookie+       -> Maybe RubyObject decode mbSalt secretKeyBase cookie =   either (const Nothing) Just (decodeEither mbSalt secretKeyBase cookie) @@ -108,26 +131,39 @@         -> Cookie         -> Either String DecryptedData decrypt mbSalt secretKeyBase cookie =-  let salt = fromMaybe (Salt "encrypted cookie") mbSalt+  let salt = fromMaybe defaultSalt mbSalt       (SecretKey secret) = generateSecret salt secretKeyBase-      key = BS.take 32 secret       (EncryptedData encData, InitVector initVec) = prepare cookie   in case makeIV initVec of        Nothing ->          Left $! "Failed to build init. vector for: " <> show initVec-       Just initVec' ->+       Just initVec' -> do+         let key = BS.take 32 secret          case (cipherInit key :: CryptoFailable AES256) of            CryptoFailed errorMessage ->              Left (show errorMessage)            CryptoPassed cipher ->              Right . DecryptedData $! cbcDecrypt cipher initVec' encData+  where+    defaultSalt :: Salt+    defaultSalt = Salt "encrypted cookie" +-- UTIL++-- | Helper function for looking up the csrf token in a cooie.+csrfToken :: RubyObject -> Maybe ByteString+csrfToken = lookupString "_csrf_token" US_ASCII++-- | Helper function for looking up the session id in a cookie.+sessionId :: RubyObject -> Maybe ByteString+sessionId = lookupString "session_id" UTF_8+ -- | Lookup integer for a given key. lookupFixnum :: ByteString -> RubyStringEncoding -> RubyObject -> Maybe Int-lookupFixnum k enc m =-  case lookup (RIVar (RString k, enc)) m of-    Just (RFixnum v) ->-      Just v+lookupFixnum key enc rubyObject =+  case lookup (RIVar (RString key, enc)) rubyObject of+    Just (RFixnum val) ->+      Just val     _ ->       Nothing @@ -136,14 +172,14 @@              -> RubyStringEncoding              -> RubyObject              -> Maybe ByteString-lookupString k enc m =-  case lookup (RIVar (RString k, enc)) m of-    Just (RIVar (RString v, _)) ->-      Just v+lookupString key enc rubyObject =+  case lookup (RIVar (RString key, enc)) rubyObject of+    Just (RIVar (RString val, _)) ->+      Just val     _ ->       Nothing --- UTIL+-- PRIVATE  -- | Generate secret key using same cryptographic routines as Rails. generateSecret :: Salt -> SecretKeyBase -> SecretKey
test/Spec.hs view
@@ -1,21 +1,28 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} -import Data.ByteString (ByteString)-import Data.Either (isRight)-import Data.Ruby.Marshal hiding (decodeEither)-import Data.Vector (fromList)-import Test.Tasty (defaultMain, testGroup)-import Test.Tasty.Hspec (describe, it, shouldBe, shouldSatisfy, testSpec, Spec)-import Web.Rails.Session+import           Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import           Data.Either (isRight)+import           Data.Monoid ((<>))+import           Data.Ruby.Marshal hiding (decodeEither)+import           Data.Vector (fromList)+import           System.IO.Unsafe (unsafePerformIO)+import           Test.Tasty (defaultMain, testGroup)+import           Test.Tasty.Hspec (describe, it, shouldBe, shouldSatisfy, testSpec, Spec)+import           Web.Rails.Session +-- SPECS+ main :: IO () main = do-  x <- testSpec "Web.Rails.Session" specs-  defaultMain (testGroup "All the Specs" [x])+  rails4 <- testSpec "Web.Rails.Session: Rails4" $ specsFor Rails4+  defaultMain (testGroup "All the Specs" [rails4]) -specs :: Spec-specs = do+specsFor :: Rails -> Spec+specsFor rails = do+  let cookie = unsafeReadCookie rails+   describe "decode" $ do     it "should be a Right(..)" $ do       let result = decodeEither Nothing secret cookie@@ -32,20 +39,34 @@       let result = decrypt Nothing secret cookie       result `shouldSatisfy` isRight +  describe "csrfToken" $ do+    it "should look up the '_csrf_token'" $ do+      case decodeEither Nothing secret cookie of+        Left _ -> error "lookup failed"+        Right result ->+          csrfToken result `shouldBe` Just csrfTokenVal++  describe "sessionId" $ do+    it "should look up the 'session_id'" $ do+      case decodeEither Nothing secret cookie of+        Left _ -> error "lookup failed"+        Right result ->+          sessionId result `shouldBe` Just sessionIdVal+   describe "lookupString" $ do     it "should look up the '_csrf_token'" $ do       case decodeEither Nothing secret cookie of         Left _ -> error "lookup failed"         Right result ->           lookupString "_csrf_token" US_ASCII result `shouldBe`-          Just csrfToken+          Just csrfTokenVal      it "should look up the 'session_id'" $ do       case decodeEither Nothing secret cookie of         Left _ -> error "lookup failed"         Right result ->           lookupString "session_id" UTF_8 result `shouldBe`-          Just sessionId+          Just sessionIdVal      it "should look up the 'token'" $ do       case decodeEither Nothing secret cookie of@@ -54,6 +75,27 @@           lookupString "token" US_ASCII result `shouldBe`           Just authToken +-- EXAMPLES++example :: FilePath -> IO (Maybe ByteString)+example path = do+  rawCookie <- BS.readFile path+  let appSecret = mkSecretKeyBase "development_secret_token"+  let cookie = mkCookie rawCookie+  case decodeEither Nothing appSecret cookie of+    Left _ ->+      pure Nothing+    Right rubyObject ->+      pure $ sessionId rubyObject++-- UTIL++data Rails = Rails4 deriving (Show)++unsafeReadCookie :: Rails -> Cookie+unsafeReadCookie rails = unsafePerformIO $+  BS.readFile ("test/" <> (show rails)) >>= pure . mkCookie+ -- CONFIG  secret :: SecretKeyBase@@ -64,28 +106,21 @@ authToken :: ByteString authToken = "GT1EYH9X8OXYqup4HwnQIvfnh59TqNys1IvukVXpXR8=" -sessionId :: ByteString-sessionId = "912a0abcf3d64e0d9d2bdb601b9e8224"--csrfToken :: ByteString-csrfToken = "c9kRzi8L7oj2MPI/QlqYpQ79WR6YfKTDob6PGl9V2pg="---- SESSIONS+sessionIdVal :: ByteString+sessionIdVal = "912a0abcf3d64e0d9d2bdb601b9e8224" -cookie :: Cookie-cookie =-  mkCookie $-  "T3NpYnRsWGJsZ25qL0R4MFlZdE9wZVBrZXc3VnFHMHhYMFgvemlVUjlTekZKbERXbVd2Mkwra0xxTmNOYnZaWktBQWo3T25RV3VPRkR6RU9BQ3dub2FSWExlZmZ1RUxVWG9vZjRTbWphRzBFSW5nMVJOSklPTVRPRGxKN0tvdGxhZzVlZktLTmhxc0V1a1FCWHpwNVJGTjdON0JCbjZQWHM0R1M1SWIzeUkzalhVNWdVbnd2Z2E5RlBMSXcvR0tNSHVOZ0NiK1RBTzVxcCtMK3hJa1daYW13allNb1NyN3pOUTFBQ0tRcFNEdUVJelVMZE8rVXhwM3RhcHFONWRwby9kRStNNkRUVXNQTDNKcjF0ZWpGTUE9PS0tb3NGeEJiZ1dLeFFKcFV0WXgyUytnZz09--e996601dbf39ff5ffbf6e2f23f6ddf78c5179baa"+csrfTokenVal :: ByteString+csrfTokenVal = "c9kRzi8L7oj2MPI/QlqYpQ79WR6YfKTDob6PGl9V2pg="  rubySession :: RubyObject rubySession =   RHash     (fromList        [ ( RIVar (RString "session_id", UTF_8)-         , RIVar (RString sessionId, UTF_8))+         , RIVar (RString sessionIdVal, UTF_8))        , ( RIVar (RString "_csrf_token", US_ASCII)          , RIVar-             (RString csrfToken, US_ASCII))+             (RString csrfTokenVal, US_ASCII))        , ( RIVar (RString "token", US_ASCII)          , RIVar (RString authToken, UTF_8))        ])