Commit 1110d9d4 authored by Stephane Glondu's avatar Stephane Glondu

Add rudimentary password authentication

parent 11df675c
......@@ -37,16 +37,18 @@ the guides), or refer to it with a full path.
Web server
----------
The web server uses the [Ocsigen/Eliom](http://ocsigen.org)
framework. Eliom version 3 is needed.
The web server has the following additional dependencies:
With OPAM, you can install it with:
* [Eliom](http://ocsigen.org/eliom/), version 3
* [Csv](https://forge.ocamlcore.org/projects/csv/)
opam install eliom
With OPAM, you can install them with:
opam install eliom csv
On Debian-based systems, you can install it with:
apt-get install ocsigenserver eliom
apt-get install ocsigenserver eliom libcsv-ocaml-dev
But keep in mind that Belenios needs a very recent version of these
packages (in particular, eliom version 3 which is not in Debian stable
......
......@@ -132,6 +132,7 @@ Here is an excerpt of the sample configuration file:
<eliom module="_build/src/web/server.cma">
<enable-dummy/>
<enable-password db="demo/data/password_db.csv"/>
<source file="../belenios.tar.gz"/>
<main-election uuid="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"/>
<admin hash="XXX"/>
......@@ -142,8 +143,20 @@ Here is an excerpt of the sample configuration file:
The `<enable-dummy>` tag enables the dummy authentication method,
which just asks for a name. No security is intended. This is useful
for debugging or demonstration purposes but obviously not suitable for
production. This is the only authentication method supported at the
moment.
production. To disable it, just remove the tag.
The `<enable-password>` tag enables password-based authentication. It
takes as parameter a file, in CSV format, where each line consists of:
* a user name
* a salt
* SHA256(salt concatenated with password)
Additional fields are ignored. In the sample `password_db.csv` file, a
fourth field with the plaintext password is included. The sample file
has been generated with the following shell command:
for u in `seq 1 5`; do SALT=`pwgen`; PASS=`pwgen`; echo "user$u,$SALT,$(echo -n "$SALT$PASS" | sha256sum | read a b; echo $a),$PASS"; done
The `<source>` tag gives the path of the source tarball. Note that this
is a path on the local filesystem and not a URL. If you made local
......
<**/*.{ml,mli,byte,native,odoc}>: debug, annot, thread, package(zarith), package(calendar), package(uuidm), package(cryptokit), package(atdgen), package(yojson)
<demo/**/*.{ml,mli,byte,native,odoc}>: package(lwt.unix), syntax(camlp4o), package(lwt.syntax)
<src/web/*.{ml,mli,byte,native,odoc}>: package(eliom.server), syntax(camlp4o), package(lwt.syntax)
<src/web/*.{ml,mli,byte,native,odoc}>: package(eliom.server), syntax(camlp4o), package(lwt.syntax), package(csv)
<src/bin/*>: binary
<stuff/*>: binary
user1,oofeibae,ea66a656b159beaec4a10e6a2b18abf0ff348d1bd74dcb9019bc763b2df67c27,phiexoey
user2,laigezae,5a4230902e6dfb96e5b0ebe71b1ce88e9b37f8802dc075d046e62985e14909d2,eiseesho
user3,ivoorool,41c0f0c2e4b7db2d3d0e7e586bd8c31342eefec60d695a55395879dd6ff0357b,heethiax
user4,ceedohgh,c235819606ff698aa44cde97fc94ec8b91aeea8b0b68c137238dedae4c33ab01,liuyeige
user5,yeorogai,7727130f59b76646d4c3d6a9f3b6f4bede8a85210cb54bda913737c3e376168b,feoridee
......@@ -28,6 +28,7 @@
<extension findlib-package="zarith"/>
<extension findlib-package="uuidm"/>
<extension findlib-package="atdgen"/>
<extension findlib-package="csv"/>
<host charset="utf-8" hostfilter="*" defaulthostname="localhost">
<site path="booth" charset="utf-8">
......@@ -35,6 +36,7 @@
</site>
<eliom module="_build/src/web/server.cma">
<enable-dummy/>
<enable-password db="demo/data/password_db.csv"/>
<source file="../belenios.tar.gz"/>
<main-election uuid="6d122f00-2650-4de8-87de-30037a21f943"/>
<admin hash="97b878ee6f0b3fdec58875e7825e720a0cc0f973d73e415458b5544938d09fe6"/><!-- Ooj8jubi -->
......
......@@ -49,6 +49,7 @@ let secure_logfile = ref None
let data_dir = ref None
let source_file = ref None
let enable_dummy = ref false
let password_db_fname = ref None
let enable_cas = ref false
let admin_hash = ref ""
let main_election = ref None
......@@ -81,6 +82,12 @@ let () =
~obligatory:false
~init:(fun () -> enable_dummy := true)
();
element
~name:"enable-password"
~obligatory:false
~attributes:[
attribute ~name:"db" ~obligatory:true (fun s -> password_db_fname := Some s);
] ();
element
~name:"enable-cas"
~obligatory:false
......@@ -100,15 +107,30 @@ let () =
] ();
];;
module PMap = Map.Make(String)
let password_db = match !password_db_fname with
| None -> None
| Some fname -> Some (
List.fold_left (fun accu line ->
match line with
| username :: salt :: password :: _ ->
PMap.add username (salt, password) accu
| _ -> failwith "error in password db file"
) PMap.empty (Csv.load fname)
)
let login_default =
let open Services in
if !enable_dummy then login_dummy
else if password_db <> None then login_password
else Eliom_service.preapply login_cas None
let auth_systems =
(if !enable_cas then [
"CAS", Eliom_service.preapply Services.login_cas None
] else []) @
(if password_db <> None then ["password", Services.login_password] else []) @
(if !enable_dummy then ["dummy", Services.login_dummy] else [])
lwt () =
......@@ -255,7 +277,10 @@ let () = Eliom_registration.Html5.register
~service:Services.login_dummy
(fun () () ->
if !enable_dummy then (
let service = Services.create_string_login ~fallback:Services.login_dummy in
let service = Services.create_string_login
~fallback:Services.login_dummy
~post_params:Eliom_parameter.(string "username")
in
let () = Eliom_registration.Redirection.register
~service
~scope:Eliom_common.default_session_scope
......@@ -272,10 +297,44 @@ let () = Eliom_registration.Html5.register
) else fail_http 404
)
let () = Eliom_registration.Html5.register
~service:Services.login_password
(fun () () ->
match password_db with
| Some db ->
let service = Services.create_string_login
~fallback:Services.login_password
~post_params:Eliom_parameter.(string "username" ** string "password")
in
let () = Eliom_registration.Redirection.register
~service
~scope:Eliom_common.default_session_scope
(fun () (user_name, password) ->
if (
try
let salt, hashed = PMap.find user_name db in
sha256_hex (salt ^ password) = hashed
with Not_found -> false
) then (
let open Web_common in
let user_type = Password in
Eliom_reference.set Services.user (Some {user_name; user_type}) >>
Web_common.security_log (fun () ->
user_name ^ " successfully logged in using password"
) >> Services.get ()
) else forbidden ())
in
Templates.password_login ~auth_systems ~service
| None -> fail_http 404
)
let () = Eliom_registration.Html5.register
~service:Services.login_admin
(fun () () ->
let service = Services.create_string_login ~fallback:Services.login_admin in
let service = Services.create_string_login
~fallback:Services.login_admin
~post_params:Eliom_parameter.(string "password")
in
let () = Eliom_registration.Redirection.register
~service
~scope:Eliom_common.default_session_scope
......
......@@ -39,6 +39,11 @@ let login_dummy = service
~get_params:unit
()
let login_password = service
~path:["login-password"]
~get_params:unit
()
let login_admin = service
~path:["login-admin"]
~get_params:unit
......@@ -74,13 +79,11 @@ let logout = service
~get_params:unit
()
let create_string_login ~fallback =
let create_string_login ~fallback ~post_params =
Eliom_service.post_coservice
~csrf_safe:true
~csrf_scope:Eliom_common.default_session_scope
~fallback
~post_params:Eliom_parameter.(string "username")
()
~fallback ~post_params ()
let user = Eliom_reference.eref
~scope:Eliom_common.default_session_scope
......
......@@ -174,6 +174,31 @@ let dummy_login ~service =
] in
base ~title:"Login" ~content
let password_login ~service =
let form = post_form ~service
(fun (llogin, lpassword) ->
[
tablex [tbody [
tr [
th [label ~a:[a_for llogin] [pcdata "Username:"]];
td [string_input ~a:[a_maxlength 50] ~input_type:`Text ~name:llogin ()];
];
tr [
th [label ~a:[a_for lpassword] [pcdata "Password:"]];
td [string_input ~a:[a_maxlength 50] ~input_type:`Password ~name:lpassword ()];
];
]];
div [
string_input ~input_type:`Submit ~value:"Login" ();
]
]) ()
in
let content = [
h1 [pcdata "Password login"];
form;
] in
base ~title:"Password login" ~content
let format_date (date, _) =
CalendarLib.Printer.Precise_Fcalendar.sprint "%a, %d %b %Y %T %z" date
......
......@@ -25,7 +25,7 @@ open Util
open Serializable_builtin_t
open Serializable_t
type user_type = Dummy | CAS | Admin
type user_type = Dummy | Password | CAS | Admin
type user = {
user_name : string;
......@@ -35,6 +35,7 @@ type user = {
let string_of_user {user_name; user_type} =
match user_type with
| Dummy -> Printf.sprintf "dummy:%s" user_name
| Password -> Printf.sprintf "password:%s" user_name
| CAS -> user_name
| Admin -> Printf.sprintf "admin:%s" user_name
......
......@@ -22,7 +22,7 @@
open Serializable_builtin_t
open Serializable_t
type user_type = Dummy | CAS | Admin
type user_type = Dummy | Password | CAS | Admin
type user = {
user_name : string;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment