hsec-tools 0.4.0.0 → 0.5.0.0
raw patch · 11 files changed
+167/−10 lines, 11 filesdep ~bytestringdep ~hsec-corePVP ok
version bump matches the API change (PVP)
Dependency ranges changed: bytestring, hsec-core
API changes (from Hackage documentation)
+ Security.Advisories.Convert.OSV: HsecEcosystemSpecific :: [AffectedApi] -> HsecEcosystemSpecific
+ Security.Advisories.Convert.OSV: [hsecEcosystemAffectedApi] :: HsecEcosystemSpecific -> [AffectedApi]
+ Security.Advisories.Convert.OSV: instance Data.Aeson.Types.FromJSON.FromJSON Security.Advisories.Convert.OSV.AffectedLinks
+ Security.Advisories.Convert.OSV: instance Data.Aeson.Types.FromJSON.FromJSON Security.Advisories.Convert.OSV.DbLinks
+ Security.Advisories.Convert.OSV: instance Data.Aeson.Types.FromJSON.FromJSON Security.Advisories.Convert.OSV.HsecEcosystemSpecific
+ Security.Advisories.Convert.OSV: instance Data.Aeson.Types.FromJSON.FromJSON Security.Advisories.Core.Advisory.AffectedApi
+ Security.Advisories.Convert.OSV: instance Data.Aeson.Types.ToJSON.ToJSON Security.Advisories.Convert.OSV.HsecEcosystemSpecific
+ Security.Advisories.Convert.OSV: instance Data.Aeson.Types.ToJSON.ToJSON Security.Advisories.Core.Advisory.AffectedApi
+ Security.Advisories.Convert.OSV: instance GHC.Classes.Eq Security.Advisories.Convert.OSV.HsecEcosystemSpecific
+ Security.Advisories.Convert.OSV: instance GHC.Show.Show Security.Advisories.Convert.OSV.HsecEcosystemSpecific
+ Security.Advisories.Convert.OSV: newtype HsecEcosystemSpecific
+ Security.Advisories.Format: instance Toml.Schema.FromValue.FromValue Security.Advisories.Core.Advisory.AffectedApi
+ Security.Advisories.Format: instance Toml.Schema.ToValue.ToTable Security.Advisories.Core.Advisory.AffectedApi
+ Security.Advisories.Format: instance Toml.Schema.ToValue.ToValue Security.Advisories.Core.Advisory.AffectedApi
- Security.Advisories.Convert.OSV: convert :: Advisory -> Model Void Void Void Void
+ Security.Advisories.Convert.OSV: convert :: Advisory -> Model Void Void HsecEcosystemSpecific Void
- Security.Advisories.Convert.OSV: convertWithLinks :: DbLinks -> Advisory -> Model DbLinks AffectedLinks Void Void
+ Security.Advisories.Convert.OSV: convertWithLinks :: DbLinks -> Advisory -> Model DbLinks AffectedLinks HsecEcosystemSpecific Void
Files
- CHANGELOG.md +7/−0
- hsec-tools.cabal +6/−3
- src/Security/Advisories/Convert/OSV.hs +53/−5
- src/Security/Advisories/Format.hs +23/−1
- src/Security/Advisories/Generate/HTML.hs +7/−0
- test/Spec.hs +2/−0
- test/Spec/FormatSpec.hs +5/−1
- test/Spec/OsvSpec.hs +61/−0
- test/golden/EXAMPLE_ADVISORY_GHC.md.golden +1/−0
- test/golden/EXAMPLE_ADVISORY_HACKAGE.md.golden +1/−0
- test/golden/EXAMPLE_ADVISORY_REPO.md.golden +1/−0
CHANGELOG.md view
@@ -1,3 +1,10 @@+## 0.5.0.0++* Add `HsecEcosystemSpecific` with `affected_api` in OSV exports+* Add TOML parsing/rendering for `api` key in affected sections+* Add HTML rendering for affected APIs+* Update `hsec-core` to `0.5.0.0`+ ## 0.4.0.0 * Update `hsec-core` to `0.4.0.0`
hsec-tools.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: hsec-tools-version: 0.4.0.0+version: 0.5.0.0 -- A short (one-line) description of the package. synopsis:@@ -73,7 +73,7 @@ , extra >=1.7 && <1.9 , file-embed >=0.0.13.0 && <0.0.17 , filepath >=1.4 && <1.6- , hsec-core >=0.4.0.0 && <0.5+ , hsec-core >=0.5.0.0 && <0.6 , lens >=5.1.0 && <5.4 , lucid >=2.9.0 && <3 , mtl >=2.2 && <2.4@@ -120,7 +120,7 @@ , Cabal-syntax >=3.8.1.0 && <3.15 , directory <2 , filepath >=1.4 && <1.6- , hsec-core >=0.4.0.0 && <0.5+ , hsec-core >=0.5.0.0 && <0.6 , hsec-tools , network-uri >=2.6.3.0 && <2.8 , optparse-applicative >=0.17 && <0.19@@ -142,10 +142,13 @@ other-modules: Paths_hsec_tools Spec.FormatSpec+ Spec.OsvSpec build-depends:+ , aeson >=2.0.1.0 && <3 , aeson-pretty <2 , base+ , bytestring >=0.10 && <0.14 , Cabal-syntax , containers , cvss
src/Security/Advisories/Convert/OSV.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} @@ -6,6 +8,7 @@ convertWithLinks, DbLinks (..), AffectedLinks (..),+ HsecEcosystemSpecific (..), haskellLinks, ) where@@ -18,7 +21,7 @@ import Security.Advisories.Core.OsvId (printOsvId) import qualified Security.OSV as OSV -convert :: Advisory -> OSV.Model Void Void Void Void+convert :: Advisory -> OSV.Model Void Void HsecEcosystemSpecific Void convert adv = ( OSV.newModel' (T.pack . printHsecId $ advisoryId adv)@@ -33,13 +36,16 @@ OSV.modelAffected = fmap mkAffected (advisoryAffected adv) } -mkAffected :: Affected -> OSV.Affected Void Void Void+mkAffected :: Affected -> OSV.Affected Void HsecEcosystemSpecific Void mkAffected aff = OSV.Affected { OSV.affectedPackage = mkPackage (affectedComponentIdentifier aff), OSV.affectedRanges = pure $ mkRange (affectedVersions aff), OSV.affectedSeverity = [OSV.Severity (affectedCVSS aff)],- OSV.affectedEcosystemSpecific = Nothing,+ OSV.affectedEcosystemSpecific =+ if null (affectedApi aff)+ then Nothing+ else Just (HsecEcosystemSpecific (affectedApi aff)), OSV.affectedDatabaseSpecific = Nothing } @@ -66,7 +72,7 @@ OSV.EventIntroduced (T.pack $ prettyShow $ affectedVersionRangeIntroduced range) : maybe [] (pure . OSV.EventFixed . T.pack . prettyShow) (affectedVersionRangeFixed range) -convertWithLinks :: DbLinks -> Advisory -> OSV.Model DbLinks AffectedLinks Void Void+convertWithLinks :: DbLinks -> Advisory -> OSV.Model DbLinks AffectedLinks HsecEcosystemSpecific Void convertWithLinks links adv = OSV.Model { OSV.modelDatabaseSpecific = Just links,@@ -90,6 +96,13 @@ "home" .= dbLinksHome ] +instance FromJSON DbLinks where+ parseJSON = withObject "DbLinks" $ \o -> do+ dbLinksRepository <- o .: "repository"+ dbLinksOSVs <- o .: "osvs"+ dbLinksHome <- o .: "home"+ pure DbLinks {..}+ haskellLinks :: DbLinks haskellLinks = DbLinks@@ -110,7 +123,42 @@ "human_link" .= affectedLinksHumanLink ] -mkAffectedWithLinks :: DbLinks -> HsecId -> Affected -> OSV.Affected AffectedLinks Void Void+instance FromJSON AffectedLinks where+ parseJSON = withObject "AffectedLinks" $ \o -> do+ affectedLinksOSV <- o .: "osv"+ affectedLinksHumanLink <- o .: "human_link"+ pure AffectedLinks {..}++newtype HsecEcosystemSpecific = HsecEcosystemSpecific+ { hsecEcosystemAffectedApi :: [AffectedApi]+ }+ deriving stock (Eq, Show)++instance ToJSON HsecEcosystemSpecific where+ toJSON (HsecEcosystemSpecific apis) =+ object+ [ "affected_api" .= apis+ ]++instance FromJSON HsecEcosystemSpecific where+ parseJSON = withObject "HsecEcosystemSpecific" $ \o -> do+ hsecEcosystemAffectedApi <- o .: "affected_api"+ pure HsecEcosystemSpecific {..}++instance ToJSON AffectedApi where+ toJSON AffectedApi {..} =+ object+ [ "module" .= affectedApiModule,+ "name" .= affectedApiName+ ]++instance FromJSON AffectedApi where+ parseJSON = withObject "AffectedApi" $ \o -> do+ affectedApiModule <- o .: "module"+ affectedApiName <- o .: "name"+ pure AffectedApi {..}++mkAffectedWithLinks :: DbLinks -> HsecId -> Affected -> OSV.Affected AffectedLinks HsecEcosystemSpecific Void mkAffectedWithLinks links hsecId aff = OSV.Affected { OSV.affectedDatabaseSpecific =
src/Security/Advisories/Format.hs view
@@ -176,6 +176,7 @@ os <- Toml.optKey "os" arch <- Toml.optKey "arch" decls <- maybe [] Map.toList <$> Toml.optKey "declarations"+ api <- fromMaybe [] <$> Toml.optKey "api" versions <- Toml.reqKey "versions" pure $ Affected@@ -184,7 +185,8 @@ affectedVersions = versions, affectedArchitectures = arch, affectedOS = os,- affectedDeclarations = decls+ affectedDeclarations = decls,+ affectedApi = api } instance Toml.ToValue Affected where@@ -200,6 +202,7 @@ ++ ["os" Toml..= y | Just y <- [affectedOS x]] ++ ["arch" Toml..= y | Just y <- [affectedArchitectures x]] ++ ["declarations" Toml..= asTable (affectedDeclarations x) | not (null (affectedDeclarations x))]+ ++ ["api" Toml..= affectedApi x | not (null (affectedApi x))] where ecosystem = case affectedComponentIdentifier x of Repository repoUrl repoName pkg@@ -207,6 +210,25 @@ | otherwise -> ["repository-url" Toml..= repoUrl, "repository-name" Toml..= repoName, "package" Toml..= pkg] GHC c -> ["ghc-component" Toml..= c] asTable kvs = Map.fromList [(T.unpack k, v) | (k, v) <- kvs]++instance Toml.FromValue AffectedApi where+ fromValue = Toml.parseTableFromValue $ do+ mod <- Toml.reqKey "module"+ name <- Toml.reqKey "name"+ pure AffectedApi+ { affectedApiModule = mod,+ affectedApiName = name+ }++instance Toml.ToValue AffectedApi where+ toValue = Toml.defaultTableToValue++instance Toml.ToTable AffectedApi where+ toTable x =+ Toml.table+ [ "module" Toml..= affectedApiModule x,+ "name" Toml..= affectedApiName x+ ] instance Toml.FromValue AffectedVersionRange where fromValue = Toml.parseTableFromValue $
src/Security/Advisories/Generate/HTML.hs view
@@ -265,6 +265,13 @@ code_ [] $ toHtml declaration ": " code_ [] $ toHtml $ T.pack $ prettyShow versionRange+ dt_ "Affected APIs"+ placeholderWhenEmptyOr (Advisories.affectedApi affected) $ \apis ->+ forM_ apis $ \api ->+ dd_ [] $ do+ code_ [] $ toHtml $ Advisories.affectedApiModule api+ "."+ code_ [] $ toHtml $ Advisories.affectedApiName api -- * Utils
test/Spec.hs view
@@ -13,6 +13,7 @@ import qualified Security.Advisories.Convert.OSV as OSV import Security.Advisories.Parse import qualified Spec.FormatSpec as FormatSpec+import qualified Spec.OsvSpec as OsvSpec import System.Directory (listDirectory) import Test.Tasty (defaultMain, testGroup, TestTree) import Test.Tasty.Golden (goldenVsString)@@ -26,6 +27,7 @@ "Tests" [ goldenTestsSpec goldenFiles , FormatSpec.spec+ , OsvSpec.spec ] listGoldenFiles :: IO [FilePath]
test/Spec/FormatSpec.hs view
@@ -79,6 +79,7 @@ <*> Gen.maybe (Gen.list (Range.linear 0 5) genArchitecture) <*> Gen.maybe (Gen.list (Range.linear 0 5) genOS) <*> (Map.toList . Map.fromList <$> Gen.list (Range.linear 0 5) ((,) <$> genText <*> genVersionRange))+ <*> Gen.list (Range.linear 0 5) genAffectedApi genComponentIdentifier :: Gen.Gen ComponentIdentifier genComponentIdentifier =@@ -139,7 +140,7 @@ genUTCTime = UTCTime <$> genDay- <*> fmap secondsToDiffTime (Gen.integral $ Range.constant 0 86401)+ <*> fmap secondsToDiffTime (Gen.integral $ Range.constant 0 86399) genDay :: Gen.Gen Day genDay = do@@ -205,3 +206,6 @@ ] entry <- Gen.text (Range.linear 4 10) Gen.alphaNum pure . Maybe.fromJust . parseOsvId $ prefix <> "-" <> entry++genAffectedApi :: Gen.Gen AffectedApi+genAffectedApi = AffectedApi <$> genText <*> genText
+ test/Spec/OsvSpec.hs view
@@ -0,0 +1,61 @@+{-# LANGUAGE OverloadedStrings #-}++module Spec.OsvSpec (spec) where++import Data.Aeson (decode, encode)+import Data.ByteString.Lazy (toStrict)+import qualified Data.Text as T+import qualified Data.Text.Encoding as TE+import Security.Advisories.Convert.OSV+import Security.Advisories.Core.Advisory+import Test.Tasty+import Test.Tasty.Hedgehog+import qualified Hedgehog as Gen+import qualified Hedgehog.Gen as Gen+import qualified Hedgehog.Range as Range++spec :: TestTree+spec =+ testGroup+ "OSV"+ [ testGroup+ "AffectedApi"+ [ testProperty "JSON roundtrip" $+ Gen.property $ do+ api <- Gen.forAll genAffectedApi+ let encoded = encode api+ decoded = decode encoded+ Just api Gen.=== decoded+ ]+ , testGroup+ "HsecEcosystemSpecific"+ [ testProperty "JSON roundtrip" $+ Gen.property $ do+ ecs <- Gen.forAll genHsecEcosystemSpecific+ let encoded = encode ecs+ decoded = decode encoded+ Just ecs Gen.=== decoded+ , testProperty "encodes affected_api key" $+ Gen.property $ do+ apis <- Gen.forAll (Gen.list (Range.linear 1 5) genAffectedApi)+ let ecs = HsecEcosystemSpecific apis+ jsonBytes = encode ecs+ jsonText = TE.decodeUtf8 (toStrict jsonBytes)+ Gen.assert $ "affected_api" `T.isInfixOf` jsonText+ , testProperty "Nothing when empty" $+ Gen.property $ do+ let ecs = HsecEcosystemSpecific []+ jsonBytes = encode ecs+ jsonText = TE.decodeUtf8 (toStrict jsonBytes)+ Gen.assert $ "affected_api" `T.isInfixOf` jsonText+ ]+ ]++genAffectedApi :: Gen.Gen AffectedApi+genAffectedApi = AffectedApi <$> genText <*> genText++genHsecEcosystemSpecific :: Gen.Gen HsecEcosystemSpecific+genHsecEcosystemSpecific = HsecEcosystemSpecific <$> Gen.list (Range.linear 0 5) genAffectedApi++genText :: Gen.Gen T.Text+genText = Gen.text (Range.linear 1 20) Gen.alphaNum
test/golden/EXAMPLE_ADVISORY_GHC.md.golden view
@@ -46,6 +46,7 @@ , affectedArchitectures = Nothing , affectedOS = Nothing , affectedDeclarations = []+ , affectedApi = [] } ] , advisoryReferences =
test/golden/EXAMPLE_ADVISORY_HACKAGE.md.golden view
@@ -51,6 +51,7 @@ , affectedArchitectures = Nothing , affectedOS = Nothing , affectedDeclarations = []+ , affectedApi = [] } ] , advisoryReferences =
test/golden/EXAMPLE_ADVISORY_REPO.md.golden view
@@ -51,6 +51,7 @@ , affectedArchitectures = Nothing , affectedOS = Nothing , affectedDeclarations = []+ , affectedApi = [] } ] , advisoryReferences =