{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies      #-}
--------------------------------------------------------------------------------
-- |
-- Module      :  Network.MQTT.Broker.Authentication
-- Copyright   :  (c) Lars Petersen 2016
-- License     :  MIT
--
-- Maintainer  :  info@lars-petersen.net
-- Stability   :  experimental
--------------------------------------------------------------------------------
module Network.MQTT.Broker.Authentication where

import           Control.Exception
import qualified Data.Binary              as B
import qualified Data.ByteString          as BS
import           Data.CaseInsensitive
import           Data.UUID                as UUID
import           Data.Word
import qualified Data.X509                as X509
import           GHC.Generics

import           Network.MQTT.Message
import           Network.MQTT.Trie as R

-- | A peer identity optionally associated with connection/session
--   specific information.
--newtype Principal = Principal T.Text deriving (Eq, Ord, Show)

-- | An `Authenticator` is able to determine a `Principal`'s identity from a
--   `Request`.
class (Exception (AuthenticationException a)) => Authenticator a where
  data AuthenticatorConfig a
  -- | This `Exception` may be thrown by any operation within this class.
  --   Operations /must/ only throw this type of exception. Other exceptions
  --   won't be catched and may kill the broker.
  data AuthenticationException a
  -- | Create a new authenticator instance from configuration.
  newAuthenticator       :: AuthenticatorConfig a -> IO a
  -- | Try to determine a `Principal`'s identity from a connection `Request`.
  --
  --   The operation shall return `Nothing` in case the authentication
  --   mechanism is working, but couldn't associate an identity. It shall
  --   throw an `AuthenticationException` in case of other problems.
  authenticate           :: a -> ConnectionRequest -> IO (Maybe PrincipalIdentifier)
  -- | Gets a principal by principal primary key (UUID).
  --
  --   The operation shall return `Nothing` in case the principal is not / no
  --   longer available. It shall throw an `AuthenticationException` in case
  --   of other problems.
  getPrincipal           :: a -> PrincipalIdentifier -> IO (Maybe Principal)

type PrincipalIdentifier = UUID

data Principal
   = Principal
   { principalUsername             :: Maybe Username
   , principalQuota                :: Quota
   , principalPublishPermissions   :: R.Trie ()
   , principalSubscribePermissions :: R.Trie ()
   , principalRetainPermissions    :: R.Trie ()
   } deriving (Eq, Show, Generic)

data Quota
   = Quota
   { quotaMaxIdleSessionTTL    :: Word64
   , quotaMaxPacketSize        :: Word64
   , quotaMaxPacketIdentifiers :: Word64
   , quotaMaxQueueSizeQoS0     :: Word64
   , quotaMaxQueueSizeQoS1     :: Word64
   , quotaMaxQueueSizeQoS2     :: Word64
   } deriving (Eq, Ord, Show, Generic)

instance B.Binary Quota
instance B.Binary Principal

-- | This class defines how the information gathered from a
--   connection request looks like. An `Authenticator` may use
--   whatever information it finds suitable to authenticate the `Principal`.
data ConnectionRequest
   = ConnectionRequest
   { requestClientIdentifier :: ClientIdentifier,
     requestCleanSession     :: Bool,
     -- | Is this connection secure in terms of
     --  [Transport Layer Security](https://en.wikipedia.org/wiki/Transport_Layer_Security)?
     requestSecure           :: Bool,
     -- | The username and password supplied with the MQTT handshake.
     requestCredentials      :: Maybe (Username, Maybe Password),
     -- | The HTTP request head in case the client connected via
     --   [WebSocket](https://en.wikipedia.org/wiki/WebSocket).
     requestHttp             :: Maybe (BS.ByteString, [(CI BS.ByteString, BS.ByteString)]),
     -- | An [X.509 certificate](https://en.wikipedia.org/wiki/X.509) chain
     --   supplied by the peer.
     --   It can be assumed that the transport layer implementation already
     --   verified that the peer owns the corresponding private key. The validation
     --   of the certificate claims (including certificate chain checking) /must/
     --   be performed by the `Authenticator`.
     requestCertificateChain :: Maybe X509.CertificateChain,
     requestRemoteAddress    :: Maybe BS.ByteString
   } deriving (Show)