2012-09-08 04:26:47 +00:00
{- git - annex assistant webapp configurator for pairing
-
- Pairing works like this :
-
2012-09-08 06:02:39 +00:00
- * The user opens StartPairR , which prompts them for a secret .
2012-09-08 17:04:19 +00:00
- * The user submits it . The pairing secret is stored for later .
- A PairReq is broadcast out .
2012-09-08 04:26:47 +00:00
- * On another device , it's received , and that causes its webapp to
- display an Alert .
- * The user there clicks the button , which opens FinishPairR ,
- which prompts them for the same secret .
- * The secret is used to verify the PairReq . If it checks out ,
- a PairAck is sent , and the other device adds the ssh key from the
- PairReq . An Alert is displayed noting that the pairing has been set up .
- * The PairAck is received back at the device that started the process .
- It's verified using the stored secret . The ssh key from the PairAck
- is added . An Alert is displayed noting that the pairing has been set
2012-09-08 17:04:19 +00:00
- up . The pairing secret is removed to prevent anyone cracking the
- crypto .
2012-09-08 04:26:47 +00:00
-
- Copyright 2012 Joey Hess < joey @ kitenet . net >
-
- Licensed under the GNU GPL version 3 or higher .
- }
{- # LANGUAGE TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings, RankNTypes # -}
2012-09-08 19:07:44 +00:00
{- # LANGUAGE CPP # -}
2012-09-08 04:26:47 +00:00
module Assistant.WebApp.Configurators.Pairing where
import Assistant.Pairing
2012-09-08 19:21:34 +00:00
# ifdef WITH_PAIRING
import Assistant.Pairing.Network
import Assistant.Common
2012-09-08 17:04:19 +00:00
import Assistant.DaemonStatus
2012-09-08 06:02:39 +00:00
import Utility.Verifiable
2012-09-08 19:21:34 +00:00
import Utility.Network
# endif
2012-09-08 04:26:47 +00:00
import Assistant.WebApp
import Assistant.WebApp.Types
import Assistant.WebApp.SideBar
import Utility.Yesod
import Yesod
import Data.Text ( Text )
2012-09-08 19:21:34 +00:00
# ifdef WITH_PAIRING
2012-09-08 04:26:47 +00:00
import qualified Data.Text as T
2012-09-08 06:02:39 +00:00
import qualified Data.Text.Encoding as T
import qualified Data.ByteString.Lazy as B
import Data.Char
import System.Posix.User
2012-09-08 19:21:34 +00:00
# endif
2012-09-08 04:26:47 +00:00
getStartPairR :: Handler RepHtml
2012-09-08 19:07:44 +00:00
# ifdef WITH_PAIRING
2012-09-08 17:04:19 +00:00
getStartPairR = promptSecret Nothing $ \ rawsecret secret -> do
2012-09-08 18:23:35 +00:00
hostname <- liftIO $ getHostname
2012-09-08 17:04:19 +00:00
username <- liftIO $ getUserName
2012-09-08 19:40:47 +00:00
reldir <- fromJust . relDir <$> lift getYesod
2012-09-08 17:04:19 +00:00
let sshkey = " " -- TODO generate/read ssh key
2012-09-08 18:23:35 +00:00
let mkmsg addr = PairReqM $ PairReq $
2012-09-08 19:40:47 +00:00
mkVerifiable ( PairData hostname addr username reldir sshkey ) secret
2012-09-08 17:04:19 +00:00
pip <- liftIO $ PairingInProgress secret <$> multicastPairMsg mkmsg
dstatus <- daemonStatus <$> lift getYesod
liftIO $ modifyDaemonStatus_ dstatus $
\ s -> s { pairingInProgress = pip : pairingInProgress s }
lift $ redirect $ InprogressPairR rawsecret
2012-09-08 19:07:44 +00:00
# else
getStartPairR = noPairing
# endif
2012-09-08 17:04:19 +00:00
getInprogressPairR :: Text -> Handler RepHtml
2012-09-08 19:07:44 +00:00
# ifdef WITH_PAIRING
2012-09-08 17:04:19 +00:00
getInprogressPairR secret = bootstrap ( Just Config ) $ do
2012-09-08 06:02:39 +00:00
sideBarDisplay
setTitle " Pairing "
2012-09-08 17:04:19 +00:00
$ ( widgetFile " configurators/inprogresspairing " )
2012-09-08 19:07:44 +00:00
# else
getInprogressPairR _ = noPairing
# endif
2012-09-08 04:26:47 +00:00
getFinishPairR :: PairReq -> Handler RepHtml
2012-09-08 19:07:44 +00:00
# ifdef WITH_PAIRING
2012-09-08 17:04:19 +00:00
getFinishPairR req = promptSecret ( Just req ) $ \ _ secret -> do
error " TODO "
2012-09-08 19:07:44 +00:00
# else
getFinishPairR _ = noPairing
# endif
2012-09-08 06:02:39 +00:00
2012-09-08 19:07:44 +00:00
# ifdef WITH_PAIRING
2012-09-08 06:02:39 +00:00
data InputSecret = InputSecret { secretText :: Maybe Text }
2012-09-08 17:04:19 +00:00
promptSecret :: Maybe PairReq -> ( Text -> Secret -> Widget ) -> Handler RepHtml
promptSecret req cont = bootstrap ( Just Config ) $ do
sideBarDisplay
setTitle " Pairing "
2012-09-08 06:02:39 +00:00
( ( result , form ) , enctype ) <- lift $
runFormGet $ renderBootstrap $
InputSecret <$> aopt textField " Secret phrase " Nothing
case result of
FormSuccess v -> do
2012-09-08 17:04:19 +00:00
let rawsecret = fromMaybe " " $ secretText v
let secret = toSecret rawsecret
2012-09-08 06:02:39 +00:00
case req of
Nothing -> case secretProblem secret of
2012-09-08 17:04:19 +00:00
Nothing -> cont rawsecret secret
2012-09-08 06:02:39 +00:00
Just problem ->
showform form enctype $ Just problem
Just r ->
if verified ( fromPairReq r ) secret
2012-09-08 17:04:19 +00:00
then cont rawsecret secret
2012-09-08 06:02:39 +00:00
else showform form enctype $ Just
" That's not the right secret phrase. "
_ -> showform form enctype Nothing
where
showform form enctype mproblem = do
let start = isNothing req
let badphrase = isJust mproblem
let msg = fromMaybe " " mproblem
let ( username , hostname ) = maybe ( " " , " " )
2012-09-08 18:23:35 +00:00
( \ v -> ( T . pack $ remoteUserName v , T . pack $ fromMaybe ( showAddr $ remoteAddress v ) ( remoteHostName v ) ) )
2012-09-08 06:02:39 +00:00
( verifiableVal . fromPairReq <$> req )
2012-09-08 17:04:19 +00:00
u <- T . pack <$> liftIO getUserName
2012-09-08 06:02:39 +00:00
let sameusername = username == u
let authtoken = webAppFormAuthToken
$ ( widgetFile " configurators/pairing " )
{- This counts unicode characters as more than one character,
- but that's ok ; they * do * provide additional entropy . - }
secretProblem :: Secret -> Maybe Text
secretProblem s
| B . null s = Just " The secret phrase cannot be left empty. (Remember that punctuation and white space is ignored.) "
| B . length s < 7 = Just " Enter a longer secret phrase, at least 6 characters, but really, a phrase is best! This is not a password you'll need to enter every day. "
| s == toSecret sampleQuote = Just " Speaking of foolishness, don't paste in the example I gave. Enter a different phrase, please! "
| otherwise = Nothing
toSecret :: Text -> Secret
toSecret s = B . fromChunks [ T . encodeUtf8 $ T . toLower $ T . filter isAlphaNum s ]
{- From Dickens -}
sampleQuote :: Text
sampleQuote = T . unwords
[ " It was the best of times, "
, " it was the worst of times, "
, " it was the age of wisdom, "
, " it was the age of foolishness. "
]
2012-09-08 17:04:19 +00:00
getUserName :: IO String
getUserName = userName <$> ( getUserEntryForID =<< getEffectiveUserID )
2012-09-08 19:07:44 +00:00
# else
noPairing :: Handler RepHtml
noPairing = bootstrap ( Just Config ) $ do
sideBarDisplay
setTitle " Pairing "
$ ( widgetFile " configurators/nopairing " )
# endif