1 {-# LANGUAGE NoMonomorphismRestriction #-}
2 {-# LANGUAGE OverloadedStrings #-}
3 {-# LANGUAGE StrictData #-}
4 {-# OPTIONS_GHC -Wno-missing-signatures #-}
5 module Hjugement.CLI.Administrator where
7 import Control.Applicative (Applicative(..))
8 import Control.Monad (Monad(..), forM, forM_, join, void)
9 import Control.Monad.Trans.Class (MonadTrans(..))
10 import Control.Monad.Trans.Maybe (MaybeT(..))
11 import Control.Monad.Trans.State.Strict (runState, runStateT)
12 import Data.Bits (setBit)
14 import Data.ByteString (ByteString)
15 import Data.Either (Either(..))
16 import Data.Eq (Eq(..))
17 import Data.Foldable (Foldable, foldMap, length, null)
18 import Data.Function (($), (.), id, flip)
19 import Data.Functor ((<$>), (<$))
21 import Data.Maybe (Maybe(..), maybe, fromMaybe, fromJust)
22 import Data.Monoid (Monoid(..))
23 import Data.Ord (Ord(..))
24 import Data.Proxy (Proxy(..))
25 import Data.Semigroup (Semigroup(..))
26 import Data.String (String)
27 import Data.Text (Text)
28 import GHC.Natural (minusNatural, minusNaturalMaybe)
29 import GHC.Prim (coerce)
30 import Numeric.Natural (Natural)
32 import Prelude (logBase, ceiling, Num(..), (/), (^), fromIntegral, Double)
33 import Symantic.CLI as CLI
34 import System.IO (IO, FilePath)
35 import Text.Show (Show(..))
36 import qualified Crypto.Hash as Crypto
37 import qualified Data.Aeson as JSON
38 import qualified Data.ByteArray as ByteArray
39 import qualified Data.ByteString as BS
40 import qualified Data.ByteString.Char8 as BS8
41 import qualified Data.ByteString.Lazy as BSL
42 import qualified Data.List as List
43 import qualified Data.Text as T
44 import qualified Data.Text.Encoding as T
45 import qualified Data.Text.Lazy as TL
46 import qualified Data.Text.Lazy.Builder as TLB
47 import qualified Data.Text.Lazy.Builder.Int as TLB
48 import qualified Data.Text.Lazy.Encoding as TL
49 import qualified Data.Text.Lazy.IO as TL
50 import qualified Data.Time as Time
51 import qualified Lens.Family as Lens
52 import qualified Lens.Family.State.Strict as Lens
53 import qualified Pipes as Pip
54 import qualified Pipes.ByteString as PipBS
55 import qualified Pipes.Group as Pip
56 import qualified Pipes.Prelude as Pip
57 import qualified Pipes.Aeson as PipJSON (DecodingError(..))
58 import qualified Pipes.Aeson.Unchecked as PipJSON
59 import qualified Pipes.Safe as Pip
60 import qualified Pipes.Safe.Prelude as Pip
61 import qualified Pipes.Text as PipText
62 import qualified Pipes.Text.Encoding as PipText
63 import qualified Pipes.Text.IO as PipText
64 import qualified Symantic.Document as Doc
65 import qualified System.FilePath as FP
66 import qualified System.IO as IO
67 import qualified System.Posix as Posix
68 import qualified System.Random as Rand
69 import qualified Voting.Protocol as VP
71 import Hjugement.CLI.Utils
74 data Administrator_Params = Administrator_Params
79 "Commands for an administrator."
81 command "administrator" $
82 api_administrator_election
84 run_administrator globParams =
85 run_administrator_election globParams
86 :!: run_help api_administrator
89 data AdministratorElection_Params = AdministratorElection_Params
90 { administratorElection_crypto :: VP.FFC
91 , administratorElection_name :: Text
92 , administratorElection_description :: Text
93 , administratorElection_uuid :: Maybe Text
94 , administratorElection_grades :: [Text]
95 , administratorElection_defaultGrade :: Maybe Text
98 api_administrator_election =
99 "This command reads and checks the trustees' public keys in "<>fileRef"public_keys.jsons"<>".\
100 \ From which it computes the global election public key\
101 \ put it into an "<>fileRef"election.json"<>" file \
102 \ with the infos of the vote given here."
106 (AdministratorElection_Params
109 <*> api_param_description
112 <*> api_param_defaultGrade
115 <.> response @(Maybe ())
118 "Name of the election."
120 long "name" (var "STRING")
121 api_param_description =
122 "Description of the election."
124 longOpt "description" "" (var "STRING")
126 "UUID of the election."
128 longOpt "uuid" Nothing $
129 dimap Just fromJust $ var "UUID"
133 many1 (var @Text "STRING")
134 api_param_grades = toPermutation {-Default []-} $
136 "The grades to evaluate the choices, from the lowest to the highest."
138 tagged (TagLong "grade") $
140 api_param_defaultGrade = toPermDefault Nothing $
141 "The grade used when no grade is given by a voter.\n"<>
142 "Defaults to the lowest grade."
144 tagged (TagLong "default-grade") $
145 dimap Just fromJust $
148 run_administrator_election
150 o@AdministratorElection_Params{..}
152 VP.reify administratorElection_crypto $ \(crypto::Proxy c) -> runMaybeT $ do
154 case administratorElection_uuid of
155 Nothing -> Pip.liftIO $ Rand.getStdRandom $ runState $ VP.randomUUID
157 case VP.readUUID u of
158 Left err -> outputError $ Doc.from (show err)
159 Right uuid -> return uuid
160 let trusteeKeysPath = global_dir FP.</> "public_keys.jsons"
161 trusteeKeys :: [VP.TrusteePublicKey c] <- runPipeWithError $
162 Pip.toListM' $ readJSON trusteeKeysPath
163 -- TODO: check grades are distincts.
164 -- TODO: check default-grade is a grade.
165 saveJSON (global_dir FP.</> "election.json") $
166 VP.hashElection VP.Election
167 { VP.election_name = administratorElection_name
168 , VP.election_description = administratorElection_description
169 , VP.election_crypto = VP.ElectionCrypto_FFC
170 { electionCrypto_FFC_params = administratorElection_crypto
171 , electionCrypto_FFC_PublicKey = VP.combineIndispensableTrusteePublicKeys trusteeKeys
173 , VP.election_questions = (<$> quests) $ \quest -> VP.Question
174 { question_text = quest
175 , question_choices = administratorElection_grades
180 , VP.election_hash = VP.hashJSON JSON.Null
182 outputInfo $ "Created election with "<>Doc.from (show election_uuid)