Une MAJ de sécurité est nécessaire sur notre version actuelle. Elle sera effectuée lundi 02/08 entre 12h30 et 13h. L'interruption de service devrait durer quelques minutes (probablement moins de 5 minutes).

web_site.ml 71.8 KB
Newer Older
Stephane Glondu's avatar
Stephane Glondu committed
1 2 3
(**************************************************************************)
(*                                BELENIOS                                *)
(*                                                                        *)
Stephane Glondu's avatar
Stephane Glondu committed
4
(*  Copyright © 2012-2018 Inria                                           *)
Stephane Glondu's avatar
Stephane Glondu committed
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
(*                                                                        *)
(*  This program is free software: you can redistribute it and/or modify  *)
(*  it under the terms of the GNU Affero General Public License as        *)
(*  published by the Free Software Foundation, either version 3 of the    *)
(*  License, or (at your option) any later version, with the additional   *)
(*  exemption that compiling, linking, and/or using OpenSSL is allowed.   *)
(*                                                                        *)
(*  This program is distributed in the hope that it will be useful, but   *)
(*  WITHOUT ANY WARRANTY; without even the implied warranty of            *)
(*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *)
(*  Affero General Public License for more details.                       *)
(*                                                                        *)
(*  You should have received a copy of the GNU Affero General Public      *)
(*  License along with this program.  If not, see                         *)
(*  <http://www.gnu.org/licenses/>.                                       *)
(**************************************************************************)

22
open Lwt
23
open Platform
Stephane Glondu's avatar
Stephane Glondu committed
24
open Serializable_builtin_t
25
open Serializable_j
26
open Signatures
27
open Common
28
open Web_serializable_builtin_t
Stephane Glondu's avatar
Stephane Glondu committed
29
open Web_serializable_j
30
open Web_common
31
open Web_services
Stephane Glondu's avatar
Stephane Glondu committed
32

33
let source_file = ref "belenios.tar.gz"
34
let maxmailsatonce = ref 1000
35
let uuid_length = ref None
36
let default_group = ref ""
Stephane Glondu's avatar
Stephane Glondu committed
37

38 39
let ( / ) = Filename.concat

Stephane Glondu's avatar
Stephane Glondu committed
40
module PString = String
Stephane Glondu's avatar
Stephane Glondu committed
41

Stephane Glondu's avatar
Stephane Glondu committed
42 43 44 45 46 47 48 49 50
open Eliom_service
open Eliom_registration

(* Table with elections in setup mode. *)
let election_stable = Ocsipersist.open_table "site_setup"

(* Table with tokens given to trustees. *)
let election_pktokens = Ocsipersist.open_table "site_pktokens"

51 52 53
(* Table with tokens given to trustees (in threshold mode). *)
let election_tpktokens = Ocsipersist.open_table "site_tpktokens"

Stephane Glondu's avatar
Stephane Glondu committed
54 55 56
(* Table with tokens given to trustees (in threshold mode) to decrypt *)
let election_tokens_decrypt = Ocsipersist.open_table "site_tokens_decrypt"

Stephane Glondu's avatar
Stephane Glondu committed
57 58 59 60 61
(* Table with tokens given to credential authorities. *)
let election_credtokens = Ocsipersist.open_table "site_credtokens"

module T = Web_templates

Stephane Glondu's avatar
Stephane Glondu committed
62
let raw_find_election uuid =
63
  let%lwt raw_election = Web_persist.get_raw_election uuid in
64 65
  match raw_election with
  | Some raw_election ->
Stephane Glondu's avatar
Stephane Glondu committed
66
     return (Election.of_string raw_election)
67
  | _ -> Lwt.fail Not_found
Stephane Glondu's avatar
Stephane Glondu committed
68

Stephane Glondu's avatar
Stephane Glondu committed
69
module WCacheTypes = struct
70
  type key = uuid
Stephane Glondu's avatar
Stephane Glondu committed
71
  type value = Yojson.Safe.json election
Stephane Glondu's avatar
Stephane Glondu committed
72 73 74 75 76 77 78 79
end

module WCache = Ocsigen_cache.Make (WCacheTypes)

let find_election =
  let cache = new WCache.cache raw_find_election 100 in
  fun x -> cache#find x

80
let get_setup_election uuid =
Stephane Glondu's avatar
Stephane Glondu committed
81
  let%lwt se = Ocsipersist.find election_stable (raw_string_of_uuid uuid) in
82
  return (setup_election_of_string se)
83

84
let set_setup_election uuid se =
Stephane Glondu's avatar
Stephane Glondu committed
85
  Ocsipersist.add election_stable (raw_string_of_uuid uuid) (string_of_setup_election se)
86

Stephane Glondu's avatar
Stephane Glondu committed
87
let dump_passwords dir table =
Stephane Glondu's avatar
Stephane Glondu committed
88 89 90 91 92 93
  Lwt_io.(with_file Output (dir / "passwords.csv") (fun oc ->
    Ocsipersist.iter_step (fun voter (salt, hashed) ->
      write_line oc (voter ^ "," ^ salt ^ "," ^ hashed)
    ) table
  ))

94
let finalize_election uuid se =
Stephane Glondu's avatar
Stephane Glondu committed
95
  let uuid_s = raw_string_of_uuid uuid in
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
  (* voters *)
  let () =
    if se.se_voters = [] then failwith "no voters"
  in
  (* passwords *)
  let () =
    match se.se_metadata.e_auth_config with
    | Some [{auth_system = "password"; _}] ->
       if not @@ List.for_all (fun v -> v.sv_password <> None) se.se_voters then
         failwith "some passwords are missing"
    | _ -> ()
  in
  (* credentials *)
  let () =
    if not se.se_public_creds_received then
      failwith "public credentials are missing"
  in
  (* trustees *)
  let group = Group.of_string se.se_group in
  let module G = (val group : GROUP) in
116 117 118 119 120 121 122 123 124 125 126
  let%lwt y, trustees, pk_or_tp, private_keys =
    match se.se_threshold_trustees with
    | None ->
       let module KG = Trustees.MakeSimple (G) (LwtRandom) in
       let%lwt trustees, public_keys, private_key =
         match se.se_public_keys with
         | [] ->
            let%lwt private_key = KG.generate () in
            let%lwt public_key = KG.prove private_key in
            return (None, [public_key], `KEY private_key)
         | _ :: _ ->
127 128 129 130 131 132 133 134 135 136 137 138
            let private_key =
              List.fold_left (fun accu {st_private_key; _} ->
                  match st_private_key with
                  | Some x -> x :: accu
                  | None -> accu
                ) [] se.se_public_keys
            in
            let private_key = match private_key with
              | [] -> `None
              | [x] -> `KEY x
              | _ -> failwith "multiple private keys"
            in
139 140 141 142 143 144 145
            return (
                Some (List.map (fun {st_id; _} -> st_id) se.se_public_keys),
                (List.map
                   (fun {st_public_key; _} ->
                     if st_public_key = "" then failwith "some public keys are missing";
                     trustee_public_key_of_string G.read st_public_key
                   ) se.se_public_keys),
146
                private_key)
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
       in
       let y = KG.combine (Array.of_list public_keys) in
       return (y, trustees, `PK public_keys, private_key)
    | Some ts ->
       match se.se_threshold_parameters with
       | None -> failwith "key establishment not finished"
       | Some tp ->
          let tp = threshold_parameters_of_string G.read tp in
          let module P = Trustees.MakePKI (G) (LwtRandom) in
          let module C = Trustees.MakeChannels (G) (LwtRandom) (P) in
          let module K = Trustees.MakePedersen (G) (LwtRandom) (P) (C) in
          let trustees = List.map (fun {stt_id; _} -> stt_id) ts in
          let private_keys =
            List.map (fun {stt_voutput; _} ->
                match stt_voutput with
                | Some v ->
                   let voutput = voutput_of_string G.read v in
                   voutput.vo_private_key
                | None -> failwith "inconsistent state"
              ) ts
          in
          let y = K.combine tp in
          return (y, Some trustees, `TP tp, `KEYS private_keys)
170 171
  in
  (* election parameters *)
172 173 174 175 176 177 178 179 180
  let e_server_is_trustee = match private_keys with
      | `KEY _ -> Some true
      | `None | `KEYS _ -> None
  in
  let metadata = {
      se.se_metadata with
      e_trustees = trustees;
      e_server_is_trustee;
    } in
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
  let template = se.se_questions in
  let params = {
    e_description = template.t_description;
    e_name = template.t_name;
    e_public_key = {wpk_group = G.group; wpk_y = y};
    e_questions = template.t_questions;
    e_uuid = uuid;
  } in
  let raw_election = string_of_params (write_wrapped_pubkey G.write_group G.write) params in
  (* write election files to disk *)
  let dir = !spool_dir / uuid_s in
  let create_file fname what xs =
    Lwt_io.with_file
      ~flags:(Unix.([O_WRONLY; O_NONBLOCK; O_CREAT; O_TRUNC]))
      ~perm:0o600 ~mode:Lwt_io.Output (dir / fname)
      (fun oc ->
        Lwt_list.iter_s
          (fun v ->
            Lwt_io.write oc (what v) >>
              Lwt_io.write oc "\n") xs)
  in
  Lwt_unix.mkdir dir 0o700 >>
203 204 205 206
  (match pk_or_tp with
   | `PK pk -> create_file "public_keys.jsons" (string_of_trustee_public_key G.write) pk
   | `TP tp -> create_file "threshold.json" (string_of_threshold_parameters G.write) [tp]
  ) >>
207
  create_file "voters.txt" (fun x -> x.sv_id) se.se_voters >>
208
  create_file "metadata.json" string_of_metadata [metadata] >>
209 210
  create_file "election.json" (fun x -> x) [raw_election] >>
  (* construct Web_election instance *)
211 212 213 214
  let election = Election.of_string raw_election in
  let module W = (val Election.get_group election) in
  let module E = Election.Make (W) (LwtRandom) in
  let module B = Web_election.Make (E) in
215
  (* set up authentication *)
216
  let%lwt () =
217
    match metadata.e_auth_config with
218 219 220 221 222 223 224
    | None -> return ()
    | Some xs ->
       let auth_config =
         List.map (fun {auth_system; auth_instance; auth_config} ->
           auth_instance, (auth_system, List.map snd auth_config)
         ) xs
       in
225
       Web_persist.set_auth_config (Some uuid) auth_config
226 227
  in
  (* inject credentials *)
228
  let%lwt () =
229
    let fname = !spool_dir / uuid_s ^ ".public_creds.txt" in
230 231 232 233 234 235
    match%lwt read_file fname with
    | Some xs ->
       Lwt_list.iter_s B.inject_cred xs
       >> B.update_files ()
       >> Lwt_unix.unlink fname
    | None -> return_unit
236
  in
237
  (* create file with private keys, if any *)
238
  let%lwt () =
239 240 241 242
    match private_keys with
    | `None -> return_unit
    | `KEY x -> create_file "private_key.json" string_of_number [x]
    | `KEYS x -> create_file "private_keys.jsons" (fun x -> x) x
243 244 245 246
  in
  (* clean up setup database *)
  Ocsipersist.remove election_credtokens se.se_public_creds >>
  Lwt_list.iter_s
247
    (fun {st_token; _} ->
248 249 250 251
      if st_token <> "" then (
        Ocsipersist.remove election_pktokens st_token
      ) else return_unit
    )
252
    se.se_public_keys >>
253 254 255 256 257 258 259
  (match se.se_threshold_trustees with
   | None -> return_unit
   | Some ts ->
      Lwt_list.iter_s
        (fun x -> Ocsipersist.remove election_tpktokens x.stt_token)
        ts
  ) >>
260 261
  Ocsipersist.remove election_stable uuid_s >>
  (* inject passwords *)
262
  (match metadata.e_auth_config with
263
  | Some [{auth_system = "password"; _}] ->
264
     let table = "password_" ^ underscorize uuid in
265 266 267 268 269 270 271 272
     let table = Ocsipersist.open_table table in
     Lwt_list.iter_s
       (fun v ->
         let _, login = split_identity v.sv_id in
         match v.sv_password with
         | Some x -> Ocsipersist.add table login x
         | None -> return_unit
       ) se.se_voters >>
273
       dump_passwords (!spool_dir / uuid_s) table
274 275
  | _ -> return_unit) >>
  (* finish *)
276 277
  Web_persist.set_election_state uuid `Open >>
  Web_persist.set_election_date uuid (now ())
Stephane Glondu's avatar
Stephane Glondu committed
278

Stephane Glondu's avatar
Stephane Glondu committed
279 280 281 282
let cleanup_table ?uuid_s table =
  let table = Ocsipersist.open_table table in
  match uuid_s with
  | None ->
283
     let%lwt indexes = Ocsipersist.fold_step (fun k _ accu ->
Stephane Glondu's avatar
Stephane Glondu committed
284 285 286 287 288 289
       return (k :: accu)) table []
     in
     Lwt_list.iter_s (Ocsipersist.remove table) indexes
  | Some u -> Ocsipersist.remove table u

let cleanup_file f =
290
  try%lwt Lwt_unix.unlink f
Stephane Glondu's avatar
Stephane Glondu committed
291 292
  with _ -> return_unit

293
let archive_election uuid =
Stephane Glondu's avatar
Stephane Glondu committed
294
  let uuid_s = raw_string_of_uuid uuid in
295
  let uuid_u = underscorize uuid in
296
  let%lwt () = cleanup_table ~uuid_s "election_states" in
Stephane Glondu's avatar
Stephane Glondu committed
297
  let%lwt () = cleanup_table ~uuid_s "site_tokens_decrypt" in
298 299 300 301 302 303 304
  let%lwt () = cleanup_table ~uuid_s "election_pds" in
  let%lwt () = cleanup_table ~uuid_s "auth_configs" in
  let%lwt () = cleanup_table ("password_" ^ uuid_u) in
  let%lwt () = cleanup_table ("records_" ^ uuid_u) in
  let%lwt () = cleanup_table ("creds_" ^ uuid_u) in
  let%lwt () = cleanup_table ("ballots_" ^ uuid_u) in
  let%lwt () = cleanup_file (!spool_dir / uuid_s / "private_key.json") in
305
  let%lwt () = cleanup_file (!spool_dir / uuid_s / "private_keys.jsons") in
Stephane Glondu's avatar
Stephane Glondu committed
306 307
  return_unit

Stephane Glondu's avatar
Stephane Glondu committed
308 309
let () = Any.register ~service:home
  (fun () () ->
310
    Eliom_reference.unset Web_state.cont >>
311
    Redirection.send admin
Stephane Glondu's avatar
Stephane Glondu committed
312 313
  )

314
let get_finalized_elections_by_owner u =
315
  let%lwt elections, tallied, archived =
316
    Web_persist.get_elections_by_owner u >>=
317 318 319 320
    Lwt_list.fold_left_s (fun accu uuid ->
        let%lwt w = find_election uuid in
        let%lwt state = Web_persist.get_election_state uuid in
        let%lwt date = Web_persist.get_election_date uuid in
Stephane Glondu's avatar
Stephane Glondu committed
321
        let elections, tallied, archived = accu in
322
        match state with
Stephane Glondu's avatar
Stephane Glondu committed
323 324 325 326
        | `Tallied _ -> return (elections, (date, w) :: tallied, archived)
        | `Archived -> return (elections, tallied, (date, w) :: archived)
        | _ -> return ((date, w) :: elections, tallied, archived)
    ) ([], [], [])
327 328 329 330 331
  in
  let sort l =
    List.sort (fun (x, _) (y, _) -> datetime_compare x y) l |>
    List.map (fun (_, x) -> x)
  in
Stephane Glondu's avatar
Stephane Glondu committed
332
  return (sort elections, sort tallied, sort archived)
333

334 335 336 337 338
let with_site_user f =
  match%lwt Web_state.get_site_user () with
  | Some u -> f u
  | None -> forbidden ()

339 340 341 342 343 344 345
let () =
  Redirection.register ~service:admin_gdpr_accept
    (fun () () ->
      Eliom_reference.set Web_state.show_cookie_disclaimer false >>
      return admin
    )

Stephane Glondu's avatar
Stephane Glondu committed
346 347
let () = Html5.register ~service:admin
  (fun () () ->
348 349
    let%lwt gdpr = Eliom_reference.get Web_state.show_cookie_disclaimer in
    if gdpr then T.admin_gdpr () else
Stephane Glondu's avatar
Stephane Glondu committed
350
    let cont () = Redirection.send admin in
351
    Eliom_reference.set Web_state.cont [cont] >>
352 353
    let%lwt site_user = Web_state.get_site_user () in
    let%lwt elections =
354
      match site_user with
355
      | None -> return None
Stephane Glondu's avatar
Stephane Glondu committed
356
      | Some u ->
357 358
         let%lwt elections, tallied, archived = get_finalized_elections_by_owner u in
         let%lwt setup_elections =
359
           Ocsipersist.fold_step (fun k v accu ->
360
             let v = setup_election_of_string v in
361
             if v.se_owner = u then
Stephane Glondu's avatar
Stephane Glondu committed
362
               return ((uuid_of_raw_string k, v.se_questions.t_name) :: accu)
363 364
             else return accu
           ) election_stable []
365
         in
Stephane Glondu's avatar
Stephane Glondu committed
366
         return @@ Some (elections, tallied, archived, setup_elections)
Stephane Glondu's avatar
Stephane Glondu committed
367
    in
368
    T.admin ~elections ()
Stephane Glondu's avatar
Stephane Glondu committed
369 370
  )

371
let () = File.register ~service:source_code
Stephane Glondu's avatar
Stephane Glondu committed
372 373 374
  ~content_type:"application/x-gzip"
  (fun () () -> return !source_file)

375 376
let generate_uuid =
  let gen = Uuidm.v4_gen (Random.State.make_self_init ()) in
377 378 379 380 381 382
  fun () ->
  match !uuid_length with
  | Some length ->
     let%lwt token = generate_token ~length () in
     return @@ uuid_of_raw_string token
  | None -> return @@ uuid_of_raw_string @@ Uuidm.to_string @@ gen ()
Stephane Glondu's avatar
Stephane Glondu committed
383

384 385
let redir_preapply s u () = Redirection.send (preapply s u)

Stephane Glondu's avatar
Stephane Glondu committed
386 387 388 389 390 391 392 393 394 395
let create_new_election owner cred auth =
  let e_cred_authority = match cred with
    | `Automatic -> Some "server"
    | `Manual -> None
  in
  let e_auth_config = match auth with
    | `Password -> Some [{auth_system = "password"; auth_instance = "password"; auth_config = []}]
    | `Dummy -> Some [{auth_system = "dummy"; auth_instance = "dummy"; auth_config = []}]
    | `CAS server -> Some [{auth_system = "cas"; auth_instance = "cas"; auth_config = ["server", server]}]
  in
396
  let%lwt uuid = generate_uuid () in
Stephane Glondu's avatar
Stephane Glondu committed
397
  let uuid_s = raw_string_of_uuid uuid in
398
  let%lwt token = generate_token () in
Stephane Glondu's avatar
Stephane Glondu committed
399 400 401 402
  let se_metadata = {
    e_owner = Some owner;
    e_auth_config;
    e_cred_authority;
403
    e_trustees = None;
404
    e_languages = Some ["en"; "fr"];
405
    e_contact = None;
406
    e_server_is_trustee = None;
Stephane Glondu's avatar
Stephane Glondu committed
407 408
  } in
  let question = {
Stephane Glondu's avatar
Stephane Glondu committed
409
    q_answers = [| "Answer 1"; "Answer 2"; "Answer 3" |];
410
    q_blank = None;
Stephane Glondu's avatar
Stephane Glondu committed
411
    q_min = 1;
Stephane Glondu's avatar
Stephane Glondu committed
412
    q_max = 2;
Stephane Glondu's avatar
Stephane Glondu committed
413 414 415 416 417 418 419 420 421
    q_question = "Question 1?";
  } in
  let se_questions = {
    t_description = "Description of the election.";
    t_name = "Name of the election";
    t_questions = [| question |];
  } in
  let se = {
    se_owner = owner;
422
    se_group = !default_group;
Stephane Glondu's avatar
Stephane Glondu committed
423 424 425 426 427
    se_voters = [];
    se_questions;
    se_public_keys = [];
    se_metadata;
    se_public_creds = token;
428
    se_public_creds_received = false;
429 430 431 432
    se_threshold = None;
    se_threshold_trustees = None;
    se_threshold_parameters = None;
    se_threshold_error = None;
Stephane Glondu's avatar
Stephane Glondu committed
433
  } in
434
  let%lwt () = set_setup_election uuid se in
435
  let%lwt () = Ocsipersist.add election_credtokens token uuid_s in
436
  redir_preapply election_setup uuid ()
Stephane Glondu's avatar
Stephane Glondu committed
437 438 439 440

let () = Html5.register ~service:election_setup_pre
  (fun () () -> T.election_setup_pre ())

441
let () = Any.register ~service:election_setup_new
Stephane Glondu's avatar
Stephane Glondu committed
442
  (fun () (credmgmt, (auth, cas_server)) ->
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457
    with_site_user (fun u ->
        let%lwt credmgmt = match credmgmt with
          | Some "auto" -> return `Automatic
          | Some "manual" -> return `Manual
          | _ -> fail_http 400
        in
        let%lwt auth = match auth with
          | Some "password" -> return `Password
          | Some "dummy" -> return `Dummy
          | Some "cas" -> return @@ `CAS cas_server
          | _ -> fail_http 400
        in
        create_new_election u credmgmt auth
      )
  )
Stephane Glondu's avatar
Stephane Glondu committed
458

459
let with_setup_election_ro uuid f =
460
  with_site_user (fun u ->
461
      let%lwt se = get_setup_election uuid in
462
      if se.se_owner = u then
463
        f se
464 465
      else forbidden ()
    )
466

467 468 469 470 471 472 473
let () =
  Html5.register ~service:election_setup
    (fun uuid () ->
      with_setup_election_ro uuid (fun se ->
          T.election_setup uuid se ()
        )
    )
Stephane Glondu's avatar
Stephane Glondu committed
474

475 476 477 478
let () =
  Any.register ~service:election_setup_trustees
    (fun uuid () ->
      with_setup_election_ro uuid (fun se ->
479 480 481
          match se.se_threshold_trustees with
          | None -> T.election_setup_trustees uuid se () >>= Html5.send
          | Some _ -> redir_preapply election_setup_threshold_trustees uuid ()
482 483
        )
    )
484

485 486 487 488 489 490 491
let () =
  Html5.register ~service:election_setup_threshold_trustees
    (fun uuid () ->
      with_setup_election_ro uuid (fun se ->
          T.election_setup_threshold_trustees uuid se ()
        )
    )
492

493 494 495 496 497 498 499
let () =
  Html5.register ~service:election_setup_credential_authority
    (fun uuid () ->
      with_setup_election_ro uuid (fun se ->
          T.election_setup_credential_authority uuid se ()
        )
    )
500

Stephane Glondu's avatar
Stephane Glondu committed
501 502
let election_setup_mutex = Lwt_mutex.create ()

503
let with_setup_election ?(save = true) uuid f =
504 505
  with_site_user (fun u ->
      Lwt_mutex.with_lock election_setup_mutex (fun () ->
506
          let%lwt se = get_setup_election uuid in
507 508
          if se.se_owner = u then (
            try%lwt
509
              let%lwt r = f se in
Stephane Glondu's avatar
Stephane Glondu committed
510
              let%lwt () = if save then set_setup_election uuid se else return_unit in
511
              return r
512 513 514 515 516 517
            with e ->
              let service = preapply election_setup uuid in
              T.generic_page ~title:"Error" ~service (Printexc.to_string e) () >>= Html5.send
          ) else forbidden ()
        )
    )
518

519
let () =
520
  Any.register ~service:election_setup_languages
521 522 523 524
    (fun uuid languages ->
      with_setup_election uuid (fun se ->
          let langs = languages_of_string languages in
          match langs with
Stephane Glondu's avatar
Stephane Glondu committed
525
          | [] ->
526 527 528
             let service = preapply election_setup uuid in
             T.generic_page ~title:"Error" ~service
               "You must select at least one language!" () >>= Html5.send
Stephane Glondu's avatar
Stephane Glondu committed
529
          | _ :: _ ->
530 531 532
             let unavailable =
               List.filter (fun x ->
                   not (List.mem x available_languages)
Stephane Glondu's avatar
Stephane Glondu committed
533
                 ) langs
534 535 536 537 538
             in
             match unavailable with
             | [] ->
                se.se_metadata <- {
                   se.se_metadata with
Stephane Glondu's avatar
Stephane Glondu committed
539
                   e_languages = Some langs
540 541 542
                 };
                redir_preapply election_setup uuid ()
             | l :: _ ->
543 544
                let service = preapply election_setup uuid in
                T.generic_page ~title:"Error" ~service
545 546 547
                  ("No such language: " ^ l) () >>= Html5.send
        )
    )
548

549 550 551 552
let () =
  Any.register ~service:election_setup_contact
    (fun uuid contact ->
      with_setup_election uuid (fun se ->
553 554 555 556 557
          let contact =
            if contact = "" || contact = default_contact then
              None
            else Some contact
          in
558 559 560 561 562 563 564 565
          se.se_metadata <- {
              se.se_metadata with
              e_contact = contact
            };
          redir_preapply election_setup uuid ()
        )
    )

566
let () =
567
  Any.register ~service:election_setup_description
568 569 570 571 572 573 574 575 576
    (fun uuid (name, description) ->
      with_setup_election uuid (fun se ->
          se.se_questions <- {se.se_questions with
                               t_name = name;
                               t_description = description;
                             };
          redir_preapply election_setup uuid ()
        )
    )
577

578
let generate_password metadata langs title url id =
579
  let email, login = split_identity id in
580 581
  let%lwt salt = generate_token () in
  let%lwt password = generate_token () in
582
  let hashed = sha256_hex (salt ^ password) in
Stephane Glondu's avatar
Stephane Glondu committed
583 584
  let bodies = List.map (fun lang ->
    let module L = (val Web_i18n.get_lang lang) in
585 586
    let contact = T.contact_footer metadata L.please_contact in
    Printf.sprintf L.mail_password title login password url contact
Stephane Glondu's avatar
Stephane Glondu committed
587 588 589
  ) langs in
  let body = PString.concat "\n\n----------\n\n" bodies in
  let body = body ^ "\n\n-- \nBelenios" in
Stephane Glondu's avatar
Stephane Glondu committed
590 591 592 593 594
  let subject =
    let lang = List.hd langs in
    let module L = (val Web_i18n.get_lang lang) in
    Printf.sprintf L.mail_password_subject title
  in
595
  send_email email subject body >>
596
  return (salt, hashed)
597

598
let handle_password se uuid ~force voters =
599 600 601
  if List.length voters > !maxmailsatonce then
    Lwt.fail (Failure (Printf.sprintf "Cannot send passwords, there are too many voters (max is %d)" !maxmailsatonce))
  else
602 603 604 605
  let title = se.se_questions.t_name in
  let url = Eliom_uri.make_string_uri ~absolute:true ~service:election_home
    (uuid, ()) |> rewrite_prefix
  in
606
  let langs = get_languages se.se_metadata.e_languages in
607 608 609 610 611
  let%lwt () =
    Lwt_list.iter_s (fun id ->
        match id.sv_password with
        | Some _ when not force -> return_unit
        | None | Some _ ->
612
           let%lwt x = generate_password se.se_metadata langs title url id.sv_id in
613 614 615 616 617 618
           return (id.sv_password <- Some x)
      ) voters
  in
  let service = preapply election_setup uuid in
  T.generic_page ~title:"Success" ~service
    "Passwords have been generated and mailed!" () >>= Html5.send
619

620
let () =
621
  Any.register ~service:election_setup_auth_genpwd
622 623 624 625 626
    (fun uuid () ->
      with_setup_election uuid (fun se ->
          handle_password se uuid ~force:false se.se_voters
        )
    )
627

628
let () =
629
  Any.register ~service:election_regenpwd
Stephane Glondu's avatar
Stephane Glondu committed
630
    (fun (uuid, ()) () ->
631 632
      T.regenpwd uuid () >>= Html5.send)

633 634 635 636 637 638 639 640 641 642 643
let find_user_id uuid user =
  let uuid_s = raw_string_of_uuid uuid in
  let db = Lwt_io.lines_of_file (!spool_dir / uuid_s / "voters.txt") in
  let%lwt db = Lwt_stream.to_list db in
  let rec loop = function
    | [] -> Lwt.fail Not_found
    | id :: xs ->
       let _, login = split_identity id in
       if login = user then return id else loop xs
  in loop db

644
let () =
645
  Any.register ~service:election_regenpwd_post
646
    (fun (uuid, ()) user ->
647
      with_site_user (fun u ->
Stephane Glondu's avatar
Stephane Glondu committed
648
          let%lwt election = find_election uuid in
649
          let%lwt metadata = Web_persist.get_election_metadata uuid in
650
          if metadata.e_owner = Some u then (
651
            let table = "password_" ^ underscorize uuid in
652
            let table = Ocsipersist.open_table table in
Stephane Glondu's avatar
Stephane Glondu committed
653
            let title = election.e_params.e_name in
654 655 656 657 658 659
            let url = Eliom_uri.make_string_uri
                        ~absolute:true ~service:election_home
                        (uuid, ()) |> rewrite_prefix
            in
            let service = preapply election_admin (uuid, ()) in
            (try%lwt
660
               let%lwt id = find_user_id uuid user in
661
               let langs = get_languages metadata.e_languages in
662
               let%lwt x = generate_password metadata langs title url id in
663
               Ocsipersist.add table user x >>
Stephane Glondu's avatar
Stephane Glondu committed
664
                 dump_passwords (!spool_dir / raw_string_of_uuid uuid) table >>
665
                 T.generic_page ~title:"Success" ~service
666
                   ("A new password has been mailed to " ^ id ^ ".") ()
667 668 669 670 671 672 673 674
               >>= Html5.send
              with Not_found ->
                T.generic_page ~title:"Error" ~service
                  (user ^ " is not a registered user for this election.") ()
                >>= Html5.send
            )
          ) else forbidden ()
        )
675 676
    )

Stephane Glondu's avatar
Stephane Glondu committed
677
let () =
678
  Html5.register ~service:election_setup_questions
Stephane Glondu's avatar
Stephane Glondu committed
679
    (fun uuid () ->
680 681
      with_setup_election_ro uuid (fun se ->
          T.election_setup_questions uuid se ()
682
        )
683 684
    )

Stephane Glondu's avatar
Stephane Glondu committed
685
let () =
686
  Any.register ~service:election_setup_questions_post
687 688 689 690 691 692
    (fun uuid template ->
      with_setup_election uuid (fun se ->
          se.se_questions <- template_of_string template;
          redir_preapply election_setup uuid ()
        )
    )
Stephane Glondu's avatar
Stephane Glondu committed
693

Stephane Glondu's avatar
Stephane Glondu committed
694
let () =
695
  Html5.register ~service:election_setup_voters
Stephane Glondu's avatar
Stephane Glondu committed
696
    (fun uuid () ->
697 698
      with_setup_election_ro uuid (fun se ->
          T.election_setup_voters uuid se !maxmailsatonce ()
699
        )
Stephane Glondu's avatar
Stephane Glondu committed
700 701 702
    )

(* see http://www.regular-expressions.info/email.html *)
Stephane Glondu's avatar
Stephane Glondu committed
703
let identity_rex = Pcre.regexp
Stephane Glondu's avatar
Stephane Glondu committed
704
  ~flags:[`CASELESS]
705
  "^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,7}(,[A-Z0-9._%+-]+)?$"
Stephane Glondu's avatar
Stephane Glondu committed
706

Stephane Glondu's avatar
Stephane Glondu committed
707 708
let is_identity x =
  try ignore (Pcre.pcre_exec ~rex:identity_rex x); true
Stephane Glondu's avatar
Stephane Glondu committed
709 710
  with Not_found -> false

711
let merge_voters a b f =
712 713 714 715 716 717 718
  let existing = List.fold_left (fun accu sv ->
    SSet.add sv.sv_id accu
  ) SSet.empty a in
  let _, res = List.fold_left (fun (existing, accu) sv_id ->
    if SSet.mem sv_id existing then
      (existing, accu)
    else
719
      (SSet.add sv_id existing, {sv_id; sv_password = f sv_id} :: accu)
720 721 722
  ) (existing, List.rev a) b in
  List.rev res

Stephane Glondu's avatar
Stephane Glondu committed
723
let () =
724
  Any.register ~service:election_setup_voters_add
725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741
    (fun uuid voters ->
      with_setup_election uuid (fun se ->
          if se.se_public_creds_received then
            forbidden ()
          else (
            let voters = Pcre.split voters in
            let () =
              try
                let bad = List.find (fun x -> not (is_identity x)) voters in
                Printf.ksprintf failwith "%S is not a valid identity" bad
              with Not_found -> ()
            in
            se.se_voters <- merge_voters se.se_voters voters (fun _ -> None);
            redir_preapply election_setup_voters uuid ()
          )
        )
    )
742 743

let () =
744
  Any.register ~service:election_setup_voters_remove
745 746 747 748 749 750 751 752 753 754
    (fun uuid voter ->
      with_setup_election uuid (fun se ->
          if se.se_public_creds_received then
            forbidden ()
          else (
            se.se_voters <- List.filter (fun v -> v.sv_id <> voter) se.se_voters;
            redir_preapply election_setup_voters uuid ()
          )
        )
    )
Stephane Glondu's avatar
Stephane Glondu committed
755

756 757
let () =
  Any.register ~service:election_setup_voters_passwd
758 759 760 761 762 763
    (fun uuid voter ->
      with_setup_election uuid (fun se ->
          let voter = List.filter (fun v -> v.sv_id = voter) se.se_voters in
          handle_password se uuid ~force:true voter
        )
    )
764

Stephane Glondu's avatar
Stephane Glondu committed
765
let () =
766
  Any.register ~service:election_setup_trustee_add
767
    (fun uuid st_id ->
768
      with_setup_election uuid (fun se ->
769
          if is_email st_id then (
770
            let%lwt st_token = generate_token () in
771
            let trustee = {st_id; st_token; st_public_key = ""; st_private_key = None} in
772
            se.se_public_keys <- se.se_public_keys @ [trustee];
Stephane Glondu's avatar
Stephane Glondu committed
773
            let%lwt () = Ocsipersist.add election_pktokens st_token (raw_string_of_uuid uuid) in
774
            redir_preapply election_setup_trustees uuid ()
775 776 777 778 779 780
          ) else (
            let msg = st_id ^ " is not a valid e-mail address!" in
            let service = preapply election_setup_trustees uuid in
            T.generic_page ~title:"Error" ~service msg () >>= Html5.send
          )
        )
781 782
    )

783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799
let () =
  Any.register ~service:election_setup_trustee_add_server
    (fun uuid () ->
      with_setup_election uuid (fun se ->
          let st_id = "server" and st_token = "" in
          let module G = (val Group.of_string se.se_group) in
          let module K = Trustees.MakeSimple (G) (LwtRandom) in
          let%lwt private_key = K.generate () in
          let%lwt public_key = K.prove private_key in
          let st_public_key = string_of_trustee_public_key G.write public_key in
          let st_private_key = Some private_key in
          let trustee = {st_id; st_token; st_public_key; st_private_key} in
          se.se_public_keys <- se.se_public_keys @ [trustee];
          redir_preapply election_setup_trustees uuid ()
        )
    )

800
let () =
801
  Any.register ~service:election_setup_trustee_del
802
    (fun uuid index ->
803 804 805 806 807 808 809 810 811 812
      with_setup_election uuid (fun se ->
          let trustees, old =
            se.se_public_keys |>
              List.mapi (fun i x -> i, x) |>
              List.partition (fun (i, _) -> i <> index) |>
              (fun (x, y) -> List.map snd x, List.map snd y)
          in
          se.se_public_keys <- trustees;
          let%lwt () =
            Lwt_list.iter_s (fun {st_token; _} ->
813 814 815
                if st_token <> "" then (
                  Ocsipersist.remove election_pktokens st_token
                ) else return_unit
816 817 818
              ) old
          in
          redir_preapply election_setup_trustees uuid ()
819
        )
820 821
    )

Stephane Glondu's avatar
Stephane Glondu committed
822
let () =
823
  Html5.register ~service:election_setup_credentials
Stephane Glondu's avatar
Stephane Glondu committed
824
    (fun token () ->
825
     let%lwt uuid = Ocsipersist.find election_credtokens token in
Stephane Glondu's avatar
Stephane Glondu committed
826
     let uuid = uuid_of_raw_string uuid in
827
     let%lwt se = get_setup_election uuid in
Stephane Glondu's avatar
Stephane Glondu committed
828 829
     T.election_setup_credentials token uuid se ()
    )
830

Stephane Glondu's avatar
Stephane Glondu committed
831
let wrap_handler f =
832
  try%lwt f ()
Stephane Glondu's avatar
Stephane Glondu committed
833
  with
834
  | e -> T.generic_page ~title:"Error" (Printexc.to_string e) () >>= Html5.send
Stephane Glondu's avatar
Stephane Glondu committed
835 836

let handle_credentials_post token creds =
837
  let%lwt uuid = Ocsipersist.find election_credtokens token in
Stephane Glondu's avatar
Stephane Glondu committed
838
  let uuid = uuid_of_raw_string uuid in
839
  let%lwt se = get_setup_election uuid in
840
  if se.se_public_creds_received then forbidden () else
Stephane Glondu's avatar
Stephane Glondu committed
841
  let module G = (val Group.of_string se.se_group : GROUP) in
Stephane Glondu's avatar
Stephane Glondu committed
842
  let fname = !spool_dir / raw_string_of_uuid uuid ^ ".public_creds.txt" in
Stephane Glondu's avatar
Stephane Glondu committed
843 844 845 846 847 848 849 850
  Lwt_mutex.with_lock
    election_setup_mutex
    (fun () ->
     Lwt_io.with_file
       ~flags:(Unix.([O_WRONLY; O_NONBLOCK; O_CREAT; O_TRUNC]))
       ~perm:0o600 ~mode:Lwt_io.Output fname
       (fun oc -> Lwt_io.write_chars oc creds)
    ) >>
851
  let%lwt () =
Stephane Glondu's avatar
Stephane Glondu committed
852
    let i = ref 1 in
853 854 855 856 857 858 859 860 861 862 863 864 865
    match%lwt read_file fname with
    | Some xs ->
       return (
           List.iter (fun x ->
               try
                 let x = G.of_string x in
                 if not (G.check x) then raise Exit;
                 incr i
               with _ ->
                 Printf.ksprintf failwith "invalid credential at line %d" !i
             ) xs
         )
    | None -> return_unit
Stephane Glondu's avatar
Stephane Glondu committed
866
  in
867
  let () = se.se_metadata <- {se.se_metadata with e_cred_authority = None} in
868
  let () = se.se_public_creds_received <- true in
869
  set_setup_election uuid se >>
870
  T.generic_page ~title:"Success"
871
    "Credentials have been received and checked!" () >>= Html5.send
Stephane Glondu's avatar
Stephane Glondu committed
872 873

let () =
874
  Any.register ~service:election_setup_credentials_post
Stephane Glondu's avatar
Stephane Glondu committed
875 876 877
    (fun token creds ->
     let s = Lwt_stream.of_string creds in
     wrap_handler (fun () -> handle_credentials_post token s))
878

Stephane Glondu's avatar
Stephane Glondu committed
879
let () =
880
  Any.register ~service:election_setup_credentials_post_file
Stephane Glondu's avatar
Stephane Glondu committed
881 882 883 884
    (fun token creds ->
     let s = Lwt_io.chars_of_file creds.Ocsigen_extensions.tmp_filename in
     wrap_handler (fun () -> handle_credentials_post token s))

885
module CG = Credential.MakeGenerate (LwtRandom)