added adminuser settings, globlist support, and used this to implement page
authorjoey <joey@0fa5a96a-9a0e-0410-b3b2-a0fd24251071>
Thu, 23 Mar 2006 01:40:46 +0000 (01:40 +0000)
committerjoey <joey@0fa5a96a-9a0e-0410-b3b2-a0fd24251071>
Thu, 23 Mar 2006 01:40:46 +0000 (01:40 +0000)
locking

basewiki/globlist.mdwn [new file with mode: 0644]
doc/features.mdwn
doc/globlist.mdwn [new file with mode: 0644]
doc/ikiwiki.setup
doc/security.mdwn
doc/todo.mdwn
doc/usage.mdwn
ikiwiki

diff --git a/basewiki/globlist.mdwn b/basewiki/globlist.mdwn
new file mode 100644 (file)
index 0000000..94f1812
--- /dev/null
@@ -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
index f362a7cbb15818ea7d1e1ca9597a2d16eb644600..c20da504db7d90e0d1c21e511f78291a4fa06d30 100644 (file)
@@ -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 (file)
index 0000000..94f1812
--- /dev/null
@@ -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
index d0a2829a1ae9f9401599854da86fbcc5ddec902e..7a561434e12a64f27a5361ffc36e3f8995eac2a9 100644 (file)
@@ -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",
index 5fda9e6787cc82b47580c3b0a0d7c32ba412cddc..63d140ec50c4d054911b39169abc2ea748a0b56f 100644 (file)
@@ -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.
index d8c5a5b8b549b66f51cb4aa4b047f6e9c6bd0bbc..1d4f4759a3b77b0d0efa1df1de4c118b8285eef4 100644 (file)
@@ -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
 
index debe04e1f6b963ae6d98d85bf60338659f7653d1..7d7acf16a50273d2354825ad5479eb5a74a90003 100644 (file)
@@ -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 4b3bd488eac4715ead8a3e415da2fb1b81694df5..aec52ca8601951f5dbe547d43d66ed249fdfcb75 100755 (executable)
--- 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 {