packages feed

powerdns-0.2.1: PowerDNS/API/TSIGKeys.hs

-- |
-- Module: PowerDNS.API.TSIGKeys
-- Description: TSIGKeys endpoints for PowerDNS API
--
-- Implementation of the API endpoints described at [PowerDNS TSIGKeys API](https://doc.powerdns.com/authoritative/http-api/tsigkey.html)

{-# LANGUAGE DataKinds          #-}
{-# LANGUAGE DeriveAnyClass     #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric      #-}
{-# LANGUAGE RecordWildCards    #-}
{-# LANGUAGE OverloadedStrings  #-}
{-# LANGUAGE TypeOperators      #-}
{-# LANGUAGE CPP                #-}
module PowerDNS.API.TSIGKeys
  (
  -- * API
    TSIGKeysAPI(..)

  -- * Data types
  , TSIGKey(..)
  , TSIGAlgorithm(..)
  )
where

import           Data.Char (toLower)
import           Data.Functor ((<&>))
import           Data.Data (Data)
import           Control.Monad.Fail (MonadFail)

import           Control.DeepSeq (NFData)
import           Data.Aeson ( FromJSON(..), ToJSON(..), Value(String), allNullaryToStringTag
                            , constructorTagModifier, defaultOptions
                            , genericParseJSON
                            , genericToJSON, withObject, object
                            , (.:), (.:?), (.=)
                            )
import qualified Data.ByteString as BS
import qualified Data.ByteString.Base64 as BS64
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import           Servant.API
import           Servant.API.Generic

---------------------------------------------------------------------------------------

data TSIGKeysAPI f = TSIGKeysAPI
  { apiListTSIGKeys  :: f :- "servers" :> Capture "server_id" T.Text :> "tsigkeys"
                          :> Get '[JSON] [TSIGKey]

  , apiCreateTSIGKey :: f :- "servers" :> Capture "server_id" T.Text :> "tsigkeys"
                          :> ReqBody '[JSON] TSIGKey
                          :> PostCreated '[JSON] TSIGKey

  , apiGetTSIGKey    :: f :- "servers" :> Capture "server_id" T.Text :> "tsigkeys" :> Capture "tsigkey_id" T.Text
                          :> Get '[JSON] TSIGKey

  , apiUpdateTSIGKey :: f :- "servers" :> Capture "server_id" T.Text :> "tsigkeys" :> Capture "tsigkey_id" T.Text
                          :> ReqBody '[JSON] TSIGKey
                          :> Put '[JSON] TSIGKey

  , apiDeleteTSIGKey :: f :- "servers" :> Capture "server_id" T.Text :> "tsigkeys" :> Capture "tsigkey_id" T.Text
                          :> DeleteNoContent
  } deriving Generic

----------------------------------------------------------------------------------------

data TSIGKey = TSIGKey
  { tsk_name :: T.Text
  , tsk_id :: T.Text
  , tsk_algorithm :: Maybe TSIGAlgorithm
  , tsk_key :: Maybe BS.ByteString
  -- ^ Unlike the original PowerDNS API we do not require the key to be base64 encoded.
  } deriving (Eq, Ord, Show, Generic, NFData, Data)

instance ToJSON TSIGKey where
  toJSON key = object [ "name" .= tsk_name key
                      , "id" .= tsk_id key
                      , "type" .= String "TSIGKey"
                      , "algorithm" .= tsk_algorithm key
                      , "key" .= (tsk_key key <&> encode64)
                      ]

instance FromJSON TSIGKey where
  parseJSON = withObject "TSIGKey" $ \o -> do
    tsk_name      <- o .: "name"
    tsk_id        <- o .: "id"
    tsk_algorithm <- o .:? "algorithm"
    tsk_key       <- o .:? "key" >>= traverse decode64
    pure TSIGKey{..}

encode64 :: BS.ByteString -> T.Text
encode64 = T.decodeUtf8 . BS64.encode

decode64 :: MonadFail m => T.Text -> m BS.ByteString
decode64 i = case BS64.decode (T.encodeUtf8 i) of
  Left err -> fail err
  Right k  -> pure k  

----------------------------------------------------------------------------------------

-- | Supported algorithms according to [PowerDNS TSIG Documentation](https://doc.powerdns.com/authoritative/tsig.html#tsig)
data TSIGAlgorithm = HMAC_MD5
                   | HMAC_SHA1
                   | HMAC_SHA224
                   | HMAC_SHA256
                   | HMAC_SHA384
                   | HMAC_SHA512
                   deriving (Eq, Ord, Show, Generic, NFData, Data)

instance ToJSON TSIGAlgorithm where
  toJSON = genericToJSON defaultOptions { allNullaryToStringTag = True
                                        , constructorTagModifier = algo
                                        }

instance FromJSON TSIGAlgorithm where
  parseJSON = genericParseJSON defaultOptions { allNullaryToStringTag = True
                                              , constructorTagModifier = algo
                                              }

algo :: String -> String
algo = fmap (\c -> if c == '_' then '-' else toLower c)