packages feed

lti13 0.2.0.3 → 0.3.0.0

raw patch · 6 files changed

+291/−78 lines, 6 filesdep ~aesondep ~basedep ~bytestringPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: aeson, base, bytestring, http-client, oidc-client, text

API changes (from Hackage documentation)

- Web.LTI13: SessionStore :: m ByteString -> (State -> Nonce -> m ()) -> m (Maybe State, Maybe Nonce) -> m () -> SessionStore (m :: Type -> Type)
+ Web.LTI13: SessionStore :: m ByteString -> (State -> Nonce -> m ()) -> (State -> m (Maybe Nonce)) -> m () -> SessionStore (m :: Type -> Type)
- Web.LTI13: [sessionStoreGet] :: SessionStore (m :: Type -> Type) -> m (Maybe State, Maybe Nonce)
+ Web.LTI13: [sessionStoreGet] :: SessionStore (m :: Type -> Type) -> State -> m (Maybe Nonce)

Files

CHANGELOG.md view
@@ -1,5 +1,12 @@ # Revision history for lti13 +## 0.3.0.0 -- 2022-11-23++* Support newer compilers and dependencies+    * aeson >= 2 now required;+    * oidc-client >= 0.7 now required+    * Support ghc-9.4+ ## 0.2.0.3 -- 2021-08-10  * Bump version for yesod-auth-lti13.
lti13.cabal view
@@ -1,57 +1,91 @@-cabal-version:       >=1.10+cabal-version: 1.12 -name:                lti13-version:             0.2.0.3-synopsis:            Core functionality for LTI 1.3.-description:         A library implementing the core-                     <http://www.imsglobal.org/spec/lti/v1p3/ LTI 1.3> authentication protocol,-                     suitable for use in implementing libraries for any web framework. An example-                     use is <https://hackage.haskell.org/package/yesod-auth-lti13>-bug-reports:         https://github.com/lf-/lti13/issues-license:             LGPL-3-author:              Jade-maintainer:          Jade <software at lfcode dot ca>--- copyright:-category:            Web-license-file:        LICENSE-build-type:          Simple-extra-source-files:  CHANGELOG.md-                     README.md+-- This file has been generated from package.yaml by hpack version 0.34.7.+--+-- see: https://github.com/sol/hpack -test-suite spec-    type:                exitcode-stdio-1.0-    default-language:    Haskell2010-    hs-source-dirs:      tests-    ghc-options:         -Wall-    main-is:             Spec.hs-    build-depends:       base-                       , hspec-                       , QuickCheck-                       , file-embed-                       , th-utilities-                       , text-                       , bytestring-                       , aeson-                       , lti13+name:           lti13+version:        0.3.0.0+synopsis:       Core functionality for LTI 1.3+description:    A library implementing the core <http://www.imsglobal.org/spec/lti/v1p3/ LTI 1.3> authentication protocol, suitable for use in implementing libraries for any web framework. An example use is <https://hackage.haskell.org/package/yesod-auth-lti13>+category:       Web+homepage:       https://github.com/lf-/lti13#readme+bug-reports:    https://github.com/lf-/lti13/issues+author:         Jade Lovelace+maintainer:     Jade Lovelace <software at lfcode dot ca>+license:        LGPL-3+license-file:   LICENSE+build-type:     Simple+tested-with:+    GHC ==8.10.7 || ==9.2.5 || ==9.4.3+extra-source-files:+    CHANGELOG.md+    README.md+    ./tests/example.json +source-repository head+  type: git+  location: https://github.com/lf-/lti13+ library-    exposed-modules:     Web.LTI13-    -- other-modules:-    -- other-extensions:-    hs-source-dirs:      src-    build-depends:       aeson                 >= 1.4.7 && < 1.6-                       , base                  >= 4.12.0 && < 5-                       , bytestring            >= 0.10.10 && < 0.11-                       , containers            >= 0.6.2 && < 0.7-                       , text                  >= 1.2.4 && < 1.3-                       , http-client           >= 0.6.4 && < 0.7-                       , http-types            >= 0.12.3 && < 0.13-                       , jose-jwt              >= 0.8.0 && < 0.10-                       , oidc-client           >= 0.5.1 && < 0.7-                       , safe-exceptions       >= 0.1.7 && < 0.2-    default-language:   Haskell2010-    ghc-options:         -Wall+  exposed-modules:+      Web.LTI13+  other-modules:+      Paths_lti13+  hs-source-dirs:+      src+  default-extensions:+      NamedFieldPuns+      OverloadedStrings+      RecordWildCards+      DerivingStrategies+      DeriveGeneric+  ghc-options: -Weverything -Wno-missing-exported-signatures -Wno-missing-export-lists -Wno-missing-import-lists -Wno-missed-specialisations -Wno-all-missed-specialisations -Wno-unsafe -Wno-missing-local-signatures -Wno-monomorphism-restriction -Wno-missing-safe-haskell-mode -Wno-prepositive-qualified-module -Wno-unused-packages -Wno-missing-kind-signatures+  build-depends:+      aeson >=2 && <2.2+    , base >=4.12.0 && <5+    , bytestring >=0.10.10 && <0.12+    , containers >=0.6.2 && <0.7+    , http-client >=0.6.4 && <0.8+    , http-types >=0.12.3 && <0.13+    , jose-jwt >=0.8.0 && <0.10+    , oidc-client ==0.7.*+    , safe-exceptions >=0.1.7 && <0.2+    , text >=1.2.4 && <1.3 || >=2.0 && <2.1+  default-language: Haskell2010 -source-repository head-    type: git-    location: https://github.com/lf-/lti13+test-suite spec+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      AnonSpec+      ParsingSpec+      Paths_lti13+  hs-source-dirs:+      tests+  default-extensions:+      NamedFieldPuns+      OverloadedStrings+      RecordWildCards+      DerivingStrategies+      DeriveGeneric+  ghc-options: -Weverything -Wno-missing-exported-signatures -Wno-missing-export-lists -Wno-missing-import-lists -Wno-missed-specialisations -Wno-all-missed-specialisations -Wno-unsafe -Wno-missing-local-signatures -Wno-monomorphism-restriction -Wno-missing-safe-haskell-mode -Wno-prepositive-qualified-module -Wno-unused-packages -Wno-missing-kind-signatures+  build-tool-depends:+      hspec-discover:hspec-discover+  build-depends:+      QuickCheck+    , aeson >=2 && <2.2+    , base >=4.12.0 && <5+    , bytestring >=0.10.10 && <0.12+    , containers >=0.6.2 && <0.7+    , file-embed+    , hspec+    , http-client >=0.6.4 && <0.8+    , http-types >=0.12.3 && <0.13+    , jose-jwt >=0.8.0 && <0.10+    , lti13+    , oidc-client ==0.7.*+    , safe-exceptions >=0.1.7 && <0.2+    , text >=1.2.4 && <1.3 || >=2.0 && <2.1+    , th-utilities+  default-language: Haskell2010
src/Web/LTI13.hs view
@@ -1,8 +1,4 @@-{-# LANGUAGE NamedFieldPuns    #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards   #-}---- | A basic LTI 1.3 library.+-- | A simple LTI 1.3 library. --   It's intended to be used by implementing routes for 'initiate' and --   'handleAuthResponse', and work out the associated parameters thereof. --@@ -35,7 +31,7 @@       , handleAuthResponse     ) where import           Control.Exception.Safe             (Exception, MonadCatch,-                                                     MonadThrow, Typeable,+                                                     MonadThrow,                                                      catch, throw, throwM) import           Control.Monad                      (when, (>=>)) import qualified Control.Monad.Fail                 as Fail@@ -50,6 +46,7 @@ import qualified Data.Aeson                         as A import           Data.Aeson.Types                   (Parser) import qualified Data.Map.Strict                    as Map+import           Data.String                        (IsString) import           Data.Text                          (Text) import qualified Data.Text                          as T import           Data.Text.Encoding                 (decodeUtf8, encodeUtf8)@@ -59,6 +56,7 @@                                                      httpLbs, parseRequest,                                                      responseBody) import qualified Network.HTTP.Types.URI             as URI+import           Prelude import qualified Web.OIDC.Client.Discovery.Provider as P import           Web.OIDC.Client.IdTokenFlow        (getValidIdTokenClaims) import qualified Web.OIDC.Client.Settings           as O@@ -67,7 +65,7 @@ import           Web.OIDC.Client.Types              (Nonce, SessionStore (..))  -- | Parses a JSON text field to a fixed expected value, failing otherwise-parseFixed :: (FromJSON a, Eq a, Show a) => Object -> Text -> a -> Parser a+parseFixed :: (FromJSON a, Eq a, Show a) => Object -> A.Key -> a -> Parser a parseFixed obj field fixedVal =     obj .: field >>= \v ->         if v == fixedVal then@@ -85,7 +83,7 @@           | Learner           | Mentor           | Other Text-          deriving (Show, Eq)+          deriving stock (Show, Eq)  roleFromString :: Text -> Role roleFromString "http://purl.imsglobal.org/vocab/lis/v2/membership#Administrator"@@ -127,7 +125,7 @@     , resultSourcedId         :: Maybe Text     -- ^ An identifier for the position in the gradebook associated with the     --   assignment being viewed.-    } deriving (Show, Eq)+    } deriving stock (Show, Eq)  instance FromJSON LisClaim where     parseJSON = withObject "LisClaim" $ \v ->@@ -166,7 +164,7 @@     , contextLabel :: Maybe Text     , contextTitle :: Maybe Text     }-    deriving (Show, Eq)+    deriving stock (Show, Eq)  instance FromJSON ContextClaim where     parseJSON = withObject "ContextClaim" $ \v ->@@ -204,16 +202,16 @@     , lastName      :: Maybe Text     , context       :: Maybe ContextClaim     , lis           :: Maybe LisClaim-    } deriving (Show, Eq)+    } deriving stock (Show, Eq)  -- | An object representing in the type system a token whose claims have been --   validated. newtype LtiTokenClaims = LtiTokenClaims { unLtiTokenClaims :: UncheckedLtiTokenClaims }-    deriving (Show, Eq)+    deriving stock (Show, Eq)  -- | LTI token claims from which all student data has been removed. For logging. newtype AnonymizedLtiTokenClaims = AnonymizedLtiTokenClaims UncheckedLtiTokenClaims-    deriving (Show, Eq)+    deriving stock (Show, Eq)  limitLength :: (Fail.MonadFail m) => Int -> Text -> m Text limitLength len string@@ -221,19 +219,19 @@     = return string limitLength _ _ = fail "String is too long" -claimMessageType :: Text+claimMessageType :: IsString t => t claimMessageType = "https://purl.imsglobal.org/spec/lti/claim/message_type"-claimVersion :: Text+claimVersion :: IsString t => t claimVersion = "https://purl.imsglobal.org/spec/lti/claim/version"-claimDeploymentId :: Text+claimDeploymentId :: IsString t => t claimDeploymentId = "https://purl.imsglobal.org/spec/lti/claim/deployment_id"-claimTargetLinkUri :: Text+claimTargetLinkUri :: IsString t => t claimTargetLinkUri = "https://purl.imsglobal.org/spec/lti/claim/target_link_uri"-claimRoles :: Text+claimRoles :: IsString t => t claimRoles = "https://purl.imsglobal.org/spec/lti/claim/roles"-claimContext :: Text+claimContext :: IsString t => t claimContext = "https://purl.imsglobal.org/spec/lti/claim/context"-claimLis :: Text+claimLis :: IsString t => t claimLis = "https://purl.imsglobal.org/spec/lti/claim/lis"  instance FromJSON UncheckedLtiTokenClaims where@@ -345,7 +343,7 @@     | InvalidLtiToken Text     -- ^ Token validation error. Per <http://www.imsglobal.org/spec/security/v1p0/#authentication-response-validation Security § 5.1.3>     --   if you get this, you should return a 401.-    deriving (Show, Typeable)+    deriving stock (Show) instance Exception LTI13Exception  -- | @client_id@, one or more per platform; <https://www.imsglobal.org/spec/lti/v1p3/#tool-deployment LTI spec § 3.1.3>@@ -420,9 +418,10 @@ initiate :: (MonadIO m) => AuthFlowConfig m -> RequestParams -> m (Issuer, ClientId, Text) initiate cfg params = do     -- we don't care about target link uri since we only support one endpoint-    res <- liftIO $ mapM (`lookupOrThrow` params) ["iss", "login_hint", "target_link_uri"]-    -- not actually fallible-    let [iss, loginHint, _] = res+    iss <- liftIO $ lookupOrThrow "iss" params+    loginHint <- liftIO $ lookupOrThrow "login_hint" params+    _targetLinkUri <- liftIO $ lookupOrThrow "target_link_uri" params+     let messageHint = Map.lookup "lti_message_hint" params     -- "This allows for a platform to support multiple registrations from a     -- single issuer, without relying on the initiate_login_uri as a key."@@ -493,8 +492,8 @@     -> PlatformInfo     -> m (Text, IdTokenClaims LtiTokenClaims) handleAuthResponse mgr cfg params pinfo = do-    params' <- liftIO $ mapM (`lookupOrThrow` params) ["state", "id_token"]-    let [state, idToken] = params'+    state <- liftIO $ lookupOrThrow "state" params+    idToken <- liftIO $ lookupOrThrow "id_token" params      let PlatformInfo { jwksUrl } = pinfo     jwkSet <- liftIO $ getJwkSet mgr jwksUrl
+ tests/AnonSpec.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE OverloadedStrings #-}+-- | Tests the various parsers in lti13 round-trip and parse the samples.+module AnonSpec (spec) where++import Test.Hspec hiding (context)+import Prelude+import Web.LTI13++exampleToken :: UncheckedLtiTokenClaims+exampleToken = UncheckedLtiTokenClaims+    { messageType = "LtiResourceLinkRequest"+    , ltiVersion = "1.3.0"+    , deploymentId = "07940580-b309-415e-a37c-914d387c1150"+    , targetLinkUri = "https://tool.example.com/lti/48320/ruix8782rs"+    , roles = [Other "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Student",Learner,Mentor]+    , email = secret+    , displayName = secret+    , firstName = secret+    , lastName = secret+    , context = Just (+        ContextClaim+        { contextId = "c1d887f0-a1a3-4bca-ae25-c375edcc131a"+        , contextLabel = Just "ECON 1010"+        , contextTitle = Just "Economics as a Social Science"}+    )+    , lis = Just (+        LisClaim {+              personSourcedId = secret+            , outcomeServiceUrl = Nothing+            , courseOfferingSourcedId = Just "example.edu:SI182-F16"+            , courseSectionSourcedId = Just "example.edu:SI182-001-F16"+            , resultSourcedId = Nothing+        }+    )}+    where+        secret = Just "secret"++spec :: Spec+spec = do+    it "removes secrets" $ do+        show exampleToken `shouldContain` "secret"+        (show . anonymizeLtiTokenForLogging $ exampleToken) `shouldNotContain` "secret"
+ tests/ParsingSpec.hs view
@@ -0,0 +1,60 @@+{-# LANGUAGE TemplateHaskell, OverloadedStrings #-}+-- | Tests the various parsers in lti13 round-trip and parse the samples.+module ParsingSpec (spec) where++import Test.Hspec+import Test.Hspec.QuickCheck+import Data.FileEmbed+import TH.RelativePaths+import Web.LTI13+import qualified Web.LTI13 as LTI+import Data.Aeson+import Prelude+import qualified Data.ByteString as BS++-- | This is lifted directly from section E of the LTI 1.3 spec.+ltiSpecExample :: BS.ByteString+ltiSpecExample = $(embedFile =<< pathRelativeToCabalPackage "tests/example.json")++specExampleOutput :: UncheckedLtiTokenClaims+specExampleOutput = UncheckedLtiTokenClaims {+      messageType = "LtiResourceLinkRequest"+    , ltiVersion = "1.3.0"+    , deploymentId = "07940580-b309-415e-a37c-914d387c1150"+    , targetLinkUri = "https://tool.example.com/lti/48320/ruix8782rs"+    , roles = [Other "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Student",Learner,Mentor]+    , email = Just "jane@platform.example.edu"+    , displayName = Just "Ms Jane Marie Doe"+    , firstName = Just "Jane"+    , lastName = Just "Doe"+    , LTI.context = Just (+        ContextClaim+        { contextId = "c1d887f0-a1a3-4bca-ae25-c375edcc131a"+        , contextLabel = Just "ECON 1010"+        , contextTitle = Just "Economics as a Social Science"}+    )+    , lis = Just (+        LisClaim {+              personSourcedId = Just "example.edu:71ee7e42-f6d2-414a-80db-b69ac2defd4"+            , outcomeServiceUrl = Nothing+            , courseOfferingSourcedId = Just "example.edu:SI182-F16"+            , courseSectionSourcedId = Just "example.edu:SI182-001-F16"+            , resultSourcedId = Nothing+        }+    )}++spec :: Spec+spec = do+    describe "id" $ do+        prop "id" $ \x y ->+            add x y == add y x+    describe "main parser" $ do+        it "parses the file from section E of the lti spec" $ do+            let v = decodeStrict ltiSpecExample :: Maybe UncheckedLtiTokenClaims+            -- (putStrLn . show $ v) `shouldReturn` ()+            v `shouldBe` Just specExampleOutput+            let roundTripped = decode . encode =<< v+            roundTripped `shouldBe` Just specExampleOutput++add :: Int -> Int -> Int+add x y = x + y
+ tests/example.json view
@@ -0,0 +1,71 @@+{+  "iss": "https://platform.example.edu",+  "sub": "a6d5c443-1f51-4783-ba1a-7686ffe3b54a",+  "aud": ["962fa4d8-bcbf-49a0-94b2-2de05ad274af"],+  "exp": 1510185728,+  "iat": 1510185228,+  "azp": "962fa4d8-bcbf-49a0-94b2-2de05ad274af",+  "nonce": "fc5fdc6d-5dd6-47f4-b2c9-5d1216e9b771",+  "name": "Ms Jane Marie Doe",+  "given_name": "Jane",+  "family_name": "Doe",+  "middle_name": "Marie",+  "picture": "https://platform.example.edu/jane.jpg",+  "email": "jane@platform.example.edu",+  "locale": "en-US",+  "https://purl.imsglobal.org/spec/lti/claim/deployment_id":+    "07940580-b309-415e-a37c-914d387c1150",+  "https://purl.imsglobal.org/spec/lti/claim/message_type": "LtiResourceLinkRequest",+  "https://purl.imsglobal.org/spec/lti/claim/version": "1.3.0",+  "https://purl.imsglobal.org/spec/lti/claim/roles": [+    "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Student",+    "http://purl.imsglobal.org/vocab/lis/v2/membership#Learner",+    "http://purl.imsglobal.org/vocab/lis/v2/membership#Mentor"+  ],+  "https://purl.imsglobal.org/spec/lti/claim/role_scope_mentor": [+    "fad5fb29-a91c-770-3c110-1e687120efd9",+    "5d7373de-c76c-e2b-01214-69e487e2bd33",+    "d779cfd4-bc7b-019-9bf1a-04bf1915d4d0"+  ],+  "https://purl.imsglobal.org/spec/lti/claim/context": {+      "id": "c1d887f0-a1a3-4bca-ae25-c375edcc131a",+      "label": "ECON 1010",+      "title": "Economics as a Social Science",+      "type": ["http://purl.imsglobal.org/vocab/lis/v2/course#CourseOffering"]+  },+  "https://purl.imsglobal.org/spec/lti/claim/resource_link": {+      "id": "200d101f-2c14-434a-a0f3-57c2a42369fd",+      "description": "Assignment to introduce who you are",+      "title": "Introduction Assignment"+  },+  "https://purl.imsglobal.org/spec/lti/claim/tool_platform": {+      "guid": "ex/48bbb541-ce55-456e-8b7d-ebc59a38d435",+      "contact_email": "support@platform.example.edu",+      "description": "An Example Tool Platform",+      "name": "Example Tool Platform",+      "url": "https://platform.example.edu",+      "product_family_code": "ExamplePlatformVendor-Product",+      "version": "1.0"+  },+  "https://purl.imsglobal.org/spec/lti/claim/target_link_uri":+      "https://tool.example.com/lti/48320/ruix8782rs",+  "https://purl.imsglobal.org/spec/lti/claim/launch_presentation": {+      "document_target": "iframe",+      "height": 320,+      "width": 240,+      "return_url": "https://platform.example.edu/terms/201601/courses/7/sections/1/resources/2"+  },+  "https://purl.imsglobal.org/spec/lti/claim/custom": {+    "xstart": "2017-04-21T01:00:00Z",+    "request_url": "https://tool.com/link/123"+  },+  "https://purl.imsglobal.org/spec/lti/claim/lis": {+      "person_sourcedid": "example.edu:71ee7e42-f6d2-414a-80db-b69ac2defd4",+      "course_offering_sourcedid": "example.edu:SI182-F16",+      "course_section_sourcedid": "example.edu:SI182-001-F16"+  },+  "http://www.ExamplePlatformVendor.com/session": {+      "id": "89023sj890dju080"+}++}