packages feed

asap 0.0.2 → 0.0.3

raw patch · 4 files changed

+156/−51 lines, 4 filesdep −base64-bytestringdep ~mtldep ~textPVP ok

version bump matches the API change (PVP)

Dependencies removed: base64-bytestring

Dependency ranges changed: mtl, text

API changes (from Hackage documentation)

+ Web.JWT.ASAP: AsapInvalidSecret :: AsapError
+ Web.JWT.ASAP: AsapMissingEnv :: String -> AsapError
+ Web.JWT.ASAP: asapSignerFromEnv :: (HasAsapError e, MonadError e m, MonadEnv m) => m Signer
+ Web.JWT.ASAP: data AsapError

Files

asap.cabal view
@@ -1,5 +1,5 @@ name:                asap-version:             0.0.2+version:             0.0.3 synopsis:            Atlassian Service Authentication Protocol description:         Atlassian Service Authentication Protocol. homepage:            https://bitbucket.org/atlassian-marketplace/haskell-asap@@ -21,7 +21,6 @@   other-modules:       Web.JWT.ASAP.Env                      , Web.JWT.ASAP.Error   build-depends:       base                 >= 4.8 && < 4.13-                     , base64-bytestring    >= 1.0 && < 1.1                      , bytestring           >= 0.10.8 && < 0.11                      , jwt                  >= 0.10 && < 0.11                      , mtl                  >= 2.2.2 && < 2.3@@ -33,9 +32,13 @@  test-suite asap-tests   type:                exitcode-stdio-1.0-  main-is:             test/Main.hs+  hs-source-dirs:      test+  main-is:             Main.hs+  other-modules:       Util   build-depends:       asap                      , base+                     , mtl                      , time                      , jwt+                     , text                      , hedgehog             >= 0.6 && < 0.7
lib/Web/JWT/ASAP.hs view
@@ -15,26 +15,27 @@ , asapReadRsaSecret , asapAuthHeader , asapAuthHeaderFromEnv+, asapSignerFromEnv , laterThanMaxAge ) where -import           Control.Applicative    (liftA2)-import           Control.Lens           (view, ( # ))-import           Control.Monad.Except   (MonadError (..))-import           Data.ByteString        (ByteString)-import           Data.ByteString.Base64 (decodeLenient)-import           Data.ByteString.Lens   (packedChars)-import           Data.IORef             (newIORef, readIORef, writeIORef)-import           Data.String            (fromString)-import qualified Data.Text              as T-import           Data.Time              (NominalDiffTime)-import           Data.Time.Clock.POSIX  (getPOSIXTime)-import           Data.UUID              (UUID)-import qualified Data.UUID              as UUID-import qualified Data.UUID.V4           as UUID-import qualified Web.JWT                as JWT-import           Web.JWT.ASAP.Env       (MonadEnv (..), asapLookupEnv)-import           Web.JWT.ASAP.Error     (HasAsapError (..))+import           Control.Applicative   (liftA2)+import           Control.Lens          (view, ( # ), _tail)+import           Control.Monad.Except  (MonadError (..))+import           Data.ByteString       (ByteString)+import           Data.ByteString.Char8 as BS (unlines)+import           Data.ByteString.Lens  (packedChars)+import           Data.IORef            (newIORef, readIORef, writeIORef)+import           Data.String           (fromString)+import qualified Data.Text             as T+import           Data.Time             (NominalDiffTime)+import           Data.Time.Clock.POSIX (getPOSIXTime)+import           Data.UUID             (UUID)+import qualified Data.UUID             as UUID+import qualified Data.UUID.V4          as UUID+import qualified Web.JWT               as JWT+import           Web.JWT.ASAP.Env+import           Web.JWT.ASAP.Error  newtype Expiry   = Expiry NominalDiffTime@@ -69,7 +70,7 @@ expiringClaim ::   Expiry   -> IO JWT.JWTClaimsSet-expiringClaim expiry = do+expiringClaim expiry =   liftA2 (timedClaim expiry) getPOSIXTime UUID.nextRandom  maxAgeClaimGenerator' ::@@ -80,8 +81,8 @@   -> (JWT.JWTClaimsSet -> m ())   -> m JWT.JWTClaimsSet   -> m JWT.JWTClaimsSet-maxAgeClaimGenerator' maxAge time newClaim =-  regenerateWhen predicate newClaim+maxAgeClaimGenerator' maxAge time =+  regenerateWhen predicate   where     predicate claim =       maybe (pure False) (\iat -> laterThanMaxAge maxAge iat <$> time) $ JWT.iat claim@@ -121,25 +122,33 @@ asapAuthHeaderFromEnv header claim = do   issuer <- asapLookupEnv "ASAP_ISSUER"   keyId <- asapLookupEnv "ASAP_KEY_ID"-  dataUri <- asapLookupEnv "ASAP_PRIVATE_KEY"-  let pem = decodeLenient . view packedChars $ dataUriData dataUri-      header' = header { JWT.kid = Just $ fromString keyId }+  let header' = header { JWT.kid = Just $ fromString keyId }       claim' = claim { JWT.iss = JWT.stringOrURI $ fromString issuer }-  signer <- asapReadRsaSecret pem+  signer <- asapSignerFromEnv   pure (asapAuthHeader signer header' claim') +asapSignerFromEnv ::+  (HasAsapError e, MonadError e m, MonadEnv m) =>+  m JWT.Signer+asapSignerFromEnv = do+  pem <- toPem . view packedChars . dataUriData <$> asapLookupEnv "ASAP_PRIVATE_KEY"+  asapReadRsaSecret pem+  where+    toPem c =+      BS.unlines [ "-----BEGIN RSA PRIVATE KEY-----", c, "-----END RSA PRIVATE KEY-----" ]+ dataUriData ::   String   -> String dataUriData =-  snd . break (== ',')+  view _tail . dropWhile (/= ',')  laterThanMaxAge ::   MaxAge   -> JWT.NumericDate   -> NominalDiffTime   -> Bool-laterThanMaxAge (MaxAge maxAgeTime) iat time = do+laterThanMaxAge (MaxAge maxAgeTime) iat time =   time - JWT.secondsSinceEpoch iat >= maxAgeTime  regenerateWhen ::
test/Main.hs view
@@ -2,31 +2,15 @@  module Main where -import           Control.Monad  (unless)-import           Data.Time+import           Control.Monad (unless, void)+import           Data.Text     (Text) import           Hedgehog-import qualified Hedgehog.Gen   as Gen-import qualified Hedgehog.Range as Range-import           System.Exit    (exitFailure)-import           System.IO      (BufferMode (..), hSetBuffering, stderr, stdout)-import qualified Web.JWT        as JWT+import           System.Exit   (exitFailure)+import           System.IO     (BufferMode (..), hSetBuffering, stderr, stdout)+import qualified Web.JWT       as JWT import           Web.JWT.ASAP -nominalYear :: NominalDiffTime-nominalYear =-  nominalDay * 365--nominalDecade :: NominalDiffTime-nominalDecade =-  nominalYear * 10--genInDecade :: Gen NominalDiffTime-genInDecade =-  Gen.realFrac_ (Range.constant 0 nominalDecade)--genMaxAge :: Gen MaxAge-genMaxAge =-  MaxAge <$> Gen.realFrac_ (Range.constant 0 (9 * 60))+import           Util  isExpiredProp :: Property isExpiredProp =@@ -36,6 +20,31 @@     -- numericDate rounds so we have to + 1 to make a later time     assert $ maybe False (\iat' -> laterThanMaxAge maxAge iat' (iat + maxAgeTime + 1)) (JWT.numericDate iat) +asapSignerFromEnvProp :: Property+asapSignerFromEnvProp =+  withTests 1 . property $ do+    let signer = runWithEnv asapSignerFromEnv [("ASAP_PRIVATE_KEY", exampleDataUri)]+    void signer === Right ()++asapAuthHeaderFromEnvProp :: Property+asapAuthHeaderFromEnvProp =+  withTests 1 . property $ do+    let env = [ ("ASAP_PRIVATE_KEY", exampleDataUri)+              , ("ASAP_ISSUER", "haskell-asap")+              , ("ASAP_KEY_ID", "haskell-asap-demo1")+              ]+        header = runWithEnv (asapAuthHeaderFromEnv mempty mempty) env+    header === Right ("Bearer " <> emptyClaimHeader)++emptyClaimHeader :: Text+emptyClaimHeader =+  "eyJhbGciOiJSUzI1NiIsImtpZCI6Imhhc2tlbGwtYXNhcC1kZW1vMSIsInR5cCI6IkpXVCJ9.eyJpc3M\+  \iOiJoYXNrZWxsLWFzYXAifQ.E95y64Fc42ulqZ--3jClZiPqXJiaOCucceJwJLFWaBYA7Evlb7fhLGy-\+  \QJTzZ7_nHTfK6omspCObPT2DA-KNNT63DjShsW5cZjyCB2zMhtnWFB63wttlIoxs7EePBI8QpmdNcCTt\+  \cokMIL0yULSE-9BZaPehs38yo71fHH3Zzk9LLnPf76FkuTsCmKi26NcL2WcQV3tSrHro1upyGq3i29dt\+  \60dkokO6plCXQFq_CNfNneytUWg0LpqJhPCO0pAiiVedEjobi8tHdJgNzD81v-QgBPr2k1eqnMtYMjzs\+  \wq_g-HxCO_rtjrgGBhcovRvIZm1-ssAJVg9JA9cGGdxq_Q"+ main :: IO () main = do   hSetBuffering stdout LineBuffering@@ -43,6 +52,8 @@    results <- checkParallel $     Group "ASAP" [ ("laterThanMaxAge", isExpiredProp)+                 , ("asapSignerFromEnv", asapSignerFromEnvProp)+                 , ("asapAuthHeaderFromEnvProp", asapAuthHeaderFromEnvProp)                  ]    unless results exitFailure
+ test/Util.hs view
@@ -0,0 +1,82 @@+{-# LANGUAGE MultiParamTypeClasses #-}++module Util where++import           Control.Monad.Except+import           Control.Monad.Reader+import           Data.Time+import           Hedgehog             (Gen)+import qualified Hedgehog.Gen         as Gen+import qualified Hedgehog.Range       as Range+import           Web.JWT.ASAP++newtype PureEnv a =+  PureEnv { runPureEnv :: ReaderT [(String, String)] (Either AsapError) a }++runWithEnv :: PureEnv a -> [(String, String)] -> Either AsapError a+runWithEnv =+  runReaderT . runPureEnv++instance Functor PureEnv where+  fmap f =+    PureEnv . fmap f . runPureEnv++instance Applicative PureEnv where+  pure =+    PureEnv . pure+  f <*> fa =+    PureEnv (runPureEnv f <*> runPureEnv fa)++instance Monad PureEnv where+  fa >>= f =+    PureEnv (runPureEnv fa >>= runPureEnv . f)++instance MonadError AsapError PureEnv where+  throwError =+    PureEnv . throwError+  catchError fa f =+    PureEnv (catchError (runPureEnv fa) (runPureEnv . f))++instance MonadEnv PureEnv where+  lookupEnv' s =+    PureEnv (ReaderT (pure . lookup s))++exampleDataUri :: String+exampleDataUri =+  "data:application/pkcs8;kid=haskell-asap-test;base64,MIIEvQIBADANBgkqhkiG9w0BAQEF\+  \AASCBKcwggSjAgEAAoIBAQC/1EaVWX/IopFgxptgkduG0a5IC3PTyvhKtvxXhOCCGueL0dpMunAAKzsy\+  \xCSJsU3DQWMu3xeH+aqPI2aIklLBTqKkPcYxLT+Q99i1cZITfCGRwTRvBKARVf9I36EERwkjuLtnEEQi\+  \kg6kL6rC+86VPXR5RIsVCDafnc3ASR77mRc2sIoxLx40cYh2ax15tC9Z778ZFEhkFW/ZdgC5xEgnh4N3\+  \Wvano9m2d+vhbEwz/6O+WzJZvGX19fsNHamEyfil+pfb28FE6GxQxqfV1PTtNPz1xMgw1rBZJRZ6Gnsr\+  \Z+lQVU3eU1f1Rt1kbW9T9hkoQ3RgiYS1Fl841A4nEhrnAgMBAAECggEAETkBzU7nxh+yZbnvIVB3ITea\+  \KiW9FHrYp/yd002+ym+X8lm4+8KRY7J98iTiEuq0TJ+GRCMLfc3QnmFTR1e7zlc9Cvnw3WFun5lg/4le\+  \0BkI+okaKA2GQYgzD1vknPmzvF1NlgdD1sa+Qcd10WPCPGv0FR8uTYkbPmFwo57tBTGlVao/8NYFClfA\+  \PUqA2Ghb5tHtYELwIOlWqBTVkNQf5sEV8okApFjtkjxP3pO8YD3uPUG0kdghqnlbQaA1UXDKbLmh+NQF\+  \lF1tw2Dri8s9yzOdr1ujALdEcoKCmbWPXDvxZsu/xiUCpWCtmtUeyZAT+LA6t5cFykAXqrXdN4KEAQKB\+  \gQD+E3DVJEElxcrozzcgtsoiPZd38gD57kgF6C3VtdTzHA4ZMGoX8eF8TXUoisAEfhfnkcx2/HUu1DXI\+  \58SI37LpIPeJYyv36GwzeuS3cr5NtGaEXp+u/YE8Eft5FGgeXR8W2/wyOFoYzjoNt//SvzmabPo63Vwk\+  \jxgwXBYPOulpZwKBgQDBSClcgiQvwibFuHyjtGrSJlvsj6eQbLBekZY8hSqlJeJTUkxyOtMpldQ0gdmS\+  \5J6IsjdJ6mMU6Il5iwF2Mk/M+UMoHvlMCrvTKoThCo8wFAZL6LVEhNsPQzSHzs38qZgb81dBgTgJnvyr\+  \q50NPI1EDLlTn/2hh15WxHw3EydSgQKBgQDhiOJM4UzPOd9ff6lg6cFOWbwd5f2F3kWisLIXFbx9PTcq\+  \lvZmYPkWvS81mMzQcBnKHnsQWBOxSQChYVLtaR1IolH5a8X43yFFJV7nlPxmv6+M6u32iONyLkg696lg\+  \4qqZQReCgNFBWbbgvKdjLQn2EayiGiMT9M21B9kxFctiGwKBgC3KELJvym7eCh1xVWXbCit7Fu/2IHZg\+  \qW/eAb+YtL+nN/URXDb7pKcHbdx6nrbkHoK35c1HD+4WIOuAePotdSZULwrEO78+E701J4HA5Kc5Nzyo\+  \hrkS2GrHOxypa7dd3kFQ5u0H1eTBm453+571J2plsUoUbxvjXAAmUF8j1H8BAoGAYNycYkiZeTt9AN4S\+  \PK5N75HlAsi5iT6kRh90nFtKIAR9V0SNEeA3MnrCKdF22mNaQBbiDOu0fmdyNLRz0dp7nu3Bp7JZ6KvP\+  \tARzFTtgRrz41jCT2F+1R5zYdwK2LnTkjPIAdDL3/oeywaYPr9HIqYLzod2jh4KhZVttg1EpT2k="++nominalYear :: NominalDiffTime+nominalYear =+  nominalDay * 365++nominalDecade :: NominalDiffTime+nominalDecade =+  nominalYear * 10++genInDecade :: Gen NominalDiffTime+genInDecade =+  Gen.realFrac_ (Range.constant 0 nominalDecade)++genMaxAge :: Gen MaxAge+genMaxAge =+  MaxAge <$> Gen.realFrac_ (Range.constant 0 (9 * 60))