Removed dependency on imported merjislib.
[cocanwiki.git] / scripts / cocanwiki.ml
1 (* COCANWIKI scripts.
2  * Written by Richard W.M. Jones <rich@merjis.com>.
3  * Copyright (C) 2004 Merjis Ltd.
4  * $Id: cocanwiki.ml,v 1.3 2004/09/07 14:58:34 rich Exp $
5  *)
6
7 open Apache
8 open Registry
9 open Cgi
10 open Printf
11
12 open Cocanwiki_ok
13
14 module Pool = DbiPool (Dbi_postgres)
15
16 (* This function is used to grab a database handle.  It's used in a couple
17  * of very special places, and is not for general consumption.
18  *)
19 let _get_dbh r = Pool.get r "cocanwiki"
20
21 (* The [CgiExit] exception should be folded back into the base
22  * mod_caml code at some point.  It just causes the 'run' function to
23  * return at that point safely.  (XXX)
24  *)
25 exception CgiExit
26
27 (* Permissions and restrictions.
28  *
29  * Use the optional ~restrict parameter to register_script to restrict
30  * who can use the script.  For example:
31  *   register_script ~restrict:[CanEdit ; CanManageUsers] run
32  *)
33 type permissions_t = CanEdit | CanManageUsers
34
35 (* The "user object". *)
36 type user_t = Anonymous                 (* Not logged in. *)
37             | User of int * string * permissions_t list
38                                         (* Userid, name, permissions. *)
39
40 let test_permission edit_anon perm user =
41   if perm = CanEdit && edit_anon then true
42   else match user with
43       Anonymous -> false
44     | User (_, _, perms) -> List.mem perm perms
45
46 let can_edit edit_anon = test_permission edit_anon CanEdit
47 let can_manage_users = test_permission false CanManageUsers
48
49 (* Our wrapper around the standard [register_script] function. *)
50 let register_script ?(restrict = []) run =
51   (* Actually register the script with the real [Registry] module. *)
52   register_script
53     (fun r ->
54        let q = new cgi r in
55        let dbh = _get_dbh r in
56
57        (* Get the host ID, by comparing the Host: header with the hostnames
58         * table in the database.
59         *)
60        let hostid, hostname, edit_anon =
61          let hostname = try Request.hostname r
62          with Not_found -> failwith "No ``Host:'' header in request" in
63          let hostname = String.lowercase hostname in
64
65          let sth =
66            dbh#prepare_cached
67              "select h.id, h.canonical_hostname, h.edit_anon
68                 from hostnames hn, hosts h
69                where hn.name = ? and hn.hostid = h.id" in
70          sth#execute [`String hostname];
71
72          try
73            (match sth#fetch1 () with
74                 [ `Int id; `String hostname; `Bool edit_anon ] ->
75                   id, hostname, edit_anon
76               | _ -> assert false)
77          with
78              Not_found ->
79                failwith ("Hostname ``" ^ hostname ^ "'' not found in " ^
80                          "the hosts/hostnames tables in the database.") in
81
82        (* Look for the user's cookie, and determine from this the user
83         * object.
84         *)
85        let user =
86          try
87            let cookie =
88              (* Allow the user to deliberately specify an extra "cookie"
89               * parameter, which we will send back as a cookie.  This is
90               * useful for "mail my password"-type scripts.
91               *)
92              if q#param_exists "cookie" then (
93                let value = q#param "cookie" in
94                let cookie = Cookie.cookie ~name:"auth" ~value ~path:"/" () in
95                Table.set (Request.headers_out r) "Set-Cookie" cookie#as_string;
96                value
97              ) else (
98                (* Normal cookie, from the headers. *)
99                let header = Table.get (Request.headers_in r) "Cookie" in
100                let cookies = Cookie.parse header in
101                let cookie =
102                  List.find (fun cookie -> cookie#name = "auth") cookies in
103                cookie#value
104              ) in
105
106            let sth =
107              dbh#prepare_cached
108                "select u.id, u.name, u.can_edit, u.can_manage_users
109                   from usercookies uc, users u
110                  where uc.cookie = ? and uc.userid = u.id and u.hostid = ?" in
111            sth#execute [`String cookie; `Int hostid];
112            (match sth#fetch1 () with
113                 [ `Int userid; `String name;
114                   `Bool can_edit; `Bool can_manage_users ] ->
115                   let perms =
116                     (if can_edit then [ CanEdit ] else []) @
117                     (if can_manage_users then [ CanManageUsers ] else []) in
118                   User (userid, name, perms)
119               | _ -> assert false)
120          with
121              Not_found -> Anonymous
122        in
123
124        (* If the ~restrict parameter is given, then we want to check that
125         * the user has sufficient permission to run this script.
126         *)
127        let permitted =
128          match restrict with
129              [] -> true                 (* empty list = no restrictions *)
130            | rs ->
131                List.fold_left ((||)) false
132                  (List.map (fun r -> test_permission edit_anon r user) rs) in
133
134        if permitted then (
135          (* Call the actual CGI script. *)
136          try
137            run r q dbh (hostid, hostname, edit_anon) user
138          with
139              CgiExit -> ()
140        ) else
141          error ~back_button:true
142            ~title:"Access denied"
143            q "You do not have permission to access this part of the site."
144     )