Browse Source

- initial import

master
Jan Dittberner 15 years ago
commit
5e60bf4a9a
  1. 5
      AUTHORS
  2. 211
      INSTALL
  3. 18
      Makefile
  4. 71
      README
  5. 6
      TODO
  6. 2
      admin/.htaccess
  7. 75
      admin/common.inc.php
  8. 331
      admin/directories.php
  9. 47
      admin/dynaform.css
  10. 88
      admin/format.css
  11. 69
      admin/getgroups.php
  12. BIN
      admin/images/assign.png
  13. BIN
      admin/images/delete.png
  14. BIN
      admin/images/directory.png
  15. BIN
      admin/images/edit.png
  16. BIN
      admin/images/groups.png
  17. BIN
      admin/images/newdirectory.png
  18. BIN
      admin/images/newuser.png
  19. BIN
      admin/images/throbber.gif
  20. BIN
      admin/images/x.png
  21. 48
      admin/index.php
  22. 305
      admin/scripts/autocomplete.js
  23. 148
      admin/scripts/directories.js
  24. 29
      admin/scripts/helper.js
  25. 1
      admin/scripts/jquery.js
  26. 189
      admin/scripts/users.js
  27. 39
      admin/templates/directories.html
  28. 1
      admin/templates/error.xml
  29. 1
      admin/templates/footer.html
  30. 8
      admin/templates/header.html
  31. 10
      admin/templates/start.html
  32. 37
      admin/templates/users.html
  33. 317
      admin/users.php
  34. 35
      config/config.inc.php
  35. 36
      config/dbsettings.inc.php
  36. 25
      setup/schema.sql
  37. 52
      setup/webdavadmin.vhost

5
AUTHORS

@ -0,0 +1,5 @@
======================
Authors of WebDAVAdmin
======================
Jan Dittberner <jan@dittberner.info>

211
INSTALL

@ -0,0 +1,211 @@
=======================================
WebDAVAdmin installation instructions
=======================================
:Author: Jan Dittberner
:Contact: jan@dittberner.info
:Version: 0.1
:Revision: $Revision$
:Date: $Date$
:Copyright: Copyright (C) 2007 Jan Dittberner
.. contents::
Unpack WebDAVAdmin
==================
1. unpack the WebDAVAdmin distribution file somewhere::
cd ~/tmp/
tar xjf webdavadmin-0.1.tar.bz2
``~/tmp/`` is just an example to be able to reference it in these
installation instructions
Setup PostgreSQL and your database
==================================
1. Install PostgreSQL by the means of your operating system. For
Debian GNU/Linux 4.0 Etch execute::
sudo aptitude install postgresql-8.1
2. Switch to user postgres::
sudo su - postgres
and
1) create a user for WebDAVAdmin::
createuser -SDRP myuser
when prompted type the password for the database user twice
2) create a database::
createdb --owner=myuser --encoding=UTF-8 mydb
3) exit the postgres shell
You may skip theese steps if you want to use an existing database
3. Import the schema for WebDAVAdmin::
psql -h localhost -U myuser mydb < ~/tmp/webdavadmin-0.1/setup/schema.sql
when prompted type the password for your database user.
Setup Apache
============
1. Install, enable and configure apache and the apache modules
- mod_dav
- mod_dav_fs
- mod_auth_pgsql
- libphp5
by the means of your operating system vendor. For Debian GNU/Linux 4.0
Etch this means [1]_::
sudo aptitude install apache2-mpm-prefork libapache2-mod-php5 libapache2-mod-auth-pgsql
sudo a2enmod php5
sudo a2enmod auth_pgsql
sudo a2enmod dav
sudo a2enmod dav_fs
.. [1] if you don't want to use ``sudo`` you may also switch to root.
2. Configure a VirtualHost to use WebDAV and PostgreSQL
authentication, this VirtualHost configuration could look like::
<VirtualHost *:80>
ServerName davhost.yourdomain.net
DavLockDb /var/run/apache2/davlock/davhost.yourdomain.net
DocumentRoot /var/www
<Directory /var/www/dav>
Options Indexes
Order allow,deny
allow from all
Dav on
# Authentication/Authorization
AuthType Basic
AuthName "WebDAVAdmin example"
AuthBasicAuthoritative Off
AuthUserFile /etc/apache2/auth/davhost.yourdomain.net.passwd
Auth_PG_host localhost
Auth_PG_port 5432
Auth_PG_user myuser
Auth_PG_pwd secret
Auth_PG_database mydb
Auth_PG_pwd_table dav_password
Auth_PG_uid_field username
Auth_PG_pwd_field password
Auth_PG_grp_table dav_group
Auth_PG_grp_user_field username
Auth_PG_grp_group_field groupname
Auth_PG_hash_type MD5
#Auth_PG_log_table dav_log
#Auth_PG_log_uname_field username
#Auth_PG_log_date_field reqdate
#Auth_PG_log_uri_field uri
#Auth_PG_log_addrs_field ipaddr
Auth_PG_authoritative on
require group davroot
</Directory>
ErrorLog /var/log/apache2/davhost.yourdomain.net_error.log
CustomLog /var/log/apache2/davhost.yourdomain.net_access.log combined
</VirtualHost>
The directory specified for ``DavLockDb`` must be writable for the
user your apache processes run as. The ``AuthUserFile`` is
specified as a fallback if your PostgreSQL database is not
available.
Install required php modules and classes
========================================
WebDAVAdmin needs Smarty and a PostgreSQL PDO driver for PHP5. To
install these requirements perform the following step::
sudo aptitude install smarty php5-pgsql
on operating systems other then Debian GNU/Linux consult your system
documentation.
Copy WebDAVAdmin files
======================
2. create a new document root directory or a subdirectory inside an
existing one
3. create a subdirectory which you'll later use for WebDAVAdmin::
mkdir /var/www/dav
4. copy the admin subdirectory of the unpacked webdavadmin distribution
file to the directory just created::
cp -R webdavadmin-0.1/admin /var/www/dav/
5. set the filesystem permissions of the dav directory to allow the
user apache is running as to write to the directory
Configure WebDAVAdmin
=====================
The WebDAVAdmin distribution contains a directory ``config`` with
configuration templates that you need to customize for your
environment.
1. ``dbsettings.inc.php``
This file contains the settings for your database connection. The
file should be placed outside the document root for security
reasons. A customized version of this file may look like::
<?php
/** Data source name. */
$dsn = "pgsql:host=localhost port=5432 dbname=mydb";
/** Database user. */
$dbuser = "myuser";
/** Database password. */
$dbpass = "secret";
?>
2. ``config.inc.php``
This file contains the absolute path to your WebDAVAdmin
installation and to your ``dbsettings.inc.php``. A customized
version of this file could be::
<?php
/** DAV area root directory. */
define(DAV_ROOT, '/var/www/dav');
/** Include the database settings. */
include_once('/etc/webdavadmin/dbsettings.inc.php');
?>
After adapting the contents to your environment put this file into
your WebDAVAdmin directory. For example::
cp config.inc.php /var/www/dav/admin/
Be sure to make the subdirectory templates_c of your WebDAVAdmin
directory writable for your apache user [2]_.
.. [2] you could use chown, chmod and/or ACLs to perform this task
Now you should be able to use your installation of WebDAVAdmin by
opening the URL http://davhost.yourdomain.net/dav/admin/ (if you just
followed this instructions).

18
Makefile

@ -0,0 +1,18 @@
.PHONY: apidoc
VERSION=0.1
PROJECT=webdavadmin
SRCFILES=admin/common.inc.php,admin/directories.php,admin/getgroups.php,admin/index.php,admin/users.php
apidoc:
if [ -d apidoc ]; then rm -r apidoc; fi
phpdoc -f $(SRCFILES) -t apidoc -s
clean:
find -name '*~' -type f -exec rm {} \;
distclean: clean
if [ -d apidoc ]; then rm -r apidoc; fi
dist: distclean
cd .. ; tar cjf $(PROJECT)-$(VERSION).tar.bz2 $(PROJECT)-$(VERSION)

71
README

@ -0,0 +1,71 @@
=============
WebDAVAdmin
=============
:Author: Jan Dittberner
:Contact: jan@dittberner.info
:Version: 0.1
:Revision: $Revision$
:Date: $Date$
:Copyright: Copyright (C) 2007 Jan Dittberner
.. contents::
Goals
=====
The goal of this software is to provide an easy to use administration
interface for a WebDAV repository using mod-auth-pgsql as its
authentication and authorization source.
Requirements
============
To use this software you need an Apache webserver configured with the
dav module and mod-auth-pgsql, PHP 5 with PostgreSQL PDO driver, the
Smarty_ template engine and a PostgreSQL database. The software has
been developed using the versions contained in Debian GNU/Linux 4.0
Etch.
- Apache 2.2.3
- PostgreSQL 8.1.8
- mod-auth-pgsql 2.0.3
- PHP 5.2.0
- Smarty 2.6.14
.. _Smarty: http://smarty.php.net/
Installation
============
Please see INSTALL_ for installation instructions.
.. _INSTALL: INSTALL
Used third party code
=====================
WebDAVAdmin uses the JQuery Javascript library for DOM manipulations
and AJAX calls.
.. _JQuery: http://www.jquery.com/
Version 1.1.3 of JQuery has been used for developing this software and
is bundled in the ``scripts`` directory as ``jquery.js``.
License
=======
This program 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.
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
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.

6
TODO

@ -0,0 +1,6 @@
TODO
====
- create an installer
- setup admin user during installation
- better integration into existing databases

2
admin/.htaccess

@ -0,0 +1,2 @@
DirectoryIndex index.php
require group davadmin

75
admin/common.inc.php

@ -0,0 +1,75 @@
<?php
/**
* Common code for WebDAVAdmin.
*
* @author Jan Dittberner <jan@dittberner.info>
* @version $Id$
* @license GPL
* @package WebDAVAdmin
*
* Copyright (c) 2007 Jan Dittberner
*
* This file is part of WebDAVAdmin.
*
* This program 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.
*
* 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
/** Include configuration information. */
require_once('config.inc.php');
/** DAV administrator group name. */
define(ADMIN_GROUP, 'davadmin');
/** DAV administration application subdirectory. */
define(ADMIN_DIR, 'admin');
/** Include the Smarty template engine. */
require_once("smarty/libs/Smarty.class.php");
/** Global Smarty template engine instance. */
$smarty = new Smarty();
/** Handle invalid requests to the application. */
function invalidCall() {
header("Content-Type: text/plain; charset=UTF-8");
print("Ungültiger Aufruf!");
die();
}
/** Handle errors with an XML message. */
function errorAsXml($errormsg) {
header("Content-Type: text/xml; charset=UTF-8");
$GLOBALS['smarty']->assign("errormsg", $errormsg);
$GLOBALS['smarty']->display("error.xml");
die();
}
/** Handle errors with an HTML error page. */
function errorAsHtml($errormsg) {
header("Content-Type: text/html; charset=UTF-8");
$GLOBALS['smarty']->assign("errormsg", $errormsg);
$GLOBALS['smarty']->display("error.html");
die();
}
/**
* Handle a PDO statement error.
*
* @param PDOStatement $sth statement handle
*/
function statementErrorAsXml(&$sth) {
errorAsXml(utf8_encode(implode("\n", $sth->errorInfo())));
}
?>

331
admin/directories.php

@ -0,0 +1,331 @@
<?php
/**
* Directory administration code.
*
* @author Jan Dittberner <jan@dittberner.info>
* @version $Id$
* @license GPL
* @package WebDAVAdmin
*
* Copyright (c) 2007 Jan Dittberner
*
* This file is part of WebDAVAdmin.
*
* This program 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.
*
* 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
/** Include common code. */
include_once('common.inc.php');
/** Format string for group lines in .htaccess files. */
define('GROUPLINEFORMAT', "require group %s\n");
/** Regular expression for finding group lines in .htaccess files. */
define('GROUPLINERE', '/^require\s+group\s+(.*)$/i');
/** Regular expression matching legal directory names. */
define('DIRNAMERE', '/^[a-zA-Z0-9 -_.]+$/');
/**
* List of mandatory groups. These groups are assigned to every new or
* changed directory.
*/
$mandatorygroups = array(ADMIN_GROUP);
/**
* Gets the group names allowed to access the given directory from its
* .htaccess file. Mandatory groups are excluded.
*
* @param string $dirname a fully qualified directory name
* @return an array of group names
*/
function getDirGroupsFromHtaccess($dirname) {
$htaccessname = $dirname . DIRECTORY_SEPARATOR . ".htaccess";
$groups = array();
if (false !== ($fh = fopen($htaccessname, "r"))) {
while (!feof($fh)) {
$buffer = fgets($fh);
if (preg_match_all(GROUPLINERE, trim($buffer), $matches)) {
$grouparr = explode(" ", $matches[1][0]);
foreach ($grouparr as $group) {
$group = trim($group);
if (!in_array($group, $GLOBALS['mandatorygroups'])) {
array_push($groups, $group);
}
}
}
}
}
fclose($fh);
return $groups;
}
/**
* Gets the names of groups for a directory.
*
* @param string $dirname directory name relative to {@link DAV_ROOT}
* @return an array of group names
* @see #getDirGroupsFromHtaccess(string)
*/
function getDirGroups($dirname) {
return getDirGroupsFromHtaccess(DAV_ROOT . DIRECTORY_SEPARATOR . $dirname);
}
/**
* Counts the visible files and their accumulated size in a directory
* tree.
*
* @param string $dirname a fully qualified directory name
* @param array $retval a two component array index 0 is the file
* count, index 2 is the accumulated size of files
* @return array updated with the data from subdirectories and files
* of $dirname
*/
function countFilesRecursive($dirname, $retval = array(0, 0)) {
$dh = opendir($dirname);
while (false !== ($entry = readdir($dh))) {
$fullname = $dirname . DIRECTORY_SEPARATOR . $entry;
if (strpos($entry, '.') !== 0) {
if (is_dir($fullname)) {
$retval = countFilesRecursive($fullname, $retval);
} else if (is_file($fullname)) {
$retval = array($retval[0] + 1, $retval[1] + filesize($fullname));
}
}
}
closedir($dh);
return $retval;
}
/**
* Gets the data of a directory.
*
* @param string $dirname a fully qualified directory name
* @return array of containing directory data
*/
function getDirectoryData($dirname) {
$dir = array();
$dir['name'] = basename($dirname);
$dir['groups'] = getDirGroupsFromHtaccess($dirname);
list($dir['filecount'], $dir['filesize']) = countFilesRecursive($dirname);
$dir['maydelete'] = ($dir['filecount'] == 0) ? 1 : 0;
$dir['filesize'] = sprintf("%d kBytes", $dir['filesize'] / 1024);
return $dir;
}
/**
* Gets XML encoded data of a directory.
*
* @param string $dirname dirname relative to {@link DAV_ROOT}
* @return XML string
*/
function getDirectoryDataAsXml($dirname) {
if (is_dir(DAV_ROOT . $dirname)) {
$dirdata = getDirectoryData(DAV_ROOT . $dirname);
header("Content-Type: text/xml; charset=UTF-8");
return sprintf('<?xml version="1.0" encoding="utf8"?><directory><dirname>%s</dirname><groups>%s</groups><filecount>%d</filecount><filesize>%s</filesize><maydelete>%d</maydelete></directory>', $dirdata['name'], implode(", ", $dirdata['groups']), $dirdata['filecount'], $dirdata['filesize'], $dirdata['maydelete']);
} else {
errorAsXml(sprintf(_("Invalid directory name %s!"), $dirname));
}
}
/**
* Gets XML encoded data of a deleted directory.
*
* @param string $dirname directory name relative to {@link DAV_ROOT}
* @return XML string
*/
function getDeletedDirectoryData($dirname) {
header("Content-Type: text/xml; charset=UTF-8");
return sprintf('<?xml version="1.0" encoding="utf8"?><directory><dirname>%s</dirname></directory>', $dirname);
}
/**
* Gets the list of directory data for all valid directories below
* {@link DAV_ROOT}.
*
* @return array of directory data arrays
* @see #getDirectoryData(string)
*/
function getDirectories() {
$dirs = array();
if (false !== ($entries = scandir(DAV_ROOT))) {
foreach ($entries as $entry) {
if (is_dir(DAV_ROOT . $entry)) {
if (strpos($entry, '.') !== 0) {
if ($entry != ADMIN_DIR) {
array_push($dirs, getDirectoryData(DAV_ROOT . $entry));
}
}
}
}
}
return $dirs;
}
/**
* Sets the groups of a directory in its .htaccess file. Mandatory
* groups are added automatically.
*
* @param string $dirname directory name relative to {@link DAV_ROOT}
* @param array &$groups reference to a list of group names
*/
function setGroups($dirname, &$groups) {
$fullname = DAV_ROOT . $dirname;
foreach ($groups as $key => $value) {
$groups[$key] = trim($value);
}
foreach ($GLOBALS['mandatorygroups'] as $mgroup) {
if (!in_array($mgroup, $groups)) {
array_push($groups, $mgroup);
}
}
$found = false;
$lines = array();
$found = false;
if (is_dir($fullname)) {
$htaccess = $fullname . DIRECTORY_SEPARATOR . ".htaccess";
if (file_exists($htaccess) && (false !== ($fh = fopen($htaccess, "r")))) {
while (!feof($fh)) {
$buffer = fgets($fh);
if (preg_match(GROUPLINERE, $buffer)) {
array_push($lines, sprintf(GROUPLINEFORMAT, join(" ", $groups)));
$found = true;
} else {
array_push($lines, $buffer);
}
}
fclose($fh);
}
if (!$found) {
array_push($lines, sprintf(GROUPLINEFORMAT, join(" ", $groups)));
}
if (false !== ($fh = fopen($htaccess, "w"))) {
foreach ($lines as $line) {
fwrite($fh, $line);
}
fclose($fh);
}
}
}
/**
* Updates a directory to be accessible by the given list of
* groups. The directory is created if it doesn't exist.
*
* @param string $dirname directory name relative to {@link DAV_ROOT}
* @param array $groups a list of group names
*/
function updateDirectory($dirname, $groups) {
if (preg_match(DIRNAMERE, $dirname, $matches)) {
if ($dirname != ADMIN_DIR) {
$fullname = DAV_ROOT . $dirname;
if (file_exists($fullname)) {
if (!is_dir($fullname)) {
errorAsXml(sprintf(_("There already is a directory entry named %s, but it's not a directory!"), $dirname));
}
} else {
mkdir($fullname);
}
setGroups($dirname, $groups);
}
return;
}
errorAsXml(sprintf(_("Invalid directory name %s!"), $dirname));
}
/**
* Recursively deletes the given directory.
*
* @param string $fullname fully qualified directory name
*/
function delrecursive($fullname) {
$dh = opendir($fullname);
while (false !== ($entry = readdir($dh))) {
if ($entry != "." && $entry != "..") {
$entryname = $fullname . DIRECTORY_SEPARATOR . $entry;
if (is_dir($entryname)) {
delrecursive($entryname);
} else {
unlink($entryname);
}
}
}
closedir($dh);
rmdir($fullname);
return true;
}
/**
* Deletes the given directory if it has a valid name and is not the
* administration interface directory.
*
* @param string $dirname directory name relative to {@link DAV_ROOT}
*/
function deleteDirectory($dirname) {
if (preg_match(DIRNAMERE, $dirname, $matches)) {
if ($dirname != ADMIN_DIR) {
$fullname = DAV_ROOT . $dirname;
if (is_dir($fullname)) {
return delrecursive($fullname);
}
}
errorAsXml(_("Tried to delete the administration interface directory!"));
}
errorAsXml(sprintf(_("Invalid directory name %s!"), $dirname));
}
if ($_GET) {
// handle get requests with data
if ($_GET['method']) {
switch ($_GET['method']) {
case 'getdirectorydata':
print getDirectoryDataAsXml($_GET['dirname']);
break;
default:
errorAsXml(sprintf(_("Unexpected values %s!"), serialize($_GET)));
}
} else {
invalidCall();
}
} elseif ($_POST) {
// handle post requests with data
if ($_POST['method']) {
switch ($_POST['method']) {
case 'submitdirectory':
updateDirectory($_POST['dirname'], explode(",", $_POST['groups']));
print getDirectoryDataAsXml($_POST['dirname']);
break;
case 'deletedirectory':
if (deleteDirectory($_POST['dirname'])) {
print getDeletedDirectoryData($_POST['dirname']);
} else {
errorAsXml(_("Delete failed!"));
}
break;
default:
errorAsXml(sprintf(_("Unexpected values %s!"), serialize($_POST)));
}
} else {
invalidCall();
}
} else {
// handle requests without data
header("Content-Type: text/html; charset=UTF-8");
$smarty->assign("directories", getDirectories());
$smarty->display("directories.html");
}
?>

47
admin/dynaform.css

@ -0,0 +1,47 @@
#usereditor {
display:none;
z-index:10;
position:absolute;
top:10px;
margin-left:auto;
margin-right:auto;
}
.dynaform {
border:2px solid black;
background-color:#fffbed;
color:#2b2b26;
width:380px;
padding:2px;
}
.dynaform .formelement {
padding:2px;
}
.dynaform .formelement label {
display:block;
width:171px;
float:left;
}
.dynaform .formactions {
margin-top:5px;
border-top:1px solid #ffd436;
padding:5px;
text-align:right;
}
.dynaform input, .dynaform select
{
padding:0;
width:200px;
background-color:#fffbed;
color:#2b2b26;
border:1px solid #ffd436;
clear:both;
}
.dynaform input[type=submit] {
width:auto;
}

88
admin/format.css

@ -0,0 +1,88 @@
html
{
font-family: Verdana,Helvetica,Arial,sans-serif;
font-size: 10pt;
background-color:white;
color:black;
}
#usertable, #dirtable {
border-collapse:collapse;
}
#usertable #hcol-username {
width:120px;
}
#usertable #hcol-name {
width:264px;
}
#dirtable #hcol-directory {
width:120px;
}
#dirtable #hcol-groups {
width:200px;
}
#dirtable #hcol-files {
width:150px;
}
#usertable th, #usertable td, #dirtable th, #dirtable td {
border:1px solid black;
text-align:left;
padding:2px;
}
dt {
display:block;
float:left;
width:20px;
clear:both;
}
dd {
display:block;
margin:0;
}
/*
* Autocomplete styles
*/
/* Suggestion list */
#autocomplete {
position: absolute;
border: 1px solid;
overflow: hidden;
z-index: 100;
}
#autocomplete ul {
margin: 0;
padding: 0;
list-style: none;
}
#autocomplete li {
background: #fff;
color: #000;
white-space: pre;
cursor: default;
}
#autocomplete li.selected {
background: #0072b9;
color: #fff;
}
/* Animated throbber */
input.form-autocomplete {
background-image: url(images/throbber.gif);
background-repeat: no-repeat;
background-position: 100% 2px;
}
input.throbbing {
background-position: 100% -18px;
}
a img.actionicon {
border:0;
}

69
admin/getgroups.php

@ -0,0 +1,69 @@
<?php
/**
* AJAX autocompletion code for group names.
*
* @author Jan Dittberner <jan@dittberner.info>
* @version $Id$
* @license GPL
* @package WebDAVAdmin
*
* Copyright (c) 2007 Jan Dittberner
*
* This file is part of WebDAVAdmin.
*
* This program 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.
*
* 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
/** Include configuration information. */
require_once('config.inc.php');
// output is plain text (JSON or an error message)
header("Content-Type: text/plain; charset=UTF-8");
/**
* Gets group names for autocompletion.
*
* @param string $part Comma separated list of groups and beginning of
* a new group entry
* @return list of Comma separated lists of groups
*/
function getGroups($part) {
$regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
preg_match_all($regexp, $part, $matches);
$array = $matches[1];
$last_string = trim(array_pop($array));
if ($last_string != '') {
try {
$dbh = new PDO($GLOBALS['dsn'], $GLOBALS['dbuser'], $GLOBALS['dbpass']);
$sth = $dbh->prepare("SELECT DISTINCT groupname FROM dav_group WHERE LOWER(groupname) LIKE LOWER(?) ORDER BY groupname");
$sth->execute(array("%" . $last_string . "%"));
$prefix = count($array) ? implode(",", $array) . ", " : '';
$retval = array();
foreach ($sth->fetchAll(PDO::FETCH_ASSOC) as $row) {
$retval[$prefix . $row['groupname']] = $row['groupname'];
}
$dbh = null;
} catch (PDOException $e) {
return $e->getMessage();
}
}
return json_encode($retval);
}
// split group list part from requested URL.
$parts = substr($_SERVER['PHP_SELF'], strrpos($_SERVER['PHP_SELF'], "/") + 1);
print getGroups($parts);
?>

BIN
admin/images/assign.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 876 B

BIN
admin/images/delete.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 841 B

BIN
admin/images/directory.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 B

BIN
admin/images/edit.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 B

BIN
admin/images/groups.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 847 B

BIN
admin/images/newdirectory.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 B

BIN
admin/images/newuser.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 751 B

BIN
admin/images/throbber.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
admin/images/x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 B

48
admin/index.php

@ -0,0 +1,48 @@
<?php
/**
* Start page code.
*
* @author Jan Dittberner <jan@dittberner.info>
* @version $Id$
* @license GPL
* @package WebDAVAdmin
*
* Copyright (c) 2007 Jan Dittberner
*
* This file is part of WebDAVAdmin.
*
* This program 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.
*
* 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
/** Include common code. */
include_once('common.inc.php');
header("Content-Type: text/html; charset=UTF-8");
try {
$dbh = new PDO($dsn, $dbuser, $dbpass);
$query = $dbh->prepare("SELECT firstname, lastname FROM dav_password WHERE username=:username");
$currentuser = $_SERVER['PHP_AUTH_USER'];
$query->execute(array(":username" => $currentuser));
$row = $query->fetch(PDO::FETCH_ASSOC);
$smarty->assign("firstname", $row['firstname']);
$smarty->assign("lastname", $row['lastname']);
$smarty->display("start.html");
$dbh = null;
} catch (PDOException $e) {
$smarty->setErrorMsg($e->getMessage());
$smarty->display("error.html");
}
?>

305
admin/scripts/autocomplete.js

@ -0,0 +1,305 @@
// $Id$
/*
* Autocompletion for input fields, ideas from Drupal autocompletion
*/
/**
* Attaches the autocomplete behaviour to all required fields
*/
DAV.autocompleteAutoAttach = function () {
var acdb = [];
$('input.autocomplete').each(function () {
var uri = this.value;
if (!acdb[uri]) {
acdb[uri] = new DAV.ACDB(uri);
}
var input = $('#' + this.id.substr(0, this.id.length - 13))
.attr('autocomplete', 'OFF')[0];
$(input.form).submit(DAV.autocompleteSubmit);
new DAV.jsAC(input, acdb[uri]);
});
}
/**
* Prevents the form from submitting if the suggestions popup is open
* and closes the suggestions popup when doing so.
*/
DAV.autocompleteSubmit = function () {
return $('#autocomplete').each(function () {
this.owner.hidePopup();
}).size() == 0;
}
/**
* An AutoComplete object
*/
DAV.jsAC = function (input, db) {
var ac = this;
this.input = input;
this.db = db;
$(this.input)
.keydown(function (event) { return ac.onkeydown(this, event); })
.keyup(function (event) { ac.onkeyup(this, event) })
.blur(function () { ac.hidePopup(); ac.db.cancel(); });
};
/**
* Handler for the "keydown" event
*/
DAV.jsAC.prototype.onkeydown = function (input, e) {
if (!e) {
e = window.event;
}
switch (e.keyCode) {
case 40: // down arrow
this.selectDown();
return false;
case 38: // up arrow
this.selectUp();
return false;
default: // all other keys
return true;
}
}
/**
* Handler for the "keyup" event
*/
DAV.jsAC.prototype.onkeyup = function (input, e) {
if (!e) {
e = window.event;
}
switch (e.keyCode) {
case 16: // shift
case 17: // ctrl
case 18: // alt
case 20: // caps lock
case 33: // page up
case 34: // page down
case 35: // end
case 36: // home
case 37: // left arrow
case 38: // up arrow
case 39: // right arrow
case 40: // down arrow
return true;
case 9: // tab
case 13: // enter
case 27: // esc
this.hidePopup(e.keyCode);
return true;
default: // all other keys
if (input.value.length > 0)
this.populatePopup();
else
this.hidePopup(e.keyCode);
return true;
}
}
/**
* Puts the currently highlighted suggestion into the autocomplete field
*/
DAV.jsAC.prototype.select = function (node) {
this.input.value = node.autocompleteValue;
}
/**
* Highlights the next suggestion
*/
DAV.jsAC.prototype.selectDown = function () {
if (this.selected && this.selected.nextSibling) {
this.highlight(this.selected.nextSibling);
}
else {
var lis = $('li', this.popup);
if (lis.size() > 0) {
this.highlight(lis.get(0));
}
}
}
/**
* Highlights the previous suggestion
*/
DAV.jsAC.prototype.selectUp = function () {
if (this.selected && this.selected.previousSibling) {
this.highlight(this.selected.previousSibling);
}
}
/**
* Highlights a suggestion
*/
DAV.jsAC.prototype.highlight = function (node) {
if (this.selected) {
$(this.selected).removeClass('selected');
}
$(node).addClass('selected');
this.selected = node;
}
/**
* Unhighlights a suggestion
*/
DAV.jsAC.prototype.unhighlight = function (node) {
$(node).removeClass('selected');
this.selected = false;
}
/**
* Hides the autocomplete suggestions
*/
DAV.jsAC.prototype.hidePopup = function (keycode) {
// Select item if the right key or mousebutton was pressed
if (this.selected && ((keycode && keycode != 46 && keycode != 8 && keycode != 27) || !keycode)) {
this.input.value = this.selected.autocompleteValue;
}
// Hide popup
var popup = this.popup;
if (popup) {
this.popup = null;
$(popup).fadeOut('fast', function() { $(popup).remove(); });
}
this.selected = false;
}
/**
* Positions the suggestions popup and starts a search
*/
DAV.jsAC.prototype.populatePopup = function () {
// Show popup
if (this.popup) {
$(this.popup).remove();
}
this.selected = false;
this.popup = document.createElement('div');
this.popup.id = 'autocomplete';
this.popup.owner = this;
$(this.popup).css({
marginTop: this.input.offsetHeight +'px',
width: (this.input.offsetWidth - 4) +'px',
display: 'none'
});
$(this.input).before(this.popup);
// Do search
this.db.owner = this;
this.db.search(this.input.value);
}
/**
* Fills the suggestion popup with any matches received
*/
DAV.jsAC.prototype.found = function (matches) {
// If no value in the textfield, do not show the popup.
if (!this.input.value.length) {
return false;
}
// Prepare matches
var ul = document.createElement('ul');
var ac = this;
for (key in matches) {
var li = document.createElement('li');
$(li)
.html('<div>'+ matches[key] +'</div>')
.mousedown(function () { ac.select(this); })
.mouseover(function () { ac.highlight(this); })
.mouseout(function () { ac.unhighlight(this); });
li.autocompleteValue = key;
$(ul).append(li);
}
// Show popup with matches, if any
if (this.popup) {
if (ul.childNodes.length > 0) {
$(this.popup).empty().append(ul).show();
}
else {
$(this.popup).css({visibility: 'hidden'});
this.hidePopup();
}
}
}
DAV.jsAC.prototype.setStatus = function (status) {
switch (status) {
case 'begin':
$(this.input).addClass('throbbing');
break;
case 'cancel':
case 'error':
case 'found':
$(this.input).removeClass('throbbing');
break;
}
}
/**
* An AutoComplete DataBase object
*/
DAV.ACDB = function (uri) {
this.uri = uri;
this.delay = 300;
this.cache = {};
}
/**
* Performs a cached and delayed search
*/
DAV.ACDB.prototype.search = function (searchString) {
var db = this;
this.searchString = searchString;
// See if this key has been searched for before
if (this.cache[searchString]) {
return this.owner.found(this.cache[searchString]);
}
// Initiate delayed search
if (this.timer) {
clearTimeout(this.timer);
}
this.timer = setTimeout(function() {
db.owner.setStatus('begin');
// Ajax GET request for autocompletion
$.ajax({
type: "GET",
url: db.uri +'/'+ DAV.encodeURIComponent(searchString),
success: function (data) {
// Parse back result
var matches = DAV.parseJson(data);
if (typeof matches['status'] == 'undefined' || matches['status'] != 0) {
db.cache[searchString] = matches;
// Verify if these are still the matches the user wants to see
if (db.searchString == searchString) {
db.owner.found(matches);
}
db.owner.setStatus('found');
}
},
error: function (xmlhttp) {
alert('An HTTP error '+ xmlhttp.status +' occured.\n'+ db.uri);
}
});
}, this.delay);
}
/**
* Cancels the current autocomplete request
*/
DAV.ACDB.prototype.cancel = function() {
if (this.owner) this.owner.setStatus('cancel');
if (this.timer) clearTimeout(this.timer);
this.searchString = '';
}
// Assign autocomplete
$(document).ready(DAV.autocompleteAutoAttach);

148
admin/scripts/directories.js

@ -0,0 +1,148 @@
function handleerror(xmldata) {
if ($('error', xmldata).size()) {
var msg = 'Es ist ein Fehler aufgetreten:\n';
$('error', xmldata).find('errormsg').each(function(i) {
msg += $(this).text();
});
alert(msg);
return true;
}
return false;
$('#content').show();
}
function updateDirectories(xmldata) {
var dirname = $('dirname', xmldata).text();
var groups = $('groups', xmldata).text();
var filecount = $('filecount', xmldata).text();
var filesize = $('filesize', xmldata).text();
var maydelete = $('maydelete', xmldata).text();
var htmltext = '<td>' + dirname + '</td><td>' + groups + '</td><td>' + filecount + ', ' + filesize + '</td><td><a id="edit' + dirname + '" class="editlink" href="#" title="Die Gruppenzuordnungen dieses Verzeichnisses bearbeiten"><img class="actionicon" src="images/groups.png" width="16" height="16" alt="Gruppen"/></a>';
if (maydelete == '1') {
htmltext = htmltext + '<a id="delete' + dirname + '" class="deletelink" href="#" title="Dieses Verzeichnis löschen"><img class="actionicon" src="images/delete.png" width="16" height="16" alt="löschen" /></a>';
}
htmltext = htmltext + '</td>';
$('#dirtable').find('tr#dir' + dirname).empty().append(htmltext);
if (!($('#dirtable').find('tr#dir' + dirname).size())) {
var rows = $('#dirtable').find('tr');
var inserted = false;
for (var i = 0; !inserted && i < rows.length; i++) {
if ($(rows[i]).find('td:first').text() > dirname) {
$(rows[i]).before(
'<tr id="dir' + dirname + '">' + htmltext + '</tr>');
inserted = true;
}
}
if (!inserted) {
$(rows[rows.length-1]).before(
'<tr id="dir' + dirname + '">' + htmltext + '</tr>');
}
}
$('#dirtable').find('tr#dir' + dirname).find('a.editlink').click(function() {
editdirectory(this.id.substr(4));
});
$('#dirtable').find('tr#dir' + dirname).find('a.deletelink').click(function() {
removedirectory(this.id.substr(6));
});
}
function getDirectoryForm(title, dirname, groups) {
return '<form action="#" id="dirform"><fieldset class="dynaform"><legend><img id="closer" src="images/x.png" width="16" height="16" alt="Schließen" /> ' + title + '</legend><div class="formelement"><label for="dirname">Verzeichnisname:</label><input type="text" name="dirname" id="input-dirname" value="' + dirname + '" /></div><div class="formelement"><label for="groups">Gruppen:</label><input type="text" name="groups" class="form-autocomplete" id="input-groups" value="' + groups + '" /><input type="hidden" class="autocomplete" id="input-groups-autocomplete" value="getgroups.php" /></div><div class="formactions"><input type="submit" name="submit" value="Absenden" /></div></div></fieldset></form>';
}
function displaydirectoryeditor(title, dirname, groups) {
$('#content').hide();
$('#direditor').hide().empty().append(getDirectoryForm(title, dirname,
groups)).show();
DAV.autocompleteAutoAttach();
$('#dirform').find('#input-dirname').focus();
$('#closer').click(function() {
$('#direditor').hide().empty();
$('#content').show();
});
$('#dirform').submit(function() {
if (!this.dirname.value.match(/^[a-zA-Z0-9 -_.]+$/)) {
alert("Ungültiger Verzeichnisname.");
this.dirname.focus();
return false;
}
$.post(
"/dav/admin/directories.php",
{method : 'submitdirectory',
dirname : this.dirname.value,
groups : this.groups.value},
function(retval) {
$('div#direditor').hide().empty();
if (!handleerror(retval)) {
updateDirectories(retval);
$('#content').show();
}
});
return false;
});
}
function editdirectory(dirname) {
$.get(
"directories.php",
{dirname : dirname,
method : 'getdirectorydata'},
function(retval) {
if (!handleerror(retval)) {
var dirname, groups;
dirname = $("dirname:first", retval).text();
groups = $("groups:first", retval).text();
displaydirectoryeditor("Verzeichnisdaten bearbeiten",
dirname, groups);
}
});
}
function newdirectory() {
displaydirectoryeditor('Neues Verzeichnis anlegen', '', '');
}
function deletedirectorydialog(dirname) {
$("#direditor").hide().empty();
var msg = 'Soll das Verzeichnis ' + dirname +
' wirklich gelöscht werden?';
if (confirm(msg) == true) {
$.post(
"directories.php",
{method : 'deletedirectory',
dirname : dirname},
function(retval) {
if (!handleerror(retval)) {
var dirname = $('dirname:first', retval).text();
$('#dirtable').find('tr#dir' + dirname).remove();
}
});
}
}
function removedirectory(dirname) {
$.get(
"directories.php",
{dirname : dirname,
method : 'getdirectorydata'},
function(retval) {
if (!handleerror(retval)) {
var username, lastname, firstname;
var dirname, groups;
dirname = $("dirname:first", retval).text();
deletedirectorydialog(dirname);
}
});
}
$(function() {
$("a.editlink").each(function(i) {
$(this).click(function() { editdirectory(this.id.substr(4)); });
});
$("a.newlink").each(function(i) {
$(this).click(function() { newdirectory(); });
});
$("a.deletelink").each(function(i) {
$(this).click(function() { removedirectory(this.id.substr(6)); });
});
});

29
admin/scripts/helper.js

@ -0,0 +1,29 @@
// $Id$
/**
* Helper code. Ideas from Drupal.
*/
var DAV = DAV || {};
/**
* Parse a JSON response.
*
* The result is either the JSON object, or an object with 'status' 0 and
* 'data' an error message.
*/
DAV.parseJson = function (data) {
if ((data.substring(0, 1) != '{') && (data.substring(0, 1) != '[')) {
return { status: 0, data: data.length ? data : 'Unspecified error' };
}
return eval('(' + data + ');');
};
/**
* Wrapper to address the mod_rewrite url encoding bug.
*/
DAV.encodeURIComponent = function (item, uri) {
uri = uri || location.href;
item = encodeURIComponent(item).replace('%2F', '/');
return uri.indexOf('?q=') ? item : item.replace('%26', '%2526').replace('%23', '%2523');
};

1
admin/scripts/jquery.js

File diff suppressed because one or more lines are too long

189
admin/scripts/users.js

@ -0,0 +1,189 @@
function handleerror(xmldata) {
if ($('error', xmldata).size()) {
var msg = 'Es ist ein Fehler aufgetreten:\n';
$('error', xmldata).find('errormsg').each(function(i) {
msg += $(this).text();
});
alert(msg);
$('#content').show();
return true;
}
return false;
}
function updateusers(xmldata) {
var uid = $(xmldata).find('uid').text();
var username = $(xmldata).find('username').text();
var firstname = $(xmldata).find('firstname').text();
var lastname = $(xmldata).find('lastname').text();
var loggedin = $(xmldata).find('loggedin').text();
var htmltext = '<td>' + username + '</td><td>' + lastname + ', ' + firstname + '</td><td><a id="edit' + uid + '" class="editlink" href="#" title="Die Daten dieses Nutzers bearbeiten"><img class="actionicon" src="images/edit.png" width="16" height="16" alt="bearbeiten"/></a>';
if (loggedin == '0') {
htmltext = htmltext + '<a id="delete' + uid + '" class="deletelink" href="#" title="Die Daten dieses Nutzers löschen"><img class="actionicon" src="images/delete.png" width="16" height="16" alt="löschen" /></a>';
}
htmltext = htmltext + '</td>';
$('#usertable').find('tr#uid' + uid).empty().append(htmltext);
if (!($('#usertable').find('tr#uid' + uid).size())) {
var rows = $('#usertable').find('tr');
var inserted = false;
for (var i = 0; !inserted && i < rows.length; i++) {
if ($(rows[i]).find('td:first').text() > username) {
$(rows[i]).before(
'<tr id="uid' + uid + '">' + htmltext + '</tr>');
inserted = true;
}
}
if (!inserted) {
$(rows[rows.length-1]).before(
'<tr id="uid' + uid + '">' + htmltext + '</tr>');
}
}
$('#usertable').find('tr#uid' + uid).find('a.editlink').click(function() {
edituser(this.id.substr(4));
});
$('#usertable').find('tr#uid' + uid).find('a.deletelink').click(function() {
deleteuser(this.id.substr(6));
});
$('#content').show();
}
function getEditUserForm(title, username, firstname, lastname, groups, userid) {
var retval;
retval = '<form action="#" id="userform"><fieldset class="dynaform"><legend><img id="closer" src="images/x.png" width="16" height="16" alt="Schließen" /> ' + title + '</legend><div class="formelement"><label for="input-username">Nutzername:</label><input id="input-username" type="text" name="username" value="' + username + '" ';
if (userid != null) {
retval = retval + ' readonly="readonly"';
}
retval = retval + '/></div><div class="formelement"><label for="input-firstname">Vorname:</label><input id="input-firstname" type="text" name="firstname" value="' + firstname + '" /></div><div class="formelement"><label for="input-lastname">Nachname:</label><input id="input-lastname" type="text" name="lastname" value="' + lastname + '" /></div><div class="formelement"><label for="pwd1">Passwort:</label><input type="password" name="pwd1" /></div><div class="formelement"><label for="pwd2">Wiederholung:</label><input type="password" name="pwd2" /></div><div class="formelement"><label for="groups">Gruppen:</label><input type="text" name="groups" class="form-autocomplete" id="input-groups" value="' + groups + '" /><input type="hidden" class="autocomplete" id="input-groups-autocomplete" value="getgroups.php" /></div><div class="formactions"><input type="submit" name="submit" value="Absenden" /></div></fieldset></form>';
return retval;
}
function displayusereditor(title, userid, username, firstname, lastname, groups) {
$('#content').hide();
$('#usereditor').hide().empty().append(getEditUserForm(title, username, firstname, lastname, groups, userid)).show();
DAV.autocompleteAutoAttach();
$('#closer').click(function() {
$('#usereditor').hide().empty();
$('#content').show();
});
$('#userform').find('#input-username').focus();
$('#userform').submit(function() {
var params;
if (userid == null) {
if (!this.username.value.match(/^[a-zA-Z0-9]{2,}$/)) {
alert('Der Nutzername muss aus mindestens 2 Buchstaben oder Ziffern bestehen und darf keine sonstigen Zeichen enthalten.');
this.username.focus();
return false;
}
}
if (userid == null || this.pwd1.value.length > 0) {
if (this.pwd1.value.length < 8) {
alert('Das Passwort muss mindestens 8 Zeichen lang sein!');
this.pwd1.focus();
return false;
}
if (this.pwd1.value != this.pwd2.value) {
alert('Passwort und Wiederholung müssen übereinstimmen!');
this.pwd2.focus();
return false;
}
}
if (!this.groups.value.match(/^([0-9a-zA-z]+[,\s]*)+$/)) {
alert('Die Gruppenangabe muss eine durch Kommata getrennte Liste von Gruppennamen, die aus Buchstaben und Ziffern zusammengesetzt sein können, sein.');
this.groups.focus();
return false;
}
if (userid == null) {
params = {method : 'submituser',
username : this.username.value,
firstname : this.firstname.value,
lastname : this.lastname.value,
password : this.pwd1.value,
groups : this.groups.value};
} else {
params = {method : 'submituser',
uid : userid,
username : this.username.value,
firstname : this.firstname.value,
lastname : this.lastname.value,
password : this.pwd1.value,
groups : this.groups.value};
}
$.post(
"users.php", params,
function(retval) {
$('div#usereditor').hide().empty();
if (!handleerror(retval)) {
updateusers(retval);
}
});
return false;
});
}
function deleteuserdialog(userid, username, firstname, lastname) {
$("#usereditor").hide().empty();
var msg = 'Soll der Nutzer ' + firstname + ' ' + lastname +
' mit dem Login ' + username + ' wirklich gelöscht werden?';
if (confirm(msg) == true) {
$.post(
"users.php",
{method : 'deleteuser',
uid : userid},
function(retval) {
if (!handleerror(retval)) {
var deluid = $('uid:first', retval).text();
$('#usertable').find('tr#uid' + deluid).remove();
}
});
}
}
function deleteuser(userid) {
$.get(
"users.php",
{uid : userid,
method : 'getuserdata'},
function(retval) {
if (!handleerror(retval)) {
var username, lastname, firstname;
username = $("username:first", retval).text();
lastname = $("lastname:first", retval).text();
firstname = $("firstname:first", retval).text();
deleteuserdialog(userid, username, firstname, lastname);
}
});
}
function edituser(userid) {
$.get(
"users.php",
{uid : userid,
method : 'getuserdata'},
function(retval) {
if (!handleerror(retval)) {
var username, lastname, firstname, groups;
username = $("username:first", retval).text();
lastname = $("lastname:first", retval).text();
firstname = $("firstname:first", retval)</