2012-09-08 00:26:47 -04:00
{- git - annex assistant webapp configurator for pairing
-
- Copyright 2012 Joey Hess < joey @ kitenet . net >
-
2012-09-24 14:48:47 -04:00
- Licensed under the GNU AGPL version 3 or higher .
2012-09-08 00:26:47 -04:00
- }
2013-06-04 21:02:09 -04:00
{- # LANGUAGE TypeFamilies, QuasiQuotes, TemplateHaskell, OverloadedStrings # -}
2012-09-08 15:07:44 -04:00
{- # LANGUAGE CPP # -}
2012-09-08 00:26:47 -04:00
module Assistant.WebApp.Configurators.Pairing where
import Assistant.Pairing
2012-11-25 00:26:46 -04:00
import Assistant.WebApp.Common
2012-11-02 21:13:06 -04:00
import Assistant.Types.Buddies
2013-04-21 13:24:56 -04:00
import Annex.UUID
2012-09-08 15:21:34 -04:00
# ifdef WITH_PAIRING
import Assistant.Pairing.Network
2012-09-11 00:23:34 -04:00
import Assistant.Pairing.MakeRemote
2012-09-10 15:20:18 -04:00
import Assistant.Ssh
2012-09-09 16:24:34 -04:00
import Assistant.Alert
2012-10-30 14:34:48 -04:00
import Assistant.DaemonStatus
2012-09-08 02:02:39 -04:00
import Utility.Verifiable
2012-09-08 15:21:34 -04:00
# endif
2012-11-02 21:13:06 -04:00
# ifdef WITH_XMPP
import Assistant.XMPP.Client
2012-11-03 16:00:38 -04:00
import Assistant.XMPP.Buddies
2012-11-05 17:43:17 -04:00
import Assistant.XMPP.Git
2012-11-02 21:13:06 -04:00
import Network.Protocol.XMPP
2012-11-03 14:25:06 -04:00
import Assistant.Types.NetMessager
import Assistant.NetMessager
2013-03-12 21:51:03 -04:00
import Assistant.WebApp.RepoList
2014-02-25 14:09:39 -04:00
import Assistant.WebApp.Configurators
2014-02-25 14:53:43 -04:00
import Assistant.WebApp.Configurators.XMPP
2014-02-25 14:09:39 -04:00
# endif
2012-10-25 18:17:32 -04:00
import Utility.UserInfo
2012-11-05 12:21:13 -04:00
import Git
2012-09-08 00:26:47 -04:00
import qualified Data.Text as T
2013-04-16 22:46:20 -04:00
# ifdef WITH_PAIRING
2012-09-08 02:02:39 -04:00
import qualified Data.Text.Encoding as T
import qualified Data.ByteString.Lazy as B
import Data.Char
2012-09-09 16:24:34 -04:00
import qualified Control.Exception as E
import Control.Concurrent
2012-09-08 15:21:34 -04:00
# endif
2012-11-03 17:34:19 -04:00
# ifdef WITH_XMPP
import qualified Data.Set as S
# endif
2012-09-08 00:26:47 -04:00
2013-06-27 01:15:28 -04:00
getStartXMPPPairFriendR :: Handler Html
2012-11-02 21:13:06 -04:00
# ifdef WITH_XMPP
2013-03-15 15:05:02 -04:00
getStartXMPPPairFriendR = ifM ( isJust <$> liftAnnex getXMPPCreds )
2012-11-11 17:41:56 -04:00
( do
{- Ask buddies to send presence info, to get
- the buddy list populated . - }
liftAssistant $ sendNetMessage QueryPresence
pairPage $
2013-03-15 15:05:02 -04:00
$ ( widgetFile " configurators/pairing/xmpp/friend/prompt " )
2013-03-16 19:19:58 -04:00
, do
-- go get XMPP configured, then come back
2013-04-30 15:19:16 -04:00
redirect XMPPConfigForPairFriendR
2012-11-11 17:41:56 -04:00
)
2012-11-02 21:13:06 -04:00
# else
2013-03-15 15:05:02 -04:00
getStartXMPPPairFriendR = noXMPPPairing
2012-11-11 17:41:56 -04:00
2013-06-27 01:15:28 -04:00
noXMPPPairing :: Handler Html
2012-11-11 17:41:56 -04:00
noXMPPPairing = noPairing " XMPP "
2012-11-02 21:13:06 -04:00
# endif
2013-06-27 01:15:28 -04:00
getStartXMPPPairSelfR :: Handler Html
2012-11-02 21:13:06 -04:00
# ifdef WITH_XMPP
2013-03-15 15:05:02 -04:00
getStartXMPPPairSelfR = go =<< liftAnnex getXMPPCreds
where
2013-03-16 19:19:58 -04:00
go Nothing = do
-- go get XMPP configured, then come back
2013-04-30 15:19:16 -04:00
redirect XMPPConfigForPairSelfR
2013-03-15 15:05:02 -04:00
go ( Just creds ) = do
{- Ask buddies to send presence info, to get
- the buddy list populated . - }
liftAssistant $ sendNetMessage QueryPresence
let account = xmppJID creds
pairPage $
$ ( widgetFile " configurators/pairing/xmpp/self/prompt " )
# else
getStartXMPPPairSelfR = noXMPPPairing
# endif
2013-06-27 01:15:28 -04:00
getRunningXMPPPairFriendR :: BuddyKey -> Handler Html
2013-03-15 15:05:02 -04:00
getRunningXMPPPairFriendR = sendXMPPPairRequest . Just
2013-06-27 01:15:28 -04:00
getRunningXMPPPairSelfR :: Handler Html
2013-03-15 15:05:02 -04:00
getRunningXMPPPairSelfR = sendXMPPPairRequest Nothing
{- Sends a XMPP pair request, to a buddy or to self. -}
2013-06-27 01:15:28 -04:00
sendXMPPPairRequest :: Maybe BuddyKey -> Handler Html
2013-03-15 15:05:02 -04:00
# ifdef WITH_XMPP
sendXMPPPairRequest mbid = do
bid <- maybe getself return mbid
2012-11-03 17:34:19 -04:00
buddy <- liftAssistant $ getBuddy bid <<~ buddyList
2012-11-05 15:40:56 -04:00
go $ S . toList . buddyAssistants <$> buddy
where
go ( Just ( clients @ ( ( Client exemplar ) : _ ) ) ) = do
2013-03-04 16:36:38 -04:00
u <- liftAnnex getUUID
liftAssistant $ forM_ clients $ \ ( Client c ) -> sendNetMessage $
PairingNotification PairReq ( formatJID c ) u
2013-03-15 15:05:02 -04:00
xmppPairStatus True $
if selfpair then Nothing else Just exemplar
go _
{- Nudge the user to turn on their other device. -}
| selfpair = do
liftAssistant $ sendNetMessage QueryPresence
pairPage $
$ ( widgetFile " configurators/pairing/xmpp/self/retry " )
{- Buddy could have logged out, etc.
- Go back to buddy list . - }
| otherwise = redirect StartXMPPPairFriendR
selfpair = isNothing mbid
getself = maybe ( error " XMPP not configured " )
( return . BuddyKey . xmppJID )
=<< liftAnnex getXMPPCreds
2012-11-02 21:13:06 -04:00
# else
2013-03-15 15:05:02 -04:00
sendXMPPPairRequest _ = noXMPPPairing
2012-11-02 21:13:06 -04:00
# endif
{- Starts local pairing. -}
2013-06-27 01:15:28 -04:00
getStartLocalPairR :: Handler Html
2013-03-16 18:48:23 -04:00
getStartLocalPairR = postStartLocalPairR
2013-06-27 01:15:28 -04:00
postStartLocalPairR :: Handler Html
2012-09-08 15:07:44 -04:00
# ifdef WITH_PAIRING
2013-03-16 18:48:23 -04:00
postStartLocalPairR = promptSecret Nothing $
2012-11-02 21:13:06 -04:00
startLocalPairing PairReq noop pairingAlert Nothing
2012-09-10 17:53:51 -04:00
# else
2013-03-16 18:48:23 -04:00
postStartLocalPairR = noLocalPairing
2012-11-02 21:13:06 -04:00
2013-06-27 01:15:28 -04:00
noLocalPairing :: Handler Html
2012-11-02 21:13:06 -04:00
noLocalPairing = noPairing " local "
2012-09-10 17:53:51 -04:00
# endif
2012-11-02 21:13:06 -04:00
{- Runs on the system that responds to a local pair request; sets up the ssh
2012-09-11 00:23:34 -04:00
- authorized key first so that the originating host can immediately sync
- with us . - }
2013-06-27 01:15:28 -04:00
getFinishLocalPairR :: PairMsg -> Handler Html
2013-03-16 18:48:23 -04:00
getFinishLocalPairR = postFinishLocalPairR
2013-06-27 01:15:28 -04:00
postFinishLocalPairR :: PairMsg -> Handler Html
2012-09-10 17:53:51 -04:00
# ifdef WITH_PAIRING
2013-03-16 18:48:23 -04:00
postFinishLocalPairR msg = promptSecret ( Just msg ) $ \ _ secret -> do
2013-06-03 13:51:54 -04:00
repodir <- liftH $ repoPath <$> liftAnnex gitRepo
2012-11-05 12:21:13 -04:00
liftIO $ setup repodir
startLocalPairing PairAck ( cleanup repodir ) alert uuid " " secret
2012-10-31 02:34:03 -04:00
where
alert = pairRequestAcknowledgedAlert ( pairRepo msg ) . Just
2012-11-05 12:21:13 -04:00
setup repodir = setupAuthorizedKeys msg repodir
2013-10-01 13:43:35 -04:00
cleanup repodir = removeAuthorizedKeys True repodir $
2012-10-31 02:34:03 -04:00
remoteSshPubKey $ pairMsgData msg
uuid = Just $ pairUUID $ pairMsgData msg
2012-09-10 17:53:51 -04:00
# else
2013-03-16 18:48:23 -04:00
postFinishLocalPairR _ = noLocalPairing
2012-09-10 17:53:51 -04:00
# endif
2013-06-27 01:15:28 -04:00
getConfirmXMPPPairFriendR :: PairKey -> Handler Html
2012-11-11 22:29:16 -04:00
# ifdef WITH_XMPP
2013-03-15 15:05:02 -04:00
getConfirmXMPPPairFriendR pairkey @ ( PairKey _ t ) = case parseJID t of
2012-11-11 22:29:16 -04:00
Nothing -> error " bad JID "
Just theirjid -> pairPage $ do
let name = buddyName theirjid
2013-03-15 15:05:02 -04:00
$ ( widgetFile " configurators/pairing/xmpp/friend/confirm " )
2012-11-11 22:29:16 -04:00
# else
2013-03-15 15:05:02 -04:00
getConfirmXMPPPairFriendR _ = noXMPPPairing
2012-11-11 22:29:16 -04:00
# endif
2013-06-27 01:15:28 -04:00
getFinishXMPPPairFriendR :: PairKey -> Handler Html
2012-11-03 17:34:19 -04:00
# ifdef WITH_XMPP
2013-03-15 15:05:02 -04:00
getFinishXMPPPairFriendR ( PairKey theiruuid t ) = case parseJID t of
2012-11-03 17:34:19 -04:00
Nothing -> error " bad JID "
2012-11-05 17:43:17 -04:00
Just theirjid -> do
2013-03-04 16:36:38 -04:00
selfuuid <- liftAnnex getUUID
2012-11-05 17:43:17 -04:00
liftAssistant $ do
sendNetMessage $
PairingNotification PairAck ( formatJID theirjid ) selfuuid
finishXMPPPairing theirjid theiruuid
2013-03-15 15:05:02 -04:00
xmppPairStatus False $ Just theirjid
2012-11-03 17:34:19 -04:00
# else
2013-04-16 19:23:26 -04:00
getFinishXMPPPairFriendR _ = noXMPPPairing
2012-11-03 17:34:19 -04:00
# endif
2013-03-15 15:05:02 -04:00
{- Displays a page indicating pairing status and
- prompting to set up cloud repositories . - }
2012-11-10 20:38:52 -04:00
# ifdef WITH_XMPP
2013-06-27 01:15:28 -04:00
xmppPairStatus :: Bool -> Maybe JID -> Handler Html
2013-03-15 15:05:02 -04:00
xmppPairStatus inprogress theirjid = pairPage $ do
2012-11-10 20:38:52 -04:00
let friend = buddyName <$> theirjid
$ ( widgetFile " configurators/pairing/xmpp/end " )
# endif
2013-06-27 01:15:28 -04:00
getRunningLocalPairR :: SecretReminder -> Handler Html
2012-09-10 17:53:51 -04:00
# ifdef WITH_PAIRING
2012-11-02 21:13:06 -04:00
getRunningLocalPairR s = pairPage $ do
2012-09-11 12:26:42 -04:00
let secret = fromSecretReminder s
2012-11-02 21:13:06 -04:00
$ ( widgetFile " configurators/pairing/local/inprogress " )
2012-09-10 17:53:51 -04:00
# else
2012-11-02 21:13:06 -04:00
getRunningLocalPairR _ = noLocalPairing
2012-09-10 17:53:51 -04:00
# endif
# ifdef WITH_PAIRING
2012-11-02 21:13:06 -04:00
{- Starts local pairing, at either the PairReq (initiating host) or
2012-09-10 17:53:51 -04:00
- PairAck ( responding host ) stage .
-
- Displays an alert , and starts a thread sending the pairing message ,
- which will continue running until the other host responds , or until
- canceled by the user . If canceled by the user , runs the oncancel action .
-
- Redirects to the pairing in progress page .
- }
2012-11-02 21:13:06 -04:00
startLocalPairing :: PairStage -> IO () -> ( AlertButton -> Alert ) -> Maybe UUID -> Text -> Secret -> Widget
startLocalPairing stage oncancel alert muuid displaysecret secret = do
2013-06-03 13:51:54 -04:00
urlrender <- liftH getUrlRender
reldir <- fromJust . relDir <$> liftH getYesod
2012-09-11 15:51:27 -04:00
2013-03-16 00:12:28 -04:00
sendrequests <- liftAssistant $ asIO2 $ mksendrequests urlrender
2012-09-11 15:51:27 -04:00
{- Generating a ssh key pair can take a while, so do it in the
- background . - }
2013-03-16 00:12:28 -04:00
thread <- liftAssistant $ asIO $ do
2012-10-30 17:14:26 -04:00
keypair <- liftIO $ genSshKeyPair
pairdata <- liftIO $ PairData
2012-09-11 15:51:27 -04:00
<$> getHostname
2012-10-25 18:17:32 -04:00
<*> myUserName
2012-09-11 15:51:27 -04:00
<*> pure reldir
<*> pure ( sshPubKey keypair )
<*> ( maybe genUUID return muuid )
2012-09-11 12:58:00 -04:00
let sender = multicastPairMsg Nothing secret pairdata
let pip = PairingInProgress secret Nothing keypair pairdata stage
2012-10-30 17:14:26 -04:00
startSending pip stage $ sendrequests sender
void $ liftIO $ forkIO thread
2012-09-11 15:51:27 -04:00
2013-06-03 13:51:54 -04:00
liftH $ redirect $ RunningLocalPairR $ toSecretReminder displaysecret
2012-10-31 02:34:03 -04:00
where
{- Sends pairing messages until the thread is killed,
- and shows an activity alert while doing it .
-
2013-03-12 22:18:36 -04:00
- The cancel button returns the user to the DashboardR . This is
2012-10-31 02:34:03 -04:00
- not ideal , but they have to be sent somewhere , and could
- have been on a page specific to the in - process pairing
- that just stopped , so can't go back there .
- }
mksendrequests urlrender sender _stage = do
tid <- liftIO myThreadId
let selfdestruct = AlertButton
{ buttonLabel = " Cancel "
2013-11-23 00:54:08 -04:00
, buttonPrimary = True
2013-03-12 22:18:36 -04:00
, buttonUrl = urlrender DashboardR
2012-10-31 02:34:03 -04:00
, buttonAction = Just $ const $ do
oncancel
killThread tid
}
alertDuring ( alert selfdestruct ) $ liftIO $ do
_ <- E . try ( sender stage ) :: IO ( Either E . SomeException () )
return ()
2012-09-08 00:26:47 -04:00
2012-09-08 02:02:39 -04:00
data InputSecret = InputSecret { secretText :: Maybe Text }
2012-09-10 17:53:51 -04:00
{- If a PairMsg is passed in, ensures that the user enters a secret
- that can validate it . - }
2013-06-27 01:15:28 -04:00
promptSecret :: Maybe PairMsg -> ( Text -> Secret -> Widget ) -> Handler Html
2012-09-10 17:53:51 -04:00
promptSecret msg cont = pairPage $ do
2013-06-03 13:51:54 -04:00
( ( result , form ) , enctype ) <- liftH $
2014-04-18 02:07:09 +02:00
runFormPostNoToken $ renderBootstrap3 bootstrapFormLayout $
InputSecret <$> aopt textField ( bfs " Secret phrase " ) Nothing
2012-12-13 00:45:27 -04:00
case result of
FormSuccess v -> do
2012-09-08 13:04:19 -04:00
let rawsecret = fromMaybe " " $ secretText v
let secret = toSecret rawsecret
2012-09-08 21:06:10 -04:00
case msg of
2012-09-08 02:02:39 -04:00
Nothing -> case secretProblem secret of
2012-09-08 13:04:19 -04:00
Nothing -> cont rawsecret secret
2012-09-08 02:02:39 -04:00
Just problem ->
showform form enctype $ Just problem
2012-09-08 21:06:10 -04:00
Just m ->
2012-09-10 17:53:51 -04:00
if verify ( fromPairMsg m ) secret
2012-09-08 13:04:19 -04:00
then cont rawsecret secret
2012-09-08 02:02:39 -04:00
else showform form enctype $ Just
" That's not the right secret phrase. "
2012-12-13 00:45:27 -04:00
_ -> showform form enctype Nothing
2012-10-31 02:34:03 -04:00
where
showform form enctype mproblem = do
let start = isNothing msg
let badphrase = isJust mproblem
let problem = fromMaybe " " mproblem
let ( username , hostname ) = maybe ( " " , " " )
( \ ( _ , v , a ) -> ( T . pack $ remoteUserName v , T . pack $ fromMaybe ( showAddr a ) ( remoteHostName v ) ) )
( verifiableVal . fromPairMsg <$> msg )
u <- T . pack <$> liftIO myUserName
let sameusername = username == u
2012-11-02 21:13:06 -04:00
$ ( widgetFile " configurators/pairing/local/prompt " )
2012-09-08 02:02:39 -04:00
{- 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.) "
2013-03-05 16:21:13 -04:00
| B . length s < 6 = 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. "
2013-09-23 13:58:03 -04:00
| s == toSecret sampleQuote = Just " Speaking of foolishness, don't paste in the example I gave. Enter a different phrase, please! "
2012-09-08 02:02:39 -04:00
| 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 13:04:19 -04:00
2012-09-08 15:07:44 -04:00
# else
# endif
2012-10-26 12:56:19 -04:00
2013-06-27 01:15:28 -04:00
pairPage :: Widget -> Handler Html
2012-12-29 23:10:18 -04:00
pairPage = page " Pairing " ( Just Configuration )
2012-11-12 01:48:15 -04:00
2013-06-27 01:15:28 -04:00
noPairing :: Text -> Handler Html
2012-11-12 01:48:15 -04:00
noPairing pairingtype = pairPage $
$ ( widgetFile " configurators/pairing/disabled " )