-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMain.hs
More file actions
206 lines (174 loc) · 7.74 KB
/
Main.hs
File metadata and controls
206 lines (174 loc) · 7.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
{-# LANGUAGE RecordWildCards #-}
module Main (main) where
import Passman.Core.PassList
import Passman.Core.Hash
import Passman.Core.Config
import qualified Passman.Core.Config.Optional as OC
import Data.Maybe (fromMaybe)
import Control.Applicative (pure, (<$>), (<*>))
import Control.Monad (unless)
import Control.Monad.Trans (liftIO)
import Control.Monad.Trans.Maybe (runMaybeT, MaybeT(..))
import System.FilePath (splitFileName)
import Graphics.UI.WX
( (.+.), Align(..), Button, Frame, ListView, ListView(..), Prop(..)
, Size2D(..), Var, Window, activate, button, column, columns, command
, errorDialog, fileOpenDialog, fileSaveDialog, fill, frame, hfill
, infoDialog, listCtrlEx, listViewCtrl, listViewItems, listViewSetHandler
, listViewSetItems, minsize, on, passwordDialog, row, set, start, text
, varCreate, varCreate, varGet, varSet, varUpdate, when, widget
, windowReLayout, windowSetLayout
)
import Graphics.UI.WXCore
( Clipboard, EventList(..), clipboardCreate, clipboardSetData
, execClipBoardData, textDataObjectCreate, wxLC_REPORT, wxLC_SINGLE_SEL
, wxLC_REPORT
)
data GUI = GUI { gWin :: Frame ()
, gListView :: ListView PassListEntry
, gGetPassword :: Button ()
, gOpenFile :: Button ()
, gSaveFile :: Button ()
, gAddEntry :: Button ()
, gModifyEntry :: Button ()
, gRemoveEntry :: Button ()
, gConfig :: Var Config
, gSelectedItem :: Var Int
}
-- Main block
main :: IO ()
main = start gui
gui :: IO ()
gui = do
gWin <- frame [ text := "Passman" ]
gListView <- ListView <$> listCtrlEx gWin
(wxLC_SINGLE_SEL .+. wxLC_REPORT)
[ columns := [ ("Info", AlignLeft, 220)
, ("Length", AlignRight, -1)
, ("Mode", AlignRight, -1)
] ] <*> varCreate [] <*> pure entryToStrings
gGetPassword <- button gWin [ text := "Get Password" ]
gOpenFile <- button gWin [ text := "Open File" ]
gSaveFile <- button gWin [ text := "Save File" ]
gAddEntry <- button gWin [ text := "Add Entry" ]
gModifyEntry <- button gWin [ text := "Modify Entry" ]
gRemoveEntry <- button gWin [ text := "Remove Entry" ]
gConfig <- varCreate =<< initConfig gWin
gSelectedItem <- varCreate (-1)
let g = GUI {..}
set gWin [ on activate := flip when (windowReLayout gWin) ]
set gOpenFile [ on command := _FileEvent g False]
set gSaveFile [ on command := _FileEvent g True]
set gGetPassword [ on command := getPasswordEvent g]
listViewSetHandler gListView (listViewEvent g)
windowSetLayout gWin $ column 5 $ map (row 5)
[ [fill $ minsize (Size 400 100) $ widget $ listViewCtrl gListView]
, map (hfill . widget) [gGetPassword]
, map (hfill . widget) [gOpenFile, gSaveFile]
, map (hfill . widget) [gAddEntry, gModifyEntry, gRemoveEntry]
]
-- Event handlers
getPasswordEvent :: GUI -> IO ()
getPasswordEvent g@GUI{..} = do
selectedItem <- varGet gSelectedItem
if selectedItem < 0 then
errorDialog gWin "No item selected" "No item selected"
else do
entries <- varGet $ listViewItems gListView
getPasswordForEntry g (entries !! selectedItem)
_FileEvent :: GUI -> Bool -> IO ()
_FileEvent g@GUI{..} save = helper =<< passListDialog g save
where
_File = if save then saveFile else loadFile
helper :: Maybe FilePath -> IO ()
helper Nothing = return ()
helper (Just path) = _File gListView path >>= errHandler
errHandler Nothing = return ()
errHandler (Just err) = errorDialog' gWin err >> _FileEvent g save
listViewEvent :: GUI -> EventList -> IO ()
listViewEvent GUI{..} event = case event of
ListItemSelected i -> varSet gSelectedItem i
ListDeleteAllItems -> varSet gSelectedItem (-1)
_ -> return ()
-- Helper Functions
getPasswordForEntry :: GUI -> PassListEntry -> IO ()
getPasswordForEntry g@GUI{..} entry = do
let errorLoop = errorDialog' gWin "Incorrect password" >>
getPasswordForEntry g entry
config <- varGet gConfig
let hash = masterPasswordHash config
passwd <- passwordDialog' gWin "Please enter your password" ""
unless (null passwd) $ case masterPassword passwd of
Nothing -> errorLoop
Just mpass -> if checkMasterPassword hash mpass then do
setClipboardText $ generatePassword entry mpass
infoDialog' gWin ( "The password for "
++ passListEntryInfo entry
++ " is in the clipboard. Press OK when done"
)
setClipboardText ""
else errorLoop
setClipboardText :: String -> IO ()
setClipboardText t = clipboardCreate >>= flip execClipBoardData helper
where
helper :: Clipboard () -> IO ()
helper cl = textDataObjectCreate t >>= clipboardSetData cl >> return ()
passListDialog :: GUI -> Bool -> IO (Maybe FilePath)
passListDialog GUI{..} save = runMaybeT $ do
config <- liftIO $ varGet gConfig
let (p1,p2) = splitPassListPath $ getPassListPath config
dialog = if save then fileSaveDialog else fileOpenDialog
path <- MaybeT $ dialog gWin True True "Open file..."
[("Text Files (*.txt)", ["*.txt"])
,("All Files (*.*)",["*"])] p1 p2
liftIO $ updateConfig gConfig (setPassListPath path)
return path
getPassListPath :: Config -> Maybe FilePath
getPassListPath = OC.lookup "passlist path" . optionalConfig
setPassListPath :: FilePath -> Config -> Config
setPassListPath path config = config
{ optionalConfig = OC.insert "passlist path" path (optionalConfig config)
}
updateConfig :: Var Config -> (Config -> Config) -> IO ()
updateConfig vc f = varUpdate vc f >> varGet vc >>= saveConfig
splitPassListPath :: Maybe FilePath -> (String,String)
splitPassListPath = maybe ("","") splitFileName
loadFile :: ListView PassListEntry -> FilePath -> IO (Maybe String)
loadFile lc filename = fileToEntries filename >>= errHandler
where
errHandler (Right entries) = listViewSetItems lc entries >> return Nothing
errHandler (Left err) = return $ Just $ show err
saveFile :: ListView PassListEntry -> FilePath -> IO (Maybe String)
saveFile lc fn = fmap show <$> (varGet (listViewItems lc) >>= entriesToFile fn)
entryToStrings :: PassListEntry -> [String]
entryToStrings (PassListEntry x y z) = [x, fromMaybe "Max" $ show <$> y, show z]
initConfig :: Frame () -> IO Config
initConfig f = do
c <- loadConfig
case c of
Right config -> return config
Left ConfigFileNotFound -> do
hash <- initMasterPassword f
let config = Config { masterPasswordHash = hash
, optionalConfig = OC.empty
}
saveConfig config
return config
Left (InvalidConfig fp) ->
crashWithError f $ "Invalid config file. Please delete " ++ fp
initMasterPassword :: Frame () -> IO String
initMasterPassword f = do
spass <- passwordDialog' f "Please enter a master password" ""
case masterPassword spass of
Nothing -> initMasterPassword f
Just mpass -> hashMasterPassword mpass
passwordDialog' :: Window a -> String -> String -> IO String
passwordDialog' f s = passwordDialog f (s ++ ":") (s ++ ".")
errorDialog' :: Window a -> String -> IO ()
errorDialog' f = errorDialog f "Error"
infoDialog' :: Window a -> String -> IO ()
infoDialog' f = infoDialog f "Info"
crashWithError :: Frame () -> String -> IO a
crashWithError f m = do
errorDialog' f m
error m