changeset 738:882917cbe8d0 cannam

Merge from branch "feature_318"
author Chris Cannam
date Fri, 04 Nov 2011 16:55:52 +0000
parents 7c7ef64e68da (current diff) 1ce6efe3db0e (diff)
children 2034af722d02
files
diffstat 1 files changed, 122 insertions(+), 23 deletions(-) [+]
line wrap: on
line diff
--- a/extra/soundsoftware/SoundSoftware.pm	Mon Oct 17 17:03:46 2011 +0100
+++ b/extra/soundsoftware/SoundSoftware.pm	Fri Nov 04 16:55:52 2011 +0000
@@ -110,6 +110,11 @@
     req_override => OR_AUTHCFG,
     args_how => TAKE1,
   },
+  {
+    name => 'SoundSoftwareSslRequired',
+    req_override => OR_AUTHCFG,
+    args_how => TAKE1,
+  },
 );
 
 sub SoundSoftwareDSN { 
@@ -143,6 +148,8 @@
     }
 }
 
+sub SoundSoftwareSslRequired { set_val('SoundSoftwareSslRequired', @_); }
+
 sub trim {
     my $string = shift;
     $string =~ s/\s{2,}/ /g;
@@ -184,33 +191,80 @@
 
     my $project_id = get_project_identifier($dbh, $r);
 
-    if (!defined $read_only_methods{$method}) {
-        print STDERR "SoundSoftware.pm:$$: Method is not read-only\n";
-        if (project_repo_is_readonly($dbh, $project_id, $r)) {
-            print STDERR "SoundSoftware.pm:$$: Project repo is read-only, refusing access\n";
-	    return FORBIDDEN;
-        } else {
-	    print STDERR "SoundSoftware.pm:$$: Project repo is read-write, authentication handler required\n";
-            return OK;
-        }
-    }
+    # We want to delegate most of the work to the authentication
+    # handler (to ensure that user is asked to login even for 
+    # nonexistent projects -- so they can't tell whether a private
+    # project exists or not without authenticating). So 
+    # 
+    # * if the project is public
+    #   - if the method is read-only
+    #     + set handler to OK, no auth needed
+    #   - if the method is not read-only
+    #     + if the repo is read-only, return forbidden
+    #     + else require auth
+    # * if the project is not public or does not exist
+    #     + require auth
+    #
+    # If we are requiring auth and are not currently https, and
+    # https is required, then we must return a redirect to https
+    # instead of an OK.
 
     my $status = get_project_status($dbh, $project_id, $r);
+    my $readonly = project_repo_is_readonly($dbh, $project_id, $r);
 
     $dbh->disconnect();
     undef $dbh;
 
-    if ($status == 0) { # nonexistent
-	print STDERR "SoundSoftware.pm:$$: Project does not exist, refusing access\n";
-	return FORBIDDEN;
-    } elsif ($status == 1) { # public
-	print STDERR "SoundSoftware.pm:$$: Project is public, no restriction here\n";
-	$r->set_handlers(PerlAuthenHandler => [\&OK])
-    } else { # private
-	print STDERR "SoundSoftware.pm:$$: Project is private, authentication handler required\n";
+    my $auth_ssl_reqd = will_require_ssl_auth($r);
+
+    if ($status == 1) { # public
+
+	print STDERR "SoundSoftware.pm:$$: Project is public\n";
+
+	if (!defined $read_only_methods{$method}) {
+
+	    print STDERR "SoundSoftware.pm:$$: Method is not read-only\n";
+
+	    if ($readonly) {
+		print STDERR "SoundSoftware.pm:$$: Project repo is read-only, refusing access\n";
+		return FORBIDDEN;
+	    } else {
+		print STDERR "SoundSoftware.pm:$$: Project repo is read-write, auth required\n";
+		# fall through, this is the normal case
+	    }
+
+        } elsif ($auth_ssl_reqd and $r->unparsed_uri =~ m/cmd=branchmap/) {
+
+            # A hac^H^H^Hspecial case. We want to ensure we switch to
+            # https (if it will be necessarily for authentication) 
+            # before the first POST request, and this is what I think
+            # will give us suitable warning for Mercurial.
+
+            print STDERR "SoundSoftware.pm:$$: Switching to HTTPS in preparation\n";
+            # fall through, this is the normal case
+
+	} else {
+	    # Public project, read-only method -- this is the only
+	    # case we can decide for certain to accept in this function
+	    print STDERR "SoundSoftware.pm:$$: Method is read-only, no restriction here\n";
+	    $r->set_handlers(PerlAuthenHandler => [\&OK]);
+	    return OK;
+	}
+
+    } else { # status != 1, i.e. nonexistent or private -- equivalent here
+
+	print STDERR "SoundSoftware.pm:$$: Project is private or nonexistent, auth required\n";
+	# fall through
     }
 
-    return OK
+    if ($auth_ssl_reqd) {
+        my $redir_to = "https://" . $r->hostname() . $r->unparsed_uri();
+        print STDERR "SoundSoftware.pm:$$: Need to switch to HTTPS, redirecting to $redir_to\n";
+        $r->headers_out->add('Location' => $redir_to);
+        return REDIRECT;
+    } else {
+        return OK;
+    }
 }
 
 sub authen_handler {
@@ -237,6 +291,16 @@
     
     print STDERR "SoundSoftware.pm:$$: User is " . $r->user . ", got password\n";
 
+    my $status = get_project_status($dbh, $project_id, $r);
+    if ($status == 0) {
+	# nonexistent, behave like private project you aren't a member of
+	print STDERR "SoundSoftware.pm:$$: Project doesn't exist, not permitted\n";
+	$dbh->disconnect();
+	undef $dbh;
+	$r->note_auth_failure();
+	return AUTH_REQUIRED;
+    }
+
     my $permitted = is_permitted($dbh, $project_id, $r->user, $redmine_pass, $r);
     
     $dbh->disconnect();
@@ -279,6 +343,30 @@
     $ret;
 }
 
+sub will_require_ssl_auth {
+    my $r = shift;
+
+    my $cfg = Apache2::Module::get_config
+        (__PACKAGE__, $r->server, $r->per_dir_config);
+
+    if ($cfg->{SoundSoftwareSslRequired} eq "on") {
+        if ($r->dir_config('HTTPS') eq "on") {
+            # already have ssl
+            return 0;
+        } else {
+            # require ssl for auth, don't have it yet
+            return 1;
+        }
+    } elsif ($cfg->{SoundSoftwareSslRequired} eq "off") {
+        # don't require ssl for auth
+        return 0;
+    } else {
+        print STDERR "WARNING: SoundSoftware.pm:$$: SoundSoftwareSslRequired should be either 'on' or 'off'\n";
+        # this is safer
+        return 1;
+    }
+}
+
 sub project_repo_is_readonly {
     my $dbh = shift;
     my $project_id = shift;
@@ -368,6 +456,7 @@
 		}
 		$sthldap->finish();
 		undef $sthldap;
+                last if ($ret);
 	    }
 	} else {
 	    print STDERR "SoundSoftware.pm:$$: User $redmine_user lacks required role for this project\n";
@@ -383,14 +472,13 @@
 sub get_project_identifier {
     my $dbh = shift;
     my $r = shift;
-
     my $location = $r->location;
-    my ($repo) = $r->uri =~ m{$location/*([^/]+)};
+    my ($repo) = $r->uri =~ m{$location/*([^/]*)};
 
     return $repo if (!$repo);
 
     $repo =~ s/[^a-zA-Z0-9\._-]//g;
-
+    
     # The original Redmine.pm returns the string just calculated as
     # the project identifier.  That won't do for us -- we may have
     # (and in fact already do have, in our test instance) projects
@@ -410,7 +498,6 @@
 
     my $prefix = $cfg->{SoundSoftwareRepoPrefix};
     if (!defined $prefix) { $prefix = '%/'; }
-
     my $identifier = '';
 
     $sth->execute($prefix . $repo);
@@ -449,6 +536,18 @@
     # to project identifier if any are found
     if ($name =~ m/[^\w\d\s\._-]/) {
 	$name = $project_id;
+    } elsif ($name =~ m/^\s*$/) {
+	# empty or whitespace
+	$name = $project_id;
+    }
+    
+    if ($name =~ m/^\s*$/) {
+        # nothing even in $project_id -- probably a nonexistent project.
+        # use repo name instead (don't want to admit to user that project
+        # doesn't exist)
+        my $location = $r->location;
+        my ($repo) = $r->uri =~ m{$location/*([^/]*)};
+        $name = $repo;
     }
 
     my $realm = '"Mercurial repository for ' . "'$name'" . '"';