hunk.code_window:       -- TODO: support ES256, ES384, ES512, PS256, PS384
         _       -> invalidJwk ("Key type: " <> T.unpack keyType <> " is not supported")
       where
         parseHmacKey key size ktype = do
           let secret = BL.fromStrict $ TE.encodeUtf8 key
           when (BL.length secret < size `div` 8) $
             invalidJwk "Key size too small"
  
hunk.labels: keep keep keep replace keep keep keep
hunk.after_edit:       parseHmacKey key size ktype cns = do
  
hunk.file_path: server/src-lib/Hasura/Server/Auth/JWT.hs
hunk.type: replace
file: {-# LANGUAGE DeriveLift             #-}
  {-# LANGUAGE FlexibleContexts       #-}
  {-# LANGUAGE FlexibleInstances      #-}
  {-# LANGUAGE LambdaCase             #-}
  {-# LANGUAGE MultiParamTypeClasses  #-}
  {-# LANGUAGE OverloadedStrings      #-}
  {-# LANGUAGE QuasiQuotes            #-}
  {-# LANGUAGE RankNTypes             #-}
  {-# LANGUAGE TemplateHaskell        #-}
  {-# LANGUAGE TypeApplications       #-}
  {-# LANGUAGE TypeFamilies           #-}
  {-# LANGUAGE TypeFamilyDependencies #-}
  
  module Hasura.RQL.DDL.Permission.Internal where
  
  import           Control.Lens               hiding ((.=))
  import           Data.Aeson.Casing
  import           Data.Aeson.TH
  import           Data.Aeson.Types
  import           Instances.TH.Lift          ()
  import           Language.Haskell.TH.Syntax (Lift)
  
  import qualified Data.ByteString.Builder    as BB
  import qualified Data.HashMap.Strict        as M
  import qualified Data.Text.Encoding         as TE
  import qualified Data.Text.Extended         as T
  
  import           Hasura.Prelude
  import           Hasura.RQL.GBoolExp
  import           Hasura.RQL.Types
  import           Hasura.Server.Utils
  import           Hasura.SQL.Types
  
  import qualified Database.PG.Query          as Q
  import qualified Hasura.SQL.DML             as S
  
  data PermColSpec
    = PCStar
    | PCCols ![PGCol]
    deriving (Show, Eq, Lift)
  
  instance FromJSON PermColSpec where
    parseJSON (String "*") = return PCStar
    parseJSON x            = PCCols <$> parseJSON x
  
  instance ToJSON PermColSpec where
    toJSON (PCCols cols) = toJSON cols
    toJSON PCStar        = "*"
  
  convColSpec :: FieldInfoMap -> PermColSpec -> [PGCol]
  convColSpec _ (PCCols cols) = cols
  convColSpec cim PCStar      =
    map pgiName $ fst $ partitionEithers $
    map fieldInfoToEither $ M.elems cim
  
  assertPermNotDefined
    :: (MonadError QErr m)
    => RoleName
    -> PermAccessor a
    -> TableInfo
    -> m ()
  assertPermNotDefined roleName pa tableInfo =
    when (permissionIsDefined rpi pa || roleName == adminRole)
    $ throw400 AlreadyExists $ mconcat
    [ "'" <> T.pack (show $ permAccToType pa) <> "'"
    , " permission on " <>> tiName tableInfo
    , " for role " <>> roleName
    , " already exists"
    ]
    where
      rpi = M.lookup roleName $ tiRolePermInfoMap tableInfo
  
  permissionIsDefined
    :: Maybe RolePermInfo -> PermAccessor a -> Bool
  permissionIsDefined rpi pa =
    isJust $ join $ rpi ^? _Just.permAccToLens pa
  
  assertPermDefined
    :: (MonadError QErr m)
    => RoleName
    -> PermAccessor a
    -> TableInfo
    -> m ()
  assertPermDefined roleName pa tableInfo =
    unless (permissionIsDefined rpi pa) $ throw400 PermissionDenied $ mconcat
    [ "'" <> T.pack (show $ permAccToType pa) <> "'"
    , " permission on " <>> tiName tableInfo
    , " for role " <>> roleName
    , " does not exist"
    ]
    where
      rpi = M.lookup roleName $ tiRolePermInfoMap tableInfo
  
  askPermInfo
    :: (MonadError QErr m)
    => TableInfo
    -> RoleName
    -> PermAccessor c
    -> m c
  askPermInfo tabInfo roleName pa =
    case M.lookup roleName rpim >>= (^. paL) of
      Just c  -> return c
      Nothing -> throw400 PermissionDenied $ mconcat
                 [ pt <> " permisison on " <>> tiName tabInfo
                 , " for role " <>> roleName
                 , " does not exist"
                 ]
    where
      paL = permAccToLens pa
      pt = permTypeToCode $ permAccToType pa
      rpim = tiRolePermInfoMap tabInfo
  
  savePermToCatalog
    :: (ToJSON a)
    => PermType
    -> QualifiedTable
    -> PermDef a
    -> Q.TxE QErr ()
  savePermToCatalog pt (QualifiedTable sn tn) (PermDef  rn qdef mComment) =
    Q.unitQE defaultTxErrorHandler [Q.sql|
             INSERT INTO
                 hdb_catalog.hdb_permission
                 (table_schema, table_name, role_name, perm_type, perm_def, comment)
             VALUES ($1, $2, $3, $4, $5 :: jsonb, $6)
                  |] (sn, tn, rn, permTypeToCode pt, Q.AltJ qdef, mComment) True
  
  dropPermFromCatalog
    :: QualifiedTable
    -> RoleName
    -> PermType
    -> Q.TxE QErr ()
  dropPermFromCatalog (QualifiedTable sn tn) rn pt =
    Q.unitQE defaultTxErrorHandler [Q.sql|
             DELETE FROM
                 hdb_catalog.hdb_permission
             WHERE
                 table_schema = $1
                 AND table_name = $2
                 AND role_name = $3
                 AND perm_type = $4
                  |] (sn, tn, rn, permTypeToCode pt) True
  
  type CreatePerm a = WithTable (PermDef a)
  
  data PermDef a =
    PermDef
    { pdRole       :: !RoleName
    , pdPermission :: !a
    , pdComment    :: !(Maybe T.Text)
    } deriving (Show, Eq, Lift)
  
  $(deriveFromJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''PermDef)
  
  instance (ToJSON a) => ToJSON (PermDef a) where
    toJSON = object . toAesonPairs
  
  instance (ToJSON a) => ToAesonPairs (PermDef a) where
   toAesonPairs (PermDef rn perm comment) =
    [ "role" .= rn
    , "permission" .= perm
    , "comment" .= comment
    ]
  
  data CreatePermP1Res a
    = CreatePermP1Res
    { cprInfo :: !a
    , cprDeps :: ![SchemaDependency]
    } deriving (Show, Eq)
  
  createPermP1 :: (P1C m) => QualifiedTable -> m TableInfo
  createPermP1 tn = do
    adminOnly
    askTabInfo tn
  
  procBoolExp
    :: (QErrM m, CacheRM m)
    => QualifiedTable -> FieldInfoMap -> S.Qual -> BoolExp
    -> m (S.BoolExp, [SchemaDependency])
  procBoolExp tn fieldInfoMap tq be = do
    abe   <- annBoolExp valueParser fieldInfoMap be
    sqlbe <- convFilterExp tq abe
    let deps = getBoolExpDeps tn abe
    return (sqlbe, deps)
  
  isReqUserId :: T.Text -> Bool
  isReqUserId = (== "req_user_id") . T.toLower
  
  getDependentHeaders :: BoolExp -> [T.Text]
  getDependentHeaders boolExp = case boolExp of
    BoolAnd exps         -> concatMap getDependentHeaders exps
    BoolOr exps          -> concatMap getDependentHeaders exps
    BoolCol (ColExp _ v) -> parseValue v
    BoolNot be           -> getDependentHeaders be
    where
      parseValue val = case val of
        (Object o) -> parseObject o
        _          -> parseOnlyString val
  
      parseOnlyString val = case val of
        (String t)
          | isXHasuraTxt t -> [T.toLower t]
          | isReqUserId t -> [userIdHeader]
          | otherwise -> []
        _ -> []
      parseObject o = flip concatMap (M.toList o) $ \(k, v) ->
                               if isRQLOp k
                               then parseOnlyString v
                               else []
  
  
  valueParser :: (MonadError QErr m) => PGColType -> Value -> m S.SQLExp
  valueParser columnType = \case
    -- When it is a special variable
    val@(String t)
      | isXHasuraTxt t -> asCurrentSetting t
      | isReqUserId t -> asCurrentSetting userIdHeader
      | otherwise -> txtRHSBuilder columnType val
  
    -- Typical value as Aeson's value
    val -> txtRHSBuilder columnType val
    where
      asCurrentSetting hdr = return $ S.SEUnsafe $
        "current_setting('hasura." <> dropAndSnakeCase hdr
         <> "')::" <> T.pack (show columnType)
  
  -- Convert where clause into SQL BoolExp
  convFilterExp :: (MonadError QErr m)
                => S.Qual -> GBoolExp AnnValS -> m S.BoolExp
  convFilterExp tq be =
    cBoolExp <$> convBoolRhs builderStrategy tq be
    where
      builderStrategy = mkBoolExpBuilder return
  
  injectDefaults :: QualifiedTable -> QualifiedTable -> Q.Query
  injectDefaults qv qt =
    Q.fromBuilder $ mconcat
    [ BB.string7 "SELECT hdb_catalog.inject_table_defaults("
    , TE.encodeUtf8Builder $ pgFmtLit vsn
    , BB.string7 ", "
    , TE.encodeUtf8Builder $ pgFmtLit vn
    , BB.string7 ", "
    , TE.encodeUtf8Builder $ pgFmtLit tsn
    , BB.string7 ", "
    , TE.encodeUtf8Builder $ pgFmtLit tn
    , BB.string7 ");"
    ]
    where
      QualifiedTable (SchemaName vsn) (TableName vn) = qv
      QualifiedTable (SchemaName tsn) (TableName tn) = qt
  
  data DropPerm a
    = DropPerm
    { dipTable :: !QualifiedTable
    , dipRole  :: !RoleName
    } deriving (Show, Eq, Lift)
  
  $(deriveJSON (aesonDrop 3 snakeCase){omitNothingFields=True} ''DropPerm)
  
  type family PermInfo a = r | r -> a
  
  class (ToJSON a) => IsPerm a where
  
    type DropPermP1Res a
  
    permAccessor
      :: PermAccessor (PermInfo a)
  
    buildPermInfo
      :: (QErrM m, CacheRM m)
      => TableInfo
      -> PermDef a
      -> m (PermInfo a)
  
    addPermP2Setup
      :: (MonadTx m, QErrM m) => QualifiedTable -> PermDef a -> PermInfo a -> m ()
  
    buildDropPermP1Res
      :: (QErrM m, CacheRM m, UserInfoM m)
      => DropPerm a
      -> m (DropPermP1Res a)
  
    dropPermP2Setup
      :: (CacheRWM m, MonadTx m) => DropPerm a -> DropPermP1Res a -> m ()
  
    getPermAcc1
      :: PermDef a -> PermAccessor (PermInfo a)
    getPermAcc1 _ = permAccessor
  
    getPermAcc2
      :: DropPerm a -> PermAccessor (PermInfo a)
    getPermAcc2 _ = permAccessor
  
  addPermP1 :: (QErrM m, CacheRM m, IsPerm a) => TableInfo -> PermDef a -> m (PermInfo a)
  addPermP1 tabInfo pd = do
    assertPermNotDefined (pdRole pd) (getPermAcc1 pd) tabInfo
    buildPermInfo tabInfo pd
  
  addPermP2 :: (IsPerm a, QErrM m, CacheRWM m, MonadTx m)
            => QualifiedTable -> PermDef a -> PermInfo a -> m ()
  addPermP2 tn pd permInfo = do
    addPermP2Setup tn pd permInfo
    addPermToCache tn (pdRole pd) pa permInfo
    liftTx $ savePermToCatalog pt tn pd
    where
      pa = getPermAcc1 pd
      pt = permAccToType pa
  
  instance (IsPerm a) => HDBQuery (CreatePerm a) where
  
    type Phase1Res (CreatePerm a) = PermInfo a
  
    phaseOne (WithTable tn pd) = do
      tabInfo <- createPermP1 tn
      addPermP1 tabInfo pd
  
    phaseTwo (WithTable tn pd) permInfo = do
      addPermP2 tn pd permInfo
      return successMsg
  
    schemaCachePolicy = SCPReload
  
  dropPermP1 :: (QErrM m, CacheRM m, UserInfoM m, IsPerm a) => DropPerm a -> m (PermInfo a)
  dropPermP1 dp@(DropPerm tn rn) = do
    adminOnly
    tabInfo <- askTabInfo tn
    askPermInfo tabInfo rn $ getPermAcc2 dp
  
  dropPermP2
    :: (IsPerm a, QErrM m, CacheRWM m, MonadTx m)
    => DropPerm a -> DropPermP1Res a -> m ()
  dropPermP2 dp@(DropPerm tn rn) p1Res = do
    dropPermP2Setup dp p1Res
    delPermFromCache pa rn tn
    liftTx $ dropPermFromCatalog tn rn pt
    where
      pa = getPermAcc2 dp
      pt = permAccToType pa
  
  instance (IsPerm a) => HDBQuery (DropPerm a) where
  
    type Phase1Res (DropPerm a) = DropPermP1Res a
  
    phaseOne = buildDropPermP1Res
  
    phaseTwo dp p1Res = dropPermP2 dp p1Res >> return successMsg
  
    schemaCachePolicy = SCPReload
  