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(..), Alternative(..))
8 import Control.Monad (Monad(..), forM, forM_, join, unless, 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 Text
44 import qualified Data.Text.Encoding as Text
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 defaultTag "name" "" (var "STRING")
121 api_param_description =
122 "Description of the election."
124 defaultTag "description" "" (var "STRING")
126 "UUID of the election."
133 many1 (var @Text "STRING")
135 "The grades to evaluate the choices, from the lowest to the highest."
137 many1Tag (TagLong "grade") $
139 api_param_defaultGrade =
140 "The grade used when no grade is given by a voter.\n"<>
141 "Defaults to the lowest grade."
143 optionalTag (TagLong "default-grade") $
146 run_administrator_election
148 o@AdministratorElection_Params{..}
150 VP.reify administratorElection_crypto $ \(crypto::Proxy c) -> runMaybeT $ do
152 case administratorElection_uuid of
153 Nothing -> Pip.liftIO $ Rand.getStdRandom $ runState $ VP.randomUUID
155 case VP.readUUID u of
156 Left err -> outputError $ Doc.from (show err)
157 Right uuid -> return uuid
158 let trusteeKeysPath = global_dir FP.</> "public_keys.jsons"
159 trusteeKeys :: [VP.TrusteePublicKey c] <- runPipeWithError $
160 Pip.toListM' $ readJSON trusteeKeysPath
161 let grades = List.nub administratorElection_grades
162 unless (List.length grades > 1) $
163 outputError $ "at least two distinct grades are needed"
164 unless (List.length grades == List.length administratorElection_grades) $
165 outputError $ "indistinct grades: " <>
166 Doc.from (Text.intercalate ", " $ List.nub $
167 administratorElection_grades List.\\ grades)
168 let defaultGrade = fromMaybe (grades List.!!0) administratorElection_defaultGrade
169 -- FIXME: put defaultGrade into election.json
170 saveJSON (global_dir FP.</> "election.json") $
171 VP.hashElection VP.Election
172 { VP.election_name = administratorElection_name
173 , VP.election_description = administratorElection_description
174 , VP.election_crypto = VP.ElectionCrypto_FFC
175 { electionCrypto_FFC_params = administratorElection_crypto
176 , electionCrypto_FFC_PublicKey = VP.combineIndispensableTrusteePublicKeys trusteeKeys
178 , VP.election_questions = (<$> quests) $ \quest -> VP.Question
179 { question_text = quest
180 , question_choices = grades
185 , VP.election_hash = VP.hashJSON JSON.Null
187 outputInfo $ "Created election with "<>Doc.from (show election_uuid)