Plasma GitLab Archive
Projects Blog Knowledge

(*
 * <COPYRIGHT>
 * Copyright 2003 Gerd Stolpmann
 *
 * <GPL>
 * This file is part of WTimer.
 *
 * WTimer is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * WTimer 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with WDialog; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * </>
 *)

(* $Id: admin_access.ml,v 1.5 2003/03/23 11:59:13 gerd Exp $
 * ----------------------------------------------------------------------
 *
 *)

open Wd_dialog
open Wd_run_cgi
open Wd_types

open Db.Types
open Definitions

exception Force_dialog of dialog_type ;;  
  (* Like Change_dialog, but bypasses the check on unsaved changes *)

type row = { r_name : string;
	     r_is_new : bool;
	     r_delete : bool;
	     r_description : string;
	     r_editable : bool;
	     r_acl : (user_name * perm_type) list;
	   }
;;


let enum_bool =
  function 
      false -> Enum_value [ "no" ]
    | true  -> Enum_value [ "yes" ]
;;


class admin_access db universe name env =
object (self)
  inherit dialog universe name env
  inherit error_behaviour

  val mutable rows = [| |]          (* see read_rows, write_rows *)
  val mutable users = []

  method private check_auth () =
    check_auth db (self # dialog_variable "session")

  method private load_rows() =
    (* Load all rows from db *)
    let login = User (self # string_variable "session.login-user") in
    let luser = Db_ac.User.get db login login in
    let admin = luser.u_admin in
    let instances = Db_ac.Instance.list db login in
    let found_rows =
      List.flatten
	(List.map
	   (fun inst ->
	      try
		let p = Db_ac.Permission.get db login inst.i_name in
		        (* may raise Permission_denied *)
		[ { r_name = (match inst.i_name with Instance n -> n);
		    r_is_new = false;
		    r_delete = false;
		    r_description = inst.i_description;
		    r_editable = true;
		    r_acl = p.p_set;
		  }
		]
	      with
		  Db_ac.Types.Permission_denied ->
		    []
	   )
	   instances
	)
    in
    let found_rows' = 
      List.sort (fun a b -> Pervasives.compare a.r_name b.r_name) found_rows in
    rows <- Array.of_list found_rows';
    self # set_variable "checksum" (String_value (self # checksum()));
    users <- List.sort (fun a b -> Pervasives.compare a b)
               (List.map (fun u -> match u.u_name with User n -> n) 
                         (Db_ac.User.list db login));
    self # write_rows();

  method private store_rows() =
    (* store rows into db *)
    let login = User (self # string_variable "session.login-user") in
    for i = 0 to Array.length rows - 1 do
      let r = rows.(i) in
      if r.r_editable then (
	if r.r_delete then (
	  if not r.r_is_new then (
	    (* Delete the instance from the database: *)
	    Db_ac.Instance.delete db login (Instance r.r_name)
	  )
	) else (
	  (* insert/update *)
	  if r.r_name <> "" then (                (* empty names are ignored *)
	    if r.r_is_new then (
	      (* Create new instance: *)
	      let inst = 
		{ i_name = Instance r.r_name;
		  i_description = r.r_description;
		} in
	      Db_ac.Instance.insert db login inst;
	      (* Update permissions: *)
	      let p =
		{ p_instance = Instance r.r_name;
		  p_set = r.r_acl;
		} in
	      Db_ac.Permission.update db login p;
	    )
	    else (
	      (* Update Instance *)
	      let inst = 
		{ i_name = Instance r.r_name;
		  i_description = r.r_description;
		} in
	      Db_ac.Instance.update db login inst;
	      (* Update permissions: *)
	      let p =
		{ p_instance = Instance r.r_name;
		  p_set = r.r_acl;
		} in
	      Db_ac.Permission.update db login p;
	    )
	  )
	)
      )
    done;
    (* Finally commit *)
    Db.Connection.commit db

    
   method private checksum () =
    (* Returns the checksum of [rows] *)
    let v = self # dump_rows() in
    Digest.to_hex (Digest.string v)


  method private is_modified =
    (* Return whether there are unsaved modifications of data *)
    let db_checksum = self # string_variable "checksum" in
    let cur_checksum = self # checksum() in
    db_checksum <> cur_checksum


  method private dump_rows () =
    (* Creates a string representation of rows *)
    let buffer = Buffer.create 1000 in
    Array.iter
      (fun row ->
         Printf.bprintf 
           buffer
           "name=\"%s\" new=%b descr=\"%s\" edit=%b del=%b acl=\"%s\"\n"
	   (String.escaped row.r_name)
	   row.r_is_new
	   (String.escaped row.r_description)
	   row.r_editable
	   row.r_delete
	   (String.concat ";" 
	      (List.map 
		 (fun (User user, perm) -> 
		    (String.escaped ("\"" ^ String.escaped user ^ "\"") ^ "," ^
		     (match perm with
			  `Read -> "R"
			| `Write -> "W"
			| `Owner -> "O")))
		 (List.sort
		    (fun (User a,_) (User b,_) -> Pervasives.compare a b)
		    row.r_acl)))
      )
      rows;
    Buffer.contents buffer


  method private write_rows () =
    (* Set the dialog variables to the contents of [rows] *)
    List.iter
      (fun (dlgvar,getval) ->
	 let alist = Alist_value 
		       (Array.to_list 
			  (Array.mapi 
			     (fun id row -> (string_of_int id, getval id row))
			     rows)) in
	 self # set_variable dlgvar alist
      )
      [ "inst-del",    (fun id row -> enum_bool row.r_delete);
	"inst-name",   (fun id row -> String_value row.r_name);
	"inst-descr",  (fun id row -> String_value row.r_description);
	"inst-edit",   (fun id row -> String_value(if row.r_editable
						   then (
						     if row.r_is_new then
						       "new"
						     else
						       "yes")
						   else "no"));
	"acl-read",    (fun id row -> 
			  Dyn_enum_value
			    (List.map
			       (fun (User user, perm) -> (user, user))
			       (List.filter
				  (fun (_,perm) -> perm = `Read)
				  row.r_acl)));
	"acl-write",   (fun id row -> 
			  Dyn_enum_value
			    (List.map
			       (fun (User user, perm) -> (user, user))
			       (List.filter
				  (fun (_,perm) -> perm = `Write)
				  row.r_acl)));
	"acl-owner",   (fun id row -> 
			  Dyn_enum_value
			    (List.map
			       (fun (User user, perm) -> (user, user))
			       (List.filter
				  (fun (_,perm) -> perm = `Owner)
				  row.r_acl)));
      ];
    self # set_variable "acl-users"
      (Dyn_enum_value (List.map (fun n -> (n,n)) users));
    self # set_variable "modified" 
      (String_value (if self # is_modified then "yes" else "no"));


  method private read_rows () =
    (* Read the dialog variables and store contents as [row list] in the
     * instance variable [rows]. 
     *)
    let lookup_str = self # lookup_string_variable in        (* abbr *)
    let lookup_enum = self # lookup_enum_variable in         (* abbr *)
    let lookup_dynenum = self # lookup_dyn_enum_variable in   (* abbr *)
    let found_rows =
      Array.of_list
	(List.map
	   (fun (id_str, name_str) ->
	      let id = int_of_string id_str in
	      let edit = lookup_str "inst-edit" id_str in
	      let row = 
		{ r_name = lookup_str "inst-name" id_str;
		  r_is_new = (edit = "new");
		  r_delete = (lookup_enum "inst-del" id_str = [ "yes" ]);
		  r_description = lookup_str "inst-descr" id_str;
		  r_editable = (edit <> "no");
		  r_acl = (List.map
			     (fun (user,_) -> (User user, `Read))
			     (lookup_dynenum "acl-read" id_str) @
			   List.map
			     (fun (user,_) -> (User user, `Write))
			     (lookup_dynenum "acl-write" id_str) @
			   List.map
			     (fun (user,_) -> (User user, `Owner))
			     (lookup_dynenum "acl-owner" id_str));
		} in
	      row
	   )
	   (self # alist_variable "inst-name")
	)
    in
    rows <- found_rows;
    users <- List.map fst (self # dyn_enum_variable "acl-users");
      

  method private double_names =
    (* [true] if there are double names *)
    (* First check [rows] against itself: *)
    let login = User (self # string_variable "session.login-user") in
    let occurs = ref false in
    for i = 0 to Array.length rows - 1 do
      let name = rows.(i).r_name in
      if name <> "" && not rows.(i).r_delete then (
        for j = i+1 to Array.length rows - 1 do
          if name = rows.(j).r_name then occurs := true
        done
      )
    done;
    (* Otherwise check the new rows against the database: *)
    !occurs || (
      for i = 0 to Array.length rows - 1 do
        let name = rows.(i).r_name in
        if rows.(i).r_is_new && not rows.(i).r_delete then (
          if Db_ac.Instance.exists db login (Instance name) then occurs := true
        )
      done;
      !occurs
    )


  method private bad_owner =
    (* [true] if there is a primary sheet that is not owned by the
     * corresponding user
     *)
    let login = User (self # string_variable "session.login-user") in
    let occurs = ref false in
    for i = 0 to Array.length rows - 1 do
      let name = rows.(i).r_name in
      if Db_ac.User.exists db login (User name) then (
	(* This is a primary sheet *)
	try
	  if List.assoc (User name) rows.(i).r_acl <> `Owner then
	    occurs := true
	with
	    Not_found -> occurs := true
      )
    done;
    !occurs


  method private missing_owner =
    (* [true] if there is a sheet without owner *)
    let occurs = ref false in
    for i = 0 to Array.length rows - 1 do
      if not (List.exists (fun (_,perm) -> perm = `Owner) rows.(i).r_acl) then
	occurs := true
    done;
    !occurs


  method private save () =
    let error = ref false in
    if self # double_names  then ( self # add_message "error-double-names";
				   error := true );
    if self # bad_owner     then ( self # add_message "error-bad-ownership";
				   error := true );
    if self # missing_owner then ( self # add_message "error-missing-ownership";
				   error := true );
    if (not !error) then (
      self # store_rows();
      self # add_message "saved";
      self # load_rows();
      self # set_variable "acl-edit" (Dyn_enum_value []);
    )

  method private new_line() =
    let login = User (self # string_variable "session.login-user") in
    let row =
      { r_name = "";
	r_is_new = true;
	r_description = "";
	r_editable = true;
	r_delete = false;
	r_acl = [ login, `Owner ];
      } in
    rows <- Array.append rows [| row |];
    self # set_variable "acl-edit" (Dyn_enum_value []);


  method private activate_deferred_dialog ?(force = false) () =
    match self # dialog_variable "deferred-dialog" with
        None ->
          assert false
      | Some dlg ->
          self # set_variable "deferred-dialog" (Dialog_value None);
          raise(if force then Force_dialog dlg else Change_dialog dlg)


  method private add_message m =
    let msgs = self # enum_variable "message" in
    self # set_variable "message" (Enum_value(m :: msgs));
    self # set_variable "scroll_position" (String_value "");


  method private try_prepare_page() =
    if self # string_variable "modified" = "load" then begin
      self # load_rows()
    end


  method private try_handle() =
    (* Before anything else happens, parse the input boxes: *)
    self # set_variable "message" (Enum_value []);
    self # read_rows();
    (* Catch trials to leave this dialog, so we have the chance to 
     * warn the user if there are unmodified changes that would
     * be lost
     *)
    try
      (* Check whether a file or task menu button has been pressed: *)
      try
	raise(Change_dialog(Filemenu.handle (self :> dialog_type) 
			                    db self#event))
      with
	  Filemenu.Not_competent ->
	    ( try
		raise(Change_dialog(Taskmenu.handle (self :> dialog_type) self#event))
	      with
		  Taskmenu.Not_competent ->
		    (* No, it is another button: *)
                    ( match self # event with
                          Button "access-new" ->
			    self # new_line()
			| Button "access-save" ->
			    self # save()
			| Button "access-edit" ->
			    if (self # dyn_enum_variable "acl-edit") = [] then
			      self # add_message "error-nothing-selected"
			| Button "unsaved-save" ->
			    self # save();
			    self # activate_deferred_dialog ();
			| Button "unsaved-discard" ->
			    self # activate_deferred_dialog ~force:true ();
			| Button "unsaved-cancel" ->
			    self # set_variable "deferred-dialog" 
			                        (Dialog_value None);
			    raise(Change_page "access-list")
			| Button "cont-error" ->
			    self # set_next_page "access-list"
			| _ ->
			    ()
		    );
                    (* Always write rows (to repair formatting details) *)
                    self # write_rows()
	    )
    with
	Change_dialog new_dlg as change_request ->
          (* The user tries to leave the dialog. Check whether there are
           * unsaved changes of data.
           *)
          if self # is_modified then begin
            self # set_variable "deferred-dialog" (Dialog_value (Some new_dlg));            raise (Change_page "unsaved")
          end
          else 
            raise change_request
	      
      | Force_dialog new_dlg ->
          raise(Change_dialog new_dlg)

end
;;

Registry.new_admin_access := 
  fun universe env opt_session ->
    let dlg = universe # create env "admin-access" in
    dlg # set_variable "session" (Dialog_value opt_session);
    dlg
;;

(* ======================================================================
 * History:
 * 
 * $Log: admin_access.ml,v $
 * Revision 1.5  2003/03/23 11:59:13  gerd
 * 	GPL
 *
 * Revision 1.4  2003/02/03 01:28:59  gerd
 * 	Continued.
 *
 * Revision 1.3  2003/01/26 23:45:16  gerd
 * 	Improved error behaviour.
 *
 * Revision 1.2  2003/01/16 00:39:25  gerd
 * 	Continued.
 *
 * Revision 1.1  2002/11/16 12:34:50  gerd
 * 	Initial revision
 *
 * 
 *)

This web site is published by Informatikbüro Gerd Stolpmann
Powered by Caml