{-# LANGUAGE OverloadedStrings #-} module Snap.AtlassianConnect.Connect ( initConnectSnaplet ) where -- Data file imports import qualified Paths_atlassian_connect_core as PACC -- Helper Imports import Data.ConfigurationHelpers -- Atlassian Connect Imports import qualified Data.Connect.Descriptor as D import Snap.AtlassianConnect.Data import qualified Snap.AtlassianConnect.PageToken as PT import qualified Snap.AtlassianConnect.Routes as CR import qualified Snap.AtlassianConnect.TimeUnits as TU -- Standard imports import qualified Control.Monad as CM import qualified Control.Monad.IO.Class as MI import qualified Crypto.Cipher.AES as CCA import qualified Data.ByteString.Char8 as BSC import qualified Data.Configurator as DC import qualified Data.Configurator.Types as DCT import qualified Data.EnvironmentHelpers as DE import Data.Maybe (fromMaybe) import Data.Text import Data.Time.Units import qualified Network.HostName as HN import qualified Network.URI as NU import qualified Snap.Snaplet as SS -- | Initialise the Atlassian Connect part of this application. This gives you an Atlassian Connect configuration that -- comes with: -- -- * An Atlassian Connect configuration file. -- * The definition of what it means to be an Atlassian Connect Tenant. -- * Default routes for your Atlassian Connect Descriptor. (With automatic configuration injection of Plugin Keys / Names) -- * Page Token support -- * Host Whitelist Support -- -- In short, it is the easy way to get up and running with Atlassian Connect in your Snap applications. initConnectSnaplet :: D.Plugin -> SS.SnapletInit b Connect initConnectSnaplet plugin = SS.makeSnaplet "connect" "Atlassian Connect" (Just dataDir) $ do SS.addRoutes CR.connectRoutes configurationDirectory <- SS.getSnapletFilePath MI.liftIO . fmap (toConnect plugin) $ SS.loadAppConfig "connect.cfg" configurationDirectory >>= loadConnectConfig dataDir :: IO FilePath dataDir = CM.liftM (++"/resources") PACC.getDataDir data ConnectConfig = ConnectConfig { ccSecretKey :: BSC.ByteString , ccBaseUrl :: NU.URI , ccPageTokenTimeout :: TU.ConnectSecond , ccHostWhiteList :: [Text] -- TODO This host whitelist should be loaded from configuration } toConnect :: D.Plugin -> ConnectConfig -> Connect toConnect plugin conf = Connect { connectPlugin = plugin , connectAES = CCA.initAES $ ccSecretKey conf , connectBaseUrl = ccBaseUrl conf , connectPageTokenTimeout = D.Timeout . TU.getSecond . ccPageTokenTimeout $ conf , connectHostWhitelist = ccHostWhiteList conf } loadConnectConfig :: DCT.Config -> IO ConnectConfig loadConnectConfig connectConf = do rawBaseUrl <- require connectConf "base_url" "Missing base url in connect configuration file." secret <- require connectConf "secret_key" "Missing secret key in connect configuration file." hostWhiteListFromConfig <- require connectConf "valid_hosts" "Missing a valid_hosts whitelist." hostWhiteList <- addLocalHost hostWhiteListFromConfig let keyLength = BSC.length secret CM.when (keyLength /= 32) $ fail ("Expected Atlassian Connect secret_key to be 32 Hex Digits long but was actually: " ++ show keyLength) envBaseUrl <- DE.getEnv "CONNECT_BASE_URL" envSecretKey <- DE.getEnv "CONNECT_SECRET_KEY" let baseUrlToParse = fromMaybe rawBaseUrl envBaseUrl case NU.parseURI baseUrlToParse of Nothing -> fail("Could not parse the baseUrl in the configuration file: " ++ rawBaseUrl) Just baseUrl -> do pageTokenTimeoutInSeconds <- DC.lookupDefault PT.defaultTimeoutSeconds connectConf "page_token_timeout_seconds" return ConnectConfig { ccBaseUrl = baseUrl , ccSecretKey = maybe secret BSC.pack envSecretKey , ccPageTokenTimeout = pageTokenTimeoutInSeconds , ccHostWhiteList = hostWhiteList } addLocalHost :: [Text] -> IO [Text] addLocalHost originalHosts = do localhost <- HN.getHostName return $ (pack localhost) : originalHosts