From ff4ff7c79ee0f37e84471e93804a95074b8e84ae Mon Sep 17 00:00:00 2001
From: Joe Rayhawk <jrayhawk@omgwallhack.org>
Date: Sun, 22 May 2011 22:19:16 -0700
Subject: Attempt to support core.sharedRepository

---
 libpiny/lib/Piny/Config.pm |   4 +-
 libpiny/lib/Piny/Repo.pm   | 209 ++++++++++++++++++++++++++++++++++++---------
 2 files changed, 171 insertions(+), 42 deletions(-)

(limited to 'libpiny/lib/Piny')

diff --git a/libpiny/lib/Piny/Config.pm b/libpiny/lib/Piny/Config.pm
index a1813d4..1da5631 100644
--- a/libpiny/lib/Piny/Config.pm
+++ b/libpiny/lib/Piny/Config.pm
@@ -49,8 +49,8 @@ subtype 'HttpsUrl'
 
 subtype 'RepoPermission'
   => as 'Str'
-  => where { $_ eq "0666" or $_ eq "0664" or $_ eq "0660" or $_ eq "0640" or $_ eq "group" or $_ eq "true" or $_ eq "false" or $_ eq "all" or $_ eq "everybody" or $_ eq "world"}
-  => message { 'Must be one of 0666, 0664 (or all, everybody, world), 0660 (or true, group), or 0640 (or false).' }
+  => where { $_ eq "0666" or $_ eq "0664" or $_ eq "0660" or $_ eq "0640" or $_ eq "group" or $_ eq "true" or $_ eq "false" or $_ eq "all" or $_ eq "everybody" or $_ eq "world" or $_ eq "1" or $_ eq "2" }
+  => message { 'Must be one of 0666, 0664 (or all, everybody, world), 0660 (or true, 1, group), or 0640 (or false, 0).' }
   ;
 
 # Attributes
diff --git a/libpiny/lib/Piny/Repo.pm b/libpiny/lib/Piny/Repo.pm
index 3bb275d..8c38323 100644
--- a/libpiny/lib/Piny/Repo.pm
+++ b/libpiny/lib/Piny/Repo.pm
@@ -174,7 +174,21 @@ has 'secure_path' =>
   , init_arg    => undef
   );
 
-has 'apache_config' =>
+has 'apache_global_config' =>
+  ( is          => 'ro'
+  , isa         => 'Str'
+  , lazy_build  => 1
+  , init_arg    => undef
+  );
+
+has 'apache_secure_config' =>
+  ( is          => 'ro'
+  , isa         => 'Str'
+  , lazy_build  => 1
+  , init_arg    => undef
+  );
+
+has 'apache_www_config' =>
   ( is          => 'ro'
   , isa         => 'Str'
   , lazy_build  => 1
@@ -224,6 +238,9 @@ sub rebuild {
 sub rebuild_git {
   my ( $s ) = @_;
 
+  my $dirperm;
+  my $fileperm;
+
   unless( getgrnam("git-" . $s->shortname ) ) {
     system( "/usr/sbin/addgroup", "--quiet", "git-" . $s->shortname ) and die "Could not create repo group!";
     system( "/usr/sbin/adduser", "--quiet", $s->owner->name, "git-" . $s->shortname ) and die "Could not add you to the repo group!";
@@ -237,20 +254,45 @@ sub rebuild_git {
     };
   };
 
-  chown( 0, 0, $s->path, $s->path . "/config" ) or die "Could not change ownership of git config!";
-
   foreach( "info", "logs", "branches" ) {
     (-e $s->path . "/" . $_) or mkdir( $s->path . "/" . $_ ) or die "Could not mkdir $_ for repo: $!";
   };
 
-  foreach( "branches", "description", "HEAD", "info", "logs", "objects", "packed-refs", "refs" ) {
-    system( "/bin/chown", "-R", $s->owner->name . "." . $s->group->name, $s->path . "/" . $_ ) and die "Could not change ownership of $_ for repo: $!";
+  if      ( $s->config->core_sharedrepository eq   "0666" ) {
+    $dirperm  = "2777";
+    $fileperm = "0644";
+  } elsif ( $s->config->core_sharedrepository =~ /^(0664|all|everybody|world)$/ ) {
+    $dirperm  = "2775";
+    $fileperm = "0644";
+  } elsif ( $s->config->core_sharedrepository =~ /^(0660|true|1|group)$/ ) {
+    $dirperm  = "2770";
+    $fileperm = "0640";
+  } elsif ( $s->config->core_sharedrepository =~ /^(0640|false|0)$/ ) {
+    $dirperm  = "2750";
+    $fileperm = "0640";
+  } else {
+    die $s->config->core_sharedrepository . "is an unhandled value!"
+  };
+
+  # FIXME: we should verify we are not breaking someone else's object hardlinks with these chmod or chown operations
+  system( "/usr/bin/find " . $s->path . "/refs " . $s->path . "/info " . $s->path . "/branches " . $s->path . "/objects " . $s->path . "/logs " . $s->path . "/HEAD " . $s->path . "/packed-refs -type d -print0 | /usr/bin/xargs -0 /bin/chmod $dirperm" ) and die "Could not chmod shared git resources!";
+  system( "/usr/bin/find " . $s->path . "/refs " . $s->path . "/info " . $s->path . "/branches " . $s->path . "/objects " . $s->path . "/logs " . $s->path . "/HEAD " . $s->path . "/packed-refs -type f -print0 | /usr/bin/xargs -0 /bin/chmod $fileperm" ) and die "Could not chmod shared git resources!"; # most files are either immutable or replaced at link level
+  system( "/usr/bin/find " . $s->path . "/refs " . $s->path . "/info " . $s->path . "/branches " . $s->path . "/objects " . $s->path . "/logs " . $s->path . "/HEAD " . $s->path . "/packed-refs         -print0 | /usr/bin/xargs -0 /bin/chgrp " . $s->group->name ) and die "Could not chgrp shared git resources!";
+
+  chown( 0, 0, $s->path ) or die "Could not change ownership of " . $s->path;
+  chmod( 00755, $s->path ) or die "Could not change mode of " . $s->path;
+
+  foreach( "config", "description", "git-daemon-export-ok" ) {
+    chown( 0, 0, $s->path . "/" . $_ ) or die "Could not change ownership of $_!";
+    chmod( 00644, $s->path . "/" . $_ ) or die "Could not change mode of $_!";
   };
 
   $ENV{"GIT_DIR"} = $s->path;
     system( "/usr/bin/git", "config", "gitweb.owner", $s->owner->email->address ) and die "Could not git config gitweb.owner!";
     # packed-refs files aren't modifiable under our permission system and are poorly supported on old version of git anyway.
     system( "/usr/bin/git", "config", "gc.packrefs", "0" ) and die "Could not git config gc.packrefs!";
+    # git-upload-pack will default to core.sharedrepository 'false', so we make sure the pinyconfig value is set here.
+    system( "/usr/bin/git", "config", "core.sharedrepository", $s->config->core_sharedrepository ) and die "Could not git config core.sharedrepository!";
   delete $ENV{"GIT_DIR"};
 
   $s->clear_config;
@@ -274,15 +316,16 @@ sub rebuild_ikiwiki {
   print SETUP $s->ikiwiki_setup;
   close( SETUP ) or die "Could not close new ikiwiki setup file: $!";
 
-  unless( -d $s->ikiwiki_srcdir ) {
-    system( "/usr/bin/git", "clone", "--quiet", $s->path, $s->ikiwiki_srcdir ) and die "Could not clone repo to ikiwiki srcdir!"; 
+  foreach( $s->ikiwiki_srcdir, $s->config->piny_ikiwikidestdir . $s->name, $s->ikiwiki_destdir, $s->secure_path ) {
+    unless( -d $_ ) { mkdir( $_ ) };
+    system( "/bin/chown", "-R", $ikiuser->name . ".", $_ ) and die "Could not change ownership of ikiwiki directories!";
   };
 
   system( "/usr/bin/find " . $s->ikiwiki_srcdir . " -type d -name .ikiwiki -print0 | xargs -0 --no-run-if-empty rm -r") and die "Could not remove old Ikiwiki state dir!";
 
-  foreach( $s->ikiwiki_srcdir, $s->ikiwiki_destdir, $s->secure_path ) {
-    unless( -d $_ ) { mkdir( $_ ) };
-    system( "/bin/chown", "-R", $ikiuser->name . ".", $_ ) and die "Could not change ownership of ikiwiki directories!";
+  unless( -d $s->ikiwiki_srcdir . ".git" ) {
+    # do this after the chown -R because there are a lot of object hardlinks flying around that don't want to chown
+    system( "/usr/bin/sudo", "-u", $ikiuser->name, "/usr/bin/git", "clone", "--quiet", $s->path, $s->ikiwiki_srcdir ) and die "Could not clone repo to ikiwiki srcdir!"; 
   };
 
   open( WIKILIST, ">", "/etc/ikiwiki/wikilist.d/" . $s->name ) or die "Could not create wikilist.d file: $!";
@@ -293,12 +336,23 @@ sub rebuild_ikiwiki {
 
   system( "/usr/bin/sudo", "-u", $ikiuser->name, "/usr/bin/ikiwiki", "--setup", "/etc/ikiwiki/piny/" . $s->name . ".setup" ) and die "Could not do initial compile of ikiwiki!";
 
-  open( APACHE, ">", "/etc/apache2/piny-available/" . $s->name ) or die "Could not open new apache config: $!";
-  print APACHE $s->apache_config;
-  close( APACHE ) or die "Could not close new apache config: $!";
+  my $globaltemp = File::Temp->new( ) or die "Could not create temporary file: $!";
+  $globaltemp->unlink_on_destroy( 0 );
+  print $globaltemp $s->apache_global_config;
+  $globaltemp->close or die "Could not close new wikilist: $!";
+  rename( $globaltemp->filename, "/etc/apache2/piny/global/" . $s->name ) or die "Could not rename apache config: $!";
+
+  my $securetemp = File::Temp->new( ) or die "Could not create temporary file: $!";
+  $securetemp->unlink_on_destroy( 0 );
+  print $securetemp $s->apache_secure_config;
+  $securetemp->close or die "Could not close new wikilist: $!";
+  rename( $securetemp->filename, "/etc/apache2/piny/secure/" . $s->name ) or die "Could not rename apache config: $!";
 
-  unlink( "/etc/apache2/piny-enabled/" . $s->name );
-  symlink( "/etc/apache2/piny-available/" . $s->name, "/etc/apache2/piny-enabled/" . $s->name ) or die "Could not symlink apache config: $!";
+  my $wwwtemp = File::Temp->new( ) or die "Could not create temporary file: $!";
+  $wwwtemp->unlink_on_destroy( 0 );
+  print $wwwtemp $s->apache_www_config;
+  $wwwtemp->close or die "Could not close new wikilist: $!";
+  rename( $wwwtemp->filename, "/etc/apache2/piny/www/" . $s->name ) or die "Could not rename apache config: $!";
 
   system( "/etc/init.d/apache2", "reload" ) and die "Could not reload apache config!";
 };
@@ -340,7 +394,7 @@ sub destroy_ikiwiki {
 
   my $user = Piny::Environment->instance->user;
 
-  foreach( "/etc/apache2/piny-enabled/" . $s->name, "/etc/apache2/piny-available/" . $s->name, ) {
+  foreach( "/etc/apache2/piny/global/" . $s->name, "/etc/apache2/piny/secure/" . $s->name, "/etc/apache2/piny/www/" . $s->name, ) {
     if ( -e $_ ) {
        unlink( $_ );
     };
@@ -353,7 +407,7 @@ sub destroy_ikiwiki {
     $s->rebuild_wikilist;
   };
 
-  foreach( $s->secure_path, $s->ikiwiki_destdir, $s->ikiwiki_srcdir, "/etc/ikiwiki/piny/" . $s->name . ".setup" ) {
+  foreach( $s->secure_path, $s->config->piny_ikiwikidestdir . $s->name, $s->config->piny_ikiwikisecurepath . "read/" . $s->name, $s->ikiwiki_destdir, $s->ikiwiki_srcdir, "/etc/ikiwiki/piny/" . $s->name . ".setup" ) {
     if ( -e $_ ) {
       system( "rm", "-rf", $_ );
     };
@@ -417,18 +471,19 @@ sub create {
 
   mkdir( $repo->path ) or die "The repo $name appears to already exist! ($!)";
 
-  $ENV{"GIT_DIR"} = $repo->path;
-    system( "/usr/bin/git", "init", "--bare", "--quiet", "--shared" ) and die "Could not initialize git repo!";
-  delete $ENV{"GIT_DIR"};
-
-  system( "/bin/chown", "-R", $user->name, $repo->path . "/objects" ) and die "Could not change ownership of $_ for repo: $!";
-
+  chdir( "/srv/git" ); # so git-clone can do the work of resolving local repo names for us
   if ( defined $source ) {
-    $ENV{"GIT_DIR"} = $source;
-      system( "/usr/bin/git", "push", "--mirror", $repo->path ) and die "Could not push refs to new repository!";
-    delete $ENV{"GIT_DIR"};
+    system( "/bin/chown", $user . "." . $user, $repo->path ) and die "Could not change ownership of repo path!"; # permissions are overridden later
+    if ( system( "/usr/bin/sudo", "-u", $user, "/usr/bin/git", "clone", "--bare", "--no-hardlinks", "--quiet", $source, $repo->path ) ) { # sudo -u $user needed in order to test user readability of whatever they want to clone. the mode o+rx should probably be masked off if we want private repos to stay private.
+      system( "/bin/rm", "-r", $repo->path ) and die "Failed to clean up after failed clone operation!";
+      die( "Could not clone repository!\n" );
+    };
+  } else {
+    system( "/usr/bin/git", "init", "--bare", "--quiet", $repo->path ) and die "Could not initialize git repo!";
   };
 
+  system( "/bin/chown", "-R", $user->name, $repo->path . "/objects" ) and die "Could not change ownership of $_ for repo: $!";
+
   $repo->description( $description );
 
   $repo->rebuild_git;
@@ -544,7 +599,17 @@ sub _build_globally_writable {
 sub _build_cgit_url {
   my ( $s ) = @_;
 
-  return $s->config->piny_ikiwikisecureurl . "cgit/" . $s->name;
+  if      ( $s->config->core_sharedrepository eq   "0666" ) {
+    return $s->config->piny_ikiwikisecureurl . "cgit/" . $s->name;
+  } elsif ( $s->config->core_sharedrepository =~ /^(0664|all|everybody|world)$/ ) {
+    return $s->config->piny_ikiwikisecureurl . "cgit/" . $s->name;
+  } elsif ( $s->config->core_sharedrepository =~ /^(0660|true|1|group)$/ ) {
+    return $s->config->piny_ikiwikisecureurl . "auth/cgit/" . $s->name;
+  } elsif ( $s->config->core_sharedrepository =~ /^(0640|false|0)$/ ) {
+    return $s->config->piny_ikiwikisecureurl . "auth/cgit/" . $s->name;
+  } else {
+    die ( $s->config->core_sharedrepository . "is an unhandled value!" );
+  };
 }
 
 sub _build_ikiwiki_setup {
@@ -593,7 +658,17 @@ sub _build_ikiwiki_setup {
 sub _build_ikiwiki_destdir {
   my ( $s ) = @_;
 
-  return $s->config->piny_ikiwikidestdir . $s->name;
+  if      ( $s->config->core_sharedrepository eq   "0666" ) {
+    return ( $s->config->piny_ikiwikidestdir . $s->name );
+  } elsif ( $s->config->core_sharedrepository =~ /^(0664|all|everybody|world)$/ ) {
+    return ( $s->config->piny_ikiwikidestdir . $s->name );
+  } elsif ( $s->config->core_sharedrepository =~ /^(0660|true|1|group)$/ ) {
+    return ( $s->config->piny_ikiwikisecurepath . "read/" . $s->name );
+  } elsif ( $s->config->core_sharedrepository =~ /^(0640|false|0)$/ ) {
+    return ( $s->config->piny_ikiwikisecurepath . "read/" . $s->name );
+  } else {
+    die ( $s->config->core_sharedrepository . "is an unhandled value!" );
+  };
 };
 
 sub _build_ikiwiki_srcdir {
@@ -605,19 +680,29 @@ sub _build_ikiwiki_srcdir {
 sub _build_ikiwiki_url {
   my ( $s ) = @_;
 
-  return $s->config->piny_ikiwikiurl . $s->name;
+  if      ( $s->config->core_sharedrepository eq   "0666" ) {
+    return ( $s->config->piny_ikiwikiurl . $s->name );
+  } elsif ( $s->config->core_sharedrepository =~ /^(0664|all|everybody|world)$/ ) {
+    return ( $s->config->piny_ikiwikiurl . $s->name );
+  } elsif ( $s->config->core_sharedrepository =~ /^(0660|true|1|group)$/ ) {
+    return ( $s->config->piny_ikiwikisecureurl . "read/" . $s->name );
+  } elsif ( $s->config->core_sharedrepository =~ /^(0640|false|0)$/ ) {
+    return ( $s->config->piny_ikiwikisecureurl . "read/" . $s->name );
+  } else {
+    die ( $s->config->core_sharedrepository . "is an unhandled value!" );
+  };
 };
 
 sub _build_ikiwiki_cgiurl {
   my ( $s ) = @_;
 
-  return $s->config->piny_ikiwikisecureurl . "repos/" . $s->name . "/ikiwiki.cgi";
+  return $s->config->piny_ikiwikisecureurl . "write/" . $s->name . "/ikiwiki.cgi";
 };
 
 sub _build_secure_path {
   my ( $s ) = @_;
 
-  return $s->config->piny_ikiwikisecurepath . "repos/" . $s->name;
+  return $s->config->piny_ikiwikisecurepath . "write/" . $s->name;
 };
 
 sub _build_ikiwiki_cgipath {
@@ -629,27 +714,71 @@ sub _build_ikiwiki_cgipath {
 sub _build_ikiwiki_historyurl {
   my ( $s ) = @_;
 
-  if ( defined $s->config->{"https_url"} ) {
-    return $s->config->{"https_url"} . "cgit/" . $s->name . "/log/[[file]]";
+  return $s->cgit_url . "/log/[[file]]";
+};
+
+sub _build_ikiwiki_diffurl {
+  my ( $s ) = @_;
+
+  return $s->cgit_url . "/log/[[file]]";
+};
+
+sub _build_apache_global_config {
+  my ( $s ) = @_;
+
+  if      ( $s->config->core_sharedrepository eq   "0666" ) {
+    return (
+      "<Directory " . $s->secure_path .     ">\n  SSLRequireSSL\n  AuthPAM_Enabled on\n" .                    "  AuthPAM_FallThrough off\n  AuthBasicAuthoritative off\n  AuthType Basic\n  AuthName \"Valid Piny user needed.\"\n" .                            "  Require valid-user\n" .             "  </Directory>\n"
+    );
+  } elsif ( $s->config->core_sharedrepository =~ /^(0664|all|everybody|world)$/ ) {
+    return (
+      "<Directory " . $s->secure_path .     ">\n  SSLRequireSSL\n  AuthPAM_Enabled on\n  AuthGROUP_Enabled on\n  AuthPAM_FallThrough off\n  AuthBasicAuthoritative off\n  AuthType Basic\n  AuthName \"User with access to " . $s->name . " repository needed.\"\n  Require group " . $s->group->name . "\n</Directory>\n"
+    );
+  } elsif ( $s->config->core_sharedrepository =~ /^(0660|true|1|group)$/ ) {
+    return (
+      "<Directory " . $s->secure_path .     ">\n  SSLRequireSSL\n  AuthPAM_Enabled on\n  AuthGROUP_Enabled on\n  AuthPAM_FallThrough off\n  AuthBasicAuthoritative off\n  AuthType Basic\n  AuthName \"User with access to " . $s->name . " repository needed.\"\n  Require group " . $s->group->name . "\n</Directory>\n" .
+      "<Directory " . $s->ikiwiki_destdir . ">\n  SSLRequireSSL\n  AuthPAM_Enabled on\n  AuthGROUP_Enabled on\n  AuthPAM_FallThrough off\n  AuthBasicAuthoritative off\n  AuthType Basic\n  AuthName \"User with access to " . $s->name . " repository needed.\"\n  Require group " . $s->group->name . "\n</Directory>\n"
+    );
+  } elsif ( $s->config->core_sharedrepository =~ /^(0640|false|0)$/ ) {
+    return (
+      "<Directory " . $s->secure_path .     ">\n  SSLRequireSSL\n  AuthPAM_Enabled on\n" .                    "  AuthPAM_FallThrough off\n  AuthBasicAuthoritative off\n  AuthType Basic\n  AuthName \"Owner of " .            $s->name . " repository needed.\"\n  Require user " .  $s->owner->name . "\n</Directory>\n" .
+      "<Directory " . $s->ikiwiki_destdir . ">\n  SSLRequireSSL\n  AuthPAM_Enabled on\n  AuthGROUP_Enabled on\n  AuthPAM_FallThrough off\n  AuthBasicAuthoritative off\n  AuthType Basic\n  AuthName \"User with access to " . $s->name . " repository needed.\"\n  Require group " . $s->group->name . "\n</Directory>\n"
+    );
   } else {
-    return $s->config->piny_ikiwikisecureurl . "cgit/" . $s->name . "/log/[[file]]";
+    die ( $s->config->core_sharedrepository . "is an unhandled value!" );
   };
 };
 
-sub _build_ikiwiki_diffurl {
+sub _build_apache_secure_config {
   my ( $s ) = @_;
 
-  if ( defined $s->config->{"https_url"} ) {
-    return $s->config->{"https_url"} . "cgit/" . $s->name . "/diff/?id=[[sha1_commit]]";
+  if      ( $s->config->core_sharedrepository eq   "0666" ) {
+    return ( "Redirect /read/" . $s->name . " " . $s->ikiwiki_url . "\n" );
+  } elsif ( $s->config->core_sharedrepository =~ /^(0664|all|everybody|world)$/ ) {
+    return ( "Redirect /read/" . $s->name . " " . $s->ikiwiki_url . "\n" );
+  } elsif ( $s->config->core_sharedrepository =~ /^(0660|true|1|group)$/ ) {
+    return ( "Redirect /cgit/" . $s->name . " /auth/cgit/" . $s->name . "\n" );
+  } elsif ( $s->config->core_sharedrepository =~ /^(0640|false|0)$/ ) {
+    return ( "Redirect /cgit/" . $s->name . " /auth/cgit/" . $s->name . "\n" );
   } else {
-    return $s->config->piny_ikiwikisecureurl . "cgit/" . $s->name . "/diff/?id=[[sha1_commit]]";
+    die ( $s->config->core_sharedrepository . "is an unhandled value!" );
   };
 };
 
-sub _build_apache_config {
+sub _build_apache_www_config {
   my ( $s ) = @_;
 
-  return "<Directory " . $s->secure_path . ">\n  AuthPAM_Enabled on\n  AuthGROUP_Enabled on\n  AuthPAM_FallThrough off\n  AuthBasicAuthoritative off\n  AuthType Basic\n  AuthName \"User access to " . $s->name . " repository needed.\"\n  Require group " . $s->group->name . "\n</Directory>\n";
+  if      ( $s->config->core_sharedrepository eq   "0666" ) {
+    return ( "\n" );
+  } elsif ( $s->config->core_sharedrepository =~ /^(0664|all|everybody|world)$/ ) {
+    return ( "\n" );
+  } elsif ( $s->config->core_sharedrepository =~ /^(0660|true|1|group)$/ ) {
+    return ( "Redirect /" . $s->name . " " . $s->ikiwiki_url . "\n" );
+  } elsif ( $s->config->core_sharedrepository =~ /^(0640|false|0)$/ ) {
+    return ( "Redirect /" . $s->name . " " . $s->ikiwiki_url . "\n" );
+  } else {
+    die ( $s->config->core_sharedrepository . "is an unhandled value!" );
+  };
 };
 
 sub _build_config {
-- 
cgit v1.2.3