summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjoey <joey@0fa5a96a-9a0e-0410-b3b2-a0fd24251071>2006-03-23 01:40:46 +0000
committerjoey <joey@0fa5a96a-9a0e-0410-b3b2-a0fd24251071>2006-03-23 01:40:46 +0000
commit325d5c791fc50dc3868b2f1938d052d39b676248 (patch)
tree56ddda187ec55f50f97f4a6ae2d04c59ad2763bb
parent33b7f3444c20da53a18203ed33bb470b1c513f07 (diff)
added adminuser settings, globlist support, and used this to implement page
locking
-rw-r--r--basewiki/globlist.mdwn16
-rw-r--r--doc/features.mdwn17
-rw-r--r--doc/globlist.mdwn16
-rw-r--r--doc/ikiwiki.setup3
-rw-r--r--doc/security.mdwn9
-rw-r--r--doc/todo.mdwn9
-rw-r--r--doc/usage.mdwn6
-rwxr-xr-xikiwiki88
8 files changed, 149 insertions, 15 deletions
diff --git a/basewiki/globlist.mdwn b/basewiki/globlist.mdwn
new file mode 100644
index 000000000..94f18127c
--- /dev/null
+++ b/basewiki/globlist.mdwn
@@ -0,0 +1,16 @@
+When the wiki stores lists of pages, such as pages that are locked or pages
+that you want to be emailed if changed, it uses a GlobList.
+
+This is a list of page names, separated by white space. The "glob" bit is
+that as well as full page names, it can contain glob patterns. "`*`" stands
+in for any part of the page name, and "`?`" for any single letter of its
+name. So if you wanted to list all the pages about tea, and any
+[[SubPage]]s of the SandBox, but not including the SandBox itself:
+
+ *tea* SandBox/*
+
+You can also prefix an item in the list with "!" to skip matching any
+pages that match it. So if you want to specify all pages except for
+Discussion pages:
+
+ !*/Discussion
diff --git a/doc/features.mdwn b/doc/features.mdwn
index f362a7cbb..c20da504d 100644
--- a/doc/features.mdwn
+++ b/doc/features.mdwn
@@ -75,10 +75,21 @@ Currently implemented:
* Smart merging and conflict resolution in your web browser
- Since it uses subversion, ikiwiki takes advantage of its smart merging to avoid any conflicts when two people edit different parts of the same page at the same time. No annoying warnings about other editors, or locking, etc, instead the other person's changes will be automaticaly merged with yours when you commit.
+ Since it uses subversion, ikiwiki takes advantage of its smart merging to
+ avoid any conflicts when two people edit different parts of the same page
+ at the same time. No annoying warnings about other editors, or locking,
+ etc, instead the other person's changes will be automaticaly merged with
+ yours when you commit.
- In the rare cases where automatic merging fails due to the same part of a page being concurrently edited, regular subversion commit markers are shown in the file to resolve the conflict, so if you're already familiar with that there's no new commit marker syntax to learn.
+ In the rare cases where automatic merging fails due to the same part of a
+ page being concurrently edited, regular subversion commit markers are
+ shown in the file to resolve the conflict, so if you're already familiar
+ with that there's no new commit marker syntax to learn.
+
+* page locking
+
+ Wiki admin can [[lock]] pages so that only other admins can edit them.
----
-It also has lots of [[TODO]] items and [[Bugs]]. This wiki is not ready for production!
+It also has some [[TODO]] items and [[Bugs]].
diff --git a/doc/globlist.mdwn b/doc/globlist.mdwn
new file mode 100644
index 000000000..94f18127c
--- /dev/null
+++ b/doc/globlist.mdwn
@@ -0,0 +1,16 @@
+When the wiki stores lists of pages, such as pages that are locked or pages
+that you want to be emailed if changed, it uses a GlobList.
+
+This is a list of page names, separated by white space. The "glob" bit is
+that as well as full page names, it can contain glob patterns. "`*`" stands
+in for any part of the page name, and "`?`" for any single letter of its
+name. So if you wanted to list all the pages about tea, and any
+[[SubPage]]s of the SandBox, but not including the SandBox itself:
+
+ *tea* SandBox/*
+
+You can also prefix an item in the list with "!" to skip matching any
+pages that match it. So if you want to specify all pages except for
+Discussion pages:
+
+ !*/Discussion
diff --git a/doc/ikiwiki.setup b/doc/ikiwiki.setup
index d0a2829a1..7a561434e 100644
--- a/doc/ikiwiki.setup
+++ b/doc/ikiwiki.setup
@@ -7,7 +7,8 @@
use IkiWiki::Setup::Standard {
wikiname => "MyWiki",
-
+ #adminuser => ["yourname", ],
+
# Be sure to customise these..
srcdir => "/path/to/source",
destdir => "/var/www/wiki",
diff --git a/doc/security.mdwn b/doc/security.mdwn
index 5fda9e678..63d140ec5 100644
--- a/doc/security.mdwn
+++ b/doc/security.mdwn
@@ -50,6 +50,13 @@ It's actually possible to force a whole series of svn commits to appear to have
ikiwiki escapes any html in svn commit logs to prevent other mischief.
+## page locking can be bypassed via direct svn commits
+
+A [[lock]]ed page can only be edited on the web by an admin, but
+anyone who is allowed to commit direct to svn can bypass this. This is by
+design, although a subversion pre-commit hook could be used to prevent
+editing of locked pages when using subversion, if you really need to.
+
----
# Hopefully non-holes
@@ -136,4 +143,4 @@ directory with a symlink and trick it into following the link.
Also, if someone checks in a symlink to /etc/passwd, ikiwiki would read and publish that, which could be used to expose files a committer otherwise wouldn't see.
-To avoid this, ikiwiki will avoid reading files that are symlinks, and uses locking to prevent more than one instance running at a time. The lock prevents one ikiwiki from running a svn up at the wrong time to race another ikiwiki. So only attackers who can write to the working copy on their own can race it. \ No newline at end of file
+To avoid this, ikiwiki will avoid reading files that are symlinks, and uses locking to prevent more than one instance running at a time. The lock prevents one ikiwiki from running a svn up at the wrong time to race another ikiwiki. So only attackers who can write to the working copy on their own can race it.
diff --git a/doc/todo.mdwn b/doc/todo.mdwn
index d8c5a5b8b..1d4f4759a 100644
--- a/doc/todo.mdwn
+++ b/doc/todo.mdwn
@@ -104,11 +104,14 @@ you need that data..
Might be nice to support automatically generating an index based on headers in a page, for long pages. The question is, how to turn on such an index?
-## page locking
+## basewiki underlay
-Some wikis will need the abiity to lock a page, or the whole wiki, so that only admins can edit them. Probably using the same globbing as for recentchanges mails to determine what to lock.
+Rather than copy the basewii around everywhere, it should be configured to
+underlay the main srcdir, and pages be rendered from there if not in the
+srcdir. This would allow upgrades to add/edit pages in the basewiki.
-Probably it's ok if locking is only supported for web commits.
+Impementaion will be slightly tricky since currently ikiwiki is hardcoded
+in many places to look in srcdir for pages.
## Logo
diff --git a/doc/usage.mdwn b/doc/usage.mdwn
index debe04e1f..7d7acf16a 100644
--- a/doc/usage.mdwn
+++ b/doc/usage.mdwn
@@ -94,6 +94,12 @@ flags such as --verbose can be negated with --no-verbose.
Specifies a rexexp of source files to exclude from processing.
May be specified multiple times to add to exclude list.
+* --adminuser name
+
+ Specifies a username of a user who has the powers of a wiki admin.
+ Currently allows locking of any page, other powers may be added later.
+ May be specified multiple times for multiple admins.
+
* --setup configfile
In setup mode, ikiwiki reads the config file, which is really a perl
diff --git a/ikiwiki b/ikiwiki
index 4b3bd488e..aec52ca86 100755
--- a/ikiwiki
+++ b/ikiwiki
@@ -1,4 +1,7 @@
#!/usr/bin/perl -T
+
+eval 'exec /usr/bin/perl -T -S $0 ${1+"$@"}'
+ if 0; # not running under some shell
$ENV{PATH}="/usr/local/bin:/usr/bin:/bin";
use warnings;
@@ -32,6 +35,7 @@ our %config=( #{{{
destdir => undef,
templatedir => undef,
setup => undef,
+ adminuser => undef,
); #}}}
GetOptions( #{{{
@@ -51,6 +55,7 @@ GetOptions( #{{{
"exclude=s@" => sub {
$config{wiki_file_prune_regexp}=qr/$config{wiki_file_prune_regexp}|$_[1]/;
},
+ "adminuser=s@" => sub { push @{$config{adminuser}}, $_[1] },
) || usage();
if (! $config{setup}) {
@@ -778,6 +783,7 @@ sub gen_wrapper (@) { #{{{
push @params, "--historyurl=$config{historyurl}" if length $config{historyurl};
push @params, "--diffurl=$config{diffurl}" if length $config{diffurl};
push @params, "--anonok" if $config{anonok};
+ push @params, "--adminuser=$_" foreach @{$config{adminuser}};
my $params=join(" ", @params);
my $call='';
foreach my $p ($this, $this, @params) {
@@ -878,7 +884,8 @@ sub userinfo_get ($$) { #{{{
eval q{use Storable};
my $userdata=eval{ Storable::lock_retrieve("$config{srcdir}/.ikiwiki/userdb") };
if (! defined $userdata || ! ref $userdata ||
- ! exists $userdata->{$user} || ! ref $userdata->{$user}) {
+ ! exists $userdata->{$user} || ! ref $userdata->{$user} ||
+ ! exists $userdata->{$user}->{$field}) {
return "";
}
return $userdata->{$user}->{$field};
@@ -1079,6 +1086,59 @@ sub cgi_signin ($$) { #{{{
}
} #}}}
+sub is_admin ($) { #{{{
+ my $user_name=shift;
+
+ return grep { $_ eq $user_name } @{$config{adminuser}};
+} #}}}
+
+sub glob_match ($$) { #{{{
+ my $page=shift;
+ my $glob=shift;
+
+ # turn glob into safe regexp
+ $glob=quotemeta($glob);
+ $glob=~s/\\\*/.*/g;
+ $glob=~s/\\\?/./g;
+ $glob=~s!\\/!/!g;
+
+ $page=~/^$glob$/i;
+} #}}}
+
+sub globlist_match ($$) { #{{{
+ my $page=shift;
+ my @globlist=split(" ", shift);
+
+ # check any negated globs first
+ foreach my $glob (@globlist) {
+ return 0 if $glob=~/^!(.*)/ && glob_match($page, $1);
+ }
+
+ foreach my $glob (@globlist) {
+ return 1 if glob_match($page, $glob);
+ }
+
+ return 0;
+} #}}}
+
+sub page_locked ($$;$) { #{{{
+ my $page=shift;
+ my $session=shift;
+ my $nonfatal=shift;
+
+ my $user=$session->param("name");
+ return if length $user && is_admin($user);
+
+ foreach my $admin (@{$config{adminuser}}) {
+ my $locked_pages=userinfo_get($admin, "locked_pages");
+ if (globlist_match($page, userinfo_get($admin, "locked_pages"))) {
+ return 1 if $nonfatal;
+ error(htmllink("", $page, 1)." is locked by ".
+ htmllink("", $admin, 1)." and cannot be edited.");
+ }
+ }
+} #}}}
+
sub cgi_prefs ($$) { #{{{
my $q=shift;
my $session=shift;
@@ -1086,7 +1146,7 @@ sub cgi_prefs ($$) { #{{{
eval q{use CGI::FormBuilder};
my $form = CGI::FormBuilder->new(
title => "preferences",
- fields => [qw(do name password confirm_password email)],
+ fields => [qw(do name password confirm_password email locked_pages)],
header => 0,
method => 'POST',
validate => {
@@ -1110,9 +1170,18 @@ sub cgi_prefs ($$) { #{{{
value => $user_name, force => 1);
$form->field(name => "password", type => "password");
$form->field(name => "confirm_password", type => "password");
+ $form->field(name => "locked_pages", size => 50,
+ comment => "(".htmllink("", "GlobList", 1).")");
+
+ if (! is_admin($user_name)) {
+ $form->field(name => "locked_pages", type => "hidden");
+ }
if (! $form->submitted) {
- $form->field(name => "email", value => userinfo_get($user_name, "email"));
+ $form->field(name => "email", force => 1,
+ value => userinfo_get($user_name, "email"));
+ $form->field(name => "locked_pages", force => 1,
+ value => userinfo_get($user_name, "locked_pages"));
}
if ($form->submitted eq 'Logout') {
@@ -1125,7 +1194,7 @@ sub cgi_prefs ($$) { #{{{
return;
}
elsif ($form->submitted eq "Save Preferences" && $form->validate) {
- foreach my $field (qw(password email)) {
+ foreach my $field (qw(password email locked_pages)) {
if (length $form->field($field)) {
userinfo_set($user_name, $field, $form->field($field)) || error("failed to set $field");
}
@@ -1238,8 +1307,10 @@ sub cgi_editpage ($$) { #{{{
push @page_locs, $dir.$page;
}
- @page_locs = grep { ! exists
- $pagesources{lc($_)} } @page_locs;
+ @page_locs = grep {
+ ! exists $pagesources{lc($_)} &&
+ ! page_locked($_, $session, 1)
+ } @page_locs;
}
$form->tmpl_param("page_select", 1);
@@ -1248,6 +1319,7 @@ sub cgi_editpage ($$) { #{{{
$form->title("creating $page");
}
elsif ($form->field("do") eq "edit") {
+ page_locked($page, $session);
if (! defined $form->field('content') ||
! length $form->field('content')) {
my $content="";
@@ -1267,13 +1339,15 @@ sub cgi_editpage ($$) { #{{{
}
else {
# save page
+ page_locked($page, $session);
+
my $content=$form->field('content');
$content=~s/\r\n/\n/g;
$content=~s/\r/\n/g;
writefile("$config{srcdir}/$file", $content);
my $message="web commit ";
- if ($session->param("name")) {
+ if (length $session->param("name")) {
$message.="by ".$session->param("name");
}
else {