Skip to content

Commit cea5781

Browse files
committed
feat: add links in OSV exports (#165, #252)
1 parent 2c00a40 commit cea5781

File tree

3 files changed

+117
-34
lines changed

3 files changed

+117
-34
lines changed

code/hsec-tools/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
* Move `isVersionAffectedBy` and `isVersionRangeAffectedBy` to `Security.Advisories.Core` (`hsec-core`)
44
* Add support for GHC component in `query is-affected`
5+
* Add `model.database_specific.{repository,osvs,home}` and `model.affected.database_specific.{osv,human_link}` in OSV exports
56

67
## 0.2.0.2
78

code/hsec-tools/app/Main.hs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,27 @@ commandCheck =
9999

100100
commandOsv :: Parser (IO ())
101101
commandOsv =
102-
withAdvisory go
103-
<$> optional (argument str (metavar "FILE"))
102+
withAdvisory . go
103+
<$> dbLinksParser
104+
<*> optional (argument str (metavar "FILE"))
104105
where
105-
go _ adv = do
106-
L.putStr (Data.Aeson.encode (OSV.convert adv))
106+
dbLinksParser :: Parser OSV.DbLinks
107+
dbLinksParser =
108+
OSV.DbLinks
109+
<$> url "repository" (OSV.dbLinksRepository OSV.haskellLinks) "Repository URL"
110+
<*> url "osvs" (OSV.dbLinksOSVs OSV.haskellLinks) "OSVs link"
111+
<*> url "home" (OSV.dbLinksHome OSV.haskellLinks) "Home page URL"
112+
where
113+
url :: String -> T.Text -> String -> Parser T.Text
114+
url name def desc =
115+
T.pack <$> strOption
116+
( long name <> metavar "URL"
117+
<> help desc
118+
<> value (T.unpack def)
119+
<> showDefault
120+
)
121+
go links _ adv = do
122+
L.putStr (Data.Aeson.encode (OSV.convertWithLinks links adv))
107123
putChar '\n'
108124

109125
commandRender :: Parser (IO ())
Lines changed: 96 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
{-# LANGUAGE OverloadedStrings #-}
2+
{-# LANGUAGE RecordWildCards #-}
23

3-
module Security.Advisories.Convert.OSV
4-
( convert
5-
)
6-
where
4+
module Security.Advisories.Convert.OSV (
5+
convert,
6+
convertWithLinks,
7+
DbLinks (..),
8+
AffectedLinks (..),
9+
haskellLinks,
10+
)
11+
where
712

13+
import Data.Aeson
814
import qualified Data.Text as T
915
import Data.Void
1016
import Distribution.Pretty (prettyShow)
@@ -14,35 +20,36 @@ import qualified Security.OSV as OSV
1420

1521
convert :: Advisory -> OSV.Model Void Void Void Void
1622
convert adv =
17-
( OSV.newModel'
18-
(T.pack . printHsecId $ advisoryId adv)
19-
(advisoryModified adv)
20-
)
21-
{ OSV.modelPublished = Just $ advisoryPublished adv
22-
, OSV.modelAliases = advisoryAliases adv
23-
, OSV.modelRelated = advisoryRelated adv
24-
, OSV.modelSummary = Just $ advisorySummary adv
25-
, OSV.modelDetails = Just $ advisoryDetails adv
26-
, OSV.modelReferences = advisoryReferences adv
27-
, OSV.modelAffected = fmap mkAffected (advisoryAffected adv)
28-
}
23+
( OSV.newModel'
24+
(T.pack . printHsecId $ advisoryId adv)
25+
(advisoryModified adv)
26+
)
27+
{ OSV.modelPublished = Just $ advisoryPublished adv
28+
, OSV.modelAliases = advisoryAliases adv
29+
, OSV.modelRelated = advisoryRelated adv
30+
, OSV.modelSummary = Just $ advisorySummary adv
31+
, OSV.modelDetails = Just $ advisoryDetails adv
32+
, OSV.modelReferences = advisoryReferences adv
33+
, OSV.modelAffected = fmap mkAffected (advisoryAffected adv)
34+
}
2935

3036
mkAffected :: Affected -> OSV.Affected Void Void Void
3137
mkAffected aff =
32-
OSV.Affected
33-
{ OSV.affectedPackage = mkPackage (affectedComponentIdentifier aff)
34-
, OSV.affectedRanges = pure $ mkRange (affectedVersions aff)
35-
, OSV.affectedSeverity = [OSV.Severity (affectedCVSS aff)]
36-
, OSV.affectedEcosystemSpecific = Nothing
37-
, OSV.affectedDatabaseSpecific = Nothing
38-
}
38+
OSV.Affected
39+
{ OSV.affectedPackage = mkPackage (affectedComponentIdentifier aff)
40+
, OSV.affectedRanges = pure $ mkRange (affectedVersions aff)
41+
, OSV.affectedSeverity = [OSV.Severity (affectedCVSS aff)]
42+
, OSV.affectedEcosystemSpecific = Nothing
43+
, OSV.affectedDatabaseSpecific = Nothing
44+
}
3945

4046
mkPackage :: ComponentIdentifier -> OSV.Package
41-
mkPackage ecosystem = OSV.Package
42-
{ OSV.packageName = packageName
43-
, OSV.packageEcosystem = ecosystemName
44-
, OSV.packagePurl = Nothing
45-
}
47+
mkPackage ecosystem =
48+
OSV.Package
49+
{ OSV.packageName = packageName
50+
, OSV.packageEcosystem = ecosystemName
51+
, OSV.packagePurl = Nothing
52+
}
4653
where
4754
(ecosystemName, packageName) = case ecosystem of
4855
Hackage n -> ("Hackage", n)
@@ -54,5 +61,64 @@ mkRange ranges =
5461
where
5562
mkEvs :: AffectedVersionRange -> [OSV.Event T.Text]
5663
mkEvs range =
57-
OSV.EventIntroduced (T.pack $ prettyShow $ affectedVersionRangeIntroduced range)
58-
: maybe [] (pure . OSV.EventFixed . T.pack . prettyShow) (affectedVersionRangeFixed range)
64+
OSV.EventIntroduced (T.pack $ prettyShow $ affectedVersionRangeIntroduced range)
65+
: maybe [] (pure . OSV.EventFixed . T.pack . prettyShow) (affectedVersionRangeFixed range)
66+
67+
convertWithLinks :: DbLinks -> Advisory -> OSV.Model DbLinks AffectedLinks Void Void
68+
convertWithLinks links adv =
69+
OSV.Model
70+
{ OSV.modelDatabaseSpecific = Just links
71+
, OSV.modelAffected = mkAffectedWithLinks links (advisoryId adv) <$> advisoryAffected adv
72+
, ..
73+
}
74+
where
75+
OSV.Model{..} = convert adv
76+
77+
data DbLinks = DbLinks
78+
{ dbLinksRepository :: T.Text
79+
, dbLinksOSVs :: T.Text
80+
, dbLinksHome :: T.Text
81+
}
82+
83+
instance ToJSON DbLinks where
84+
toJSON DbLinks{..} =
85+
object
86+
[ "repository" .= dbLinksRepository
87+
, "osvs" .= dbLinksOSVs
88+
, "home" .= dbLinksHome
89+
]
90+
91+
haskellLinks :: DbLinks
92+
haskellLinks =
93+
DbLinks
94+
{ dbLinksRepository = "https://github.com/haskell/security-advisories"
95+
, dbLinksOSVs = "https://raw.githubusercontent.com/haskell/security-advisories/refs/heads/generated/osv-export"
96+
, dbLinksHome = "https://haskell.github.io/security-advisories"
97+
}
98+
99+
data AffectedLinks = AffectedLinks
100+
{ affectedLinksOSV :: T.Text
101+
, affectedLinksHumanLink :: T.Text
102+
}
103+
104+
instance ToJSON AffectedLinks where
105+
toJSON AffectedLinks{..} =
106+
object
107+
[ "osv" .= affectedLinksOSV
108+
, "human_link" .= affectedLinksHumanLink
109+
]
110+
111+
mkAffectedWithLinks :: DbLinks -> HsecId -> Affected -> OSV.Affected AffectedLinks Void Void
112+
mkAffectedWithLinks links hsecId aff =
113+
OSV.Affected
114+
{ OSV.affectedDatabaseSpecific =
115+
Just
116+
AffectedLinks
117+
{ affectedLinksOSV = stripSlash (dbLinksOSVs links) <> "/" <> T.pack (show $ hsecIdYear hsecId) <> "/" <> T.pack (printHsecId hsecId) <> ".json"
118+
, affectedLinksHumanLink = stripSlash (dbLinksHome links) <> "/advisory/" <> T.pack (printHsecId hsecId) <> ".html"
119+
}
120+
, ..
121+
}
122+
where
123+
OSV.Affected{..} = mkAffected aff
124+
stripSlash = T.dropWhileEnd (== '/')

0 commit comments

Comments
 (0)