hsec-tools-0.3.0.1: src/Security/Advisories/Convert/OSV.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Security.Advisories.Convert.OSV (
convert,
convertWithLinks,
DbLinks (..),
AffectedLinks (..),
haskellLinks,
)
where
import Data.Aeson
import qualified Data.Text as T
import Data.Void
import Distribution.Pretty (prettyShow)
import Security.Advisories
import qualified Security.OSV as OSV
convert :: Advisory -> OSV.Model Void Void Void Void
convert adv =
( OSV.newModel'
(T.pack . printHsecId $ advisoryId adv)
(advisoryModified adv)
)
{ OSV.modelPublished = Just $ advisoryPublished adv
, OSV.modelAliases = advisoryAliases adv
, OSV.modelRelated = advisoryRelated adv
, OSV.modelSummary = Just $ advisorySummary adv
, OSV.modelDetails = Just $ advisoryDetails adv
, OSV.modelReferences = advisoryReferences adv
, OSV.modelAffected = fmap mkAffected (advisoryAffected adv)
}
mkAffected :: Affected -> OSV.Affected Void Void 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.affectedDatabaseSpecific = Nothing
}
mkPackage :: ComponentIdentifier -> OSV.Package
mkPackage ecosystem =
OSV.Package
{ OSV.packageName = packageName
, OSV.packageEcosystem = ecosystemName
, OSV.packagePurl = Nothing
}
where
(ecosystemName, packageName) = case ecosystem of
Repository _ repoName pkg
| ecosystem == hackage pkg -> ("Hackage", T.pack $ unPackageName pkg)
| otherwise -> (unRepositoryName repoName, T.pack $ unPackageName pkg)
GHC c -> ("GHC", ghcComponentToText c)
mkRange :: [AffectedVersionRange] -> OSV.Range Void
mkRange ranges =
OSV.RangeEcosystem (foldMap mkEvs ranges) Nothing
where
mkEvs :: AffectedVersionRange -> [OSV.Event T.Text]
mkEvs range =
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 links adv =
OSV.Model
{ OSV.modelDatabaseSpecific = Just links
, OSV.modelAffected = mkAffectedWithLinks links (advisoryId adv) <$> advisoryAffected adv
, ..
}
where
OSV.Model{..} = convert adv
data DbLinks = DbLinks
{ dbLinksRepository :: T.Text
, dbLinksOSVs :: T.Text
, dbLinksHome :: T.Text
}
instance ToJSON DbLinks where
toJSON DbLinks{..} =
object
[ "repository" .= dbLinksRepository
, "osvs" .= dbLinksOSVs
, "home" .= dbLinksHome
]
haskellLinks :: DbLinks
haskellLinks =
DbLinks
{ dbLinksRepository = "https://github.com/haskell/security-advisories"
, dbLinksOSVs = "https://raw.githubusercontent.com/haskell/security-advisories/refs/heads/generated/osv-export"
, dbLinksHome = "https://github.com/haskell/security-advisories"
}
data AffectedLinks = AffectedLinks
{ affectedLinksOSV :: T.Text
, affectedLinksHumanLink :: T.Text
}
instance ToJSON AffectedLinks where
toJSON AffectedLinks{..} =
object
[ "osv" .= affectedLinksOSV
, "human_link" .= affectedLinksHumanLink
]
mkAffectedWithLinks :: DbLinks -> HsecId -> Affected -> OSV.Affected AffectedLinks Void Void
mkAffectedWithLinks links hsecId aff =
OSV.Affected
{ OSV.affectedDatabaseSpecific =
Just
AffectedLinks
{ affectedLinksOSV = osvLink
, affectedLinksHumanLink = humanLink
}
, ..
}
where
OSV.Affected{..} = mkAffected aff
stripSlash = T.dropWhileEnd (== '/')
osvLink =
stripSlash (dbLinksOSVs links)
<> "/" <> T.pack (show $ hsecIdYear hsecId)
<> "/" <> T.pack (printHsecId hsecId) <> ".json"
humanLink =
stripSlash (dbLinksHome links)
<> "/tree/main/advisories/published/"
<> T.pack (show $ hsecIdYear hsecId)
<> "/" <> T.pack (printHsecId hsecId) <> ".md"