compaREST-0.1.0.0: src/Data/OpenApi/Compare/Validate/SecurityRequirement.hs
{-# LANGUAGE PartialTypeSignatures #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module Data.OpenApi.Compare.Validate.SecurityRequirement
( Issue (..),
)
where
import Control.Comonad
import Control.Monad
import Control.Monad.Writer
import Data.Bifunctor
import Data.Either
import Data.Foldable
import Data.Functor
import Data.HList
import qualified Data.HashMap.Strict.InsOrd as IOHM
import qualified Data.List.NonEmpty as NE
import Data.OpenApi
import Data.OpenApi.Compare.Behavior
import Data.OpenApi.Compare.Subtree
import Data.OpenApi.Compare.Validate.OAuth2Flows
import Data.OpenApi.Compare.Validate.SecurityScheme ()
import Data.Set (Set)
import qualified Data.Set as S
import Data.Text (Text)
import Data.Traversable
instance Subtree SecurityRequirement where
type SubtreeLevel SecurityRequirement = 'SecurityRequirementLevel
type
CheckEnv SecurityRequirement =
'[ ProdCons (Traced (Definitions SecurityScheme))
]
checkStructuralCompatibility env pc = do
let normalized = do
sec <- extract <$> pc
defs <- getH env
-- lookupScheme
pure $
for (IOHM.toList $ getSecurityRequirement sec) $ \(key, scopes) ->
(,scopes) <$> lookupScheme key defs
structuralMaybeWith
( \pc' -> do
let ProdCons pScopes cScopes = fmap snd <$> pc'
unless (pScopes == cScopes) structuralIssue
structuralList env $ fmap fst <$> pc'
pure ()
)
normalized
pure ()
checkSemanticCompatibility env bhv' pc = do
let schemes = getH @(ProdCons (Traced (Definitions SecurityScheme))) env
( ProdCons pErrs cErrs
, (ProdCons pSchemes cSchemes) ::
ProdCons [(Behavior 'SecuritySchemeLevel, Traced SecurityScheme, [Text])]
) =
NE.unzip $
partitionEithers <$> do
req <- pc
scheme <- schemes
pure $
let -- [(key, scopes)]
pairs = IOHM.toList . getSecurityRequirement . extract $ req
in pairs <&> \(key, scopes) -> do
case lookupScheme key scheme of
Nothing -> Left $ UndefinedSecurityScheme key
Just x -> Right (bhv' >>> step (SecuritySchemeStep key), x, scopes)
lookSimilar :: SecurityScheme -> SecurityScheme -> Bool
lookSimilar x y = case (_securitySchemeType x, _securitySchemeType y) of
(SecuritySchemeOAuth2 {}, SecuritySchemeOAuth2 {}) -> True
(SecuritySchemeHttp {}, SecuritySchemeHttp {}) -> True
(SecuritySchemeApiKey {}, SecuritySchemeApiKey {}) -> True
(SecuritySchemeOpenIdConnect {}, SecuritySchemeOpenIdConnect {}) -> True
_ -> False
issueAt bhv' `traverse_` pErrs
issueAt bhv' `traverse_` cErrs
for_ pSchemes $ \(bhv, pScheme, pScopes) -> do
let lookPromising = filter (lookSimilar (extract pScheme) . extract . (\(_, x, _) -> x)) cSchemes
anyOfAt bhv SecuritySchemeNotMatched $
lookPromising <&> \(_, cScheme, cScopes) -> do
let untracedSchemes = join bimap (_securitySchemeType . extract) (pScheme, cScheme)
scopedFlow :: Set Text -> OAuth2Flow t -> Writer [Issue 'SecuritySchemeLevel] (OAuth2Flow t)
scopedFlow scopes x = do
let scopesMap = _oAuth2Scopes x
for_ scopes $ \scope -> unless (scope `IOHM.member` scopesMap) $ tell [ScopeNotDefined scope]
pure $ x {_oAuth2Scopes = IOHM.filterWithKey (\k _ -> k `S.member` scopes) scopesMap}
scopedSchemeType :: [Text] -> SecuritySchemeType -> Writer [Issue 'SecuritySchemeLevel] SecuritySchemeType
scopedSchemeType scopes (SecuritySchemeOAuth2 (OAuth2Flows a b c d)) =
fmap SecuritySchemeOAuth2 $ OAuth2Flows <$> flow a <*> flow b <*> flow c <*> flow d
where
flow :: Maybe (OAuth2Flow t) -> Writer [Issue 'SecuritySchemeLevel] (Maybe (OAuth2Flow t))
flow = traverse $ scopedFlow $ S.fromList scopes
scopedSchemeType _ x = pure x
scopedScheme scopes x = do
sType <- scopedSchemeType scopes $ _securitySchemeType x
pure $ x {_securitySchemeType = sType}
(pc', errs) = runWriter $ ProdCons <$> scopedScheme pScopes `traverse` pScheme <*> scopedScheme cScopes `traverse` cScheme
case untracedSchemes of
(SecuritySchemeOpenIdConnect _, SecuritySchemeOpenIdConnect _) -> do
let missingScopes = S.fromList cScopes S.\\ S.fromList pScopes
unless (S.null missingScopes) $ issueAt bhv (ScopesMissing missingScopes)
(SecuritySchemeOAuth2 {}, SecuritySchemeOAuth2 {}) -> pure ()
_ -> unless (null pScopes && null cScopes) $ issueAt bhv CanNotHaveScopes
for_ errs $ issueAt bhv
checkCompatibility bhv env pc'
pure ()
pure ()
lookupScheme :: Text -> Traced (Definitions SecurityScheme) -> Maybe (Traced SecurityScheme)
lookupScheme k (Traced t m) = Traced (t >>> step (InsOrdHashMapKeyStep k)) <$> IOHM.lookup k m