packages feed

cvss-0.3.0.0: src/Security/CVSS/Types.hs

{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralisedNewtypeDeriving #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}

module Security.CVSS.Types
  ( CVSS (..),
    CVSSVersion (..),
    CVSSNomenclature (..),
    Rating (..),
    CVSSError (..),
    Metric (..),
    MetricShortName (..),
    MetricValueChar (..),
    showCVSSError,
    toRating,
    toRating20,
  )
where

import Data.String (IsString)
import Data.Text (Text)
import Data.Text qualified as Text

newtype MetricShortName = MetricShortName Text
  deriving newtype (Eq, IsString, Ord, Show)

newtype MetricValueChar = MetricValueChar Text
  deriving newtype (Eq, IsString, Ord, Show)

data Metric = Metric
  { mName :: MetricShortName,
    mChar :: MetricValueChar
  }
  deriving (Eq, Show)

data CVSSVersion
  = CVSS40
  | CVSS31
  | CVSS30
  | CVSS20
  deriving (Eq, Show)

data CVSSNomenclature
  = CVSS_B
  | CVSS_BT
  | CVSS_BE
  | CVSS_BTE
  deriving stock (Eq, Show)

data CVSS = CVSS
  { cvssVersion :: CVSSVersion,
    cvssMetrics :: [Metric]
  }
  deriving stock (Eq)

instance Show CVSS where
  show = Text.unpack . cvssVectorString'
    where
      cvssVectorString' cvss = case cvssVersion cvss of
        CVSS40 -> Text.intercalate "/" ("CVSS:4.0" : components)
        CVSS31 -> Text.intercalate "/" ("CVSS:3.1" : components)
        CVSS30 -> Text.intercalate "/" ("CVSS:3.0" : components)
        CVSS20 -> Text.intercalate "/" components
        where
          components = map toComponent (cvssMetrics cvss)
          toComponent (Metric (MetricShortName name) (MetricValueChar value)) = name <> ":" <> value

data Rating = None | Low | Medium | High | Critical
  deriving (Enum, Eq, Ord, Show)

data CVSSError
  = UnknownVersion
  | EmptyComponent
  | MissingValue Text
  | DuplicateMetric Text
  | MissingRequiredMetric Text
  | UnknownMetric Text
  | UnknownValue Text Text

instance Show CVSSError where
  show = Text.unpack . showCVSSError

showCVSSError :: CVSSError -> Text
showCVSSError e = case e of
  UnknownVersion -> "Unknown CVSS version"
  EmptyComponent -> "Empty component"
  MissingValue name -> "Missing value for \"" <> name <> "\""
  DuplicateMetric name -> "Duplicate metric for \"" <> name <> "\""
  MissingRequiredMetric name -> "Missing required metric \"" <> name <> "\""
  UnknownMetric name -> "Unknown metric \"" <> name <> "\""
  UnknownValue name value -> "Unknown value '" <> value <> "' for \"" <> name <> "\""

toRating :: Float -> Rating
toRating score
  | score <= 0 = None
  | score < 4 = Low
  | score < 7 = Medium
  | score < 9 = High
  | otherwise = Critical

toRating20 :: Float -> Rating
toRating20 score
  | score <= 0 = None
  | score < 4 = Low
  | score < 7 = Medium
  | otherwise = High