To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

Statistics Download as Zip
| Branch: | Tag: | Revision:

root / extra / soundsoftware / SoundSoftware.pm @ 1536:e2a3230f61fa

History | View | Annotate | Download (16.3 KB)

1 7:3c16ed8faa07 Chris
package Apache::Authn::SoundSoftware;
2
3
=head1 Apache::Authn::SoundSoftware
4
5
SoundSoftware - a mod_perl module for Apache authentication against a
6
Redmine database and optional LDAP implementing the access control
7
rules required for the SoundSoftware.ac.uk repository site.
8
9
=head1 SYNOPSIS
10
11
This module is closely based on the Redmine.pm authentication module
12
provided with Redmine.  It is intended to be used for authentication
13
in front of a repository service such as hgwebdir.
14
15
Requirements:
16
17
1. Clone/pull from repo for public project: Any user, no
18
authentication required
19
20
2. Clone/pull from repo for private project: Project members only
21
22
3. Push to repo for public project: "Permitted" users only (this
23 8:0c83d98252d9 Chris
probably means project members who are also identified in the hgrc web
24
section for the repository and so will be approved by hgwebdir?)
25 7:3c16ed8faa07 Chris
26 8:0c83d98252d9 Chris
4. Push to repo for private project: "Permitted" users only (as above)
27 7:3c16ed8faa07 Chris
28 300:034e9b00b341 chris
5. Push to any repo that is tracking an external repo: Refused always
29
30 7:3c16ed8faa07 Chris
=head1 INSTALLATION
31
32
Debian/ubuntu:
33
34
  apt-get install libapache-dbi-perl libapache2-mod-perl2 \
35
    libdbd-mysql-perl libauthen-simple-ldap-perl libio-socket-ssl-perl
36
37
Note that LDAP support is hardcoded "on" in this script (it is
38
optional in the original Redmine.pm).
39
40
=head1 CONFIGURATION
41
42
   ## This module has to be in your perl path
43
   ## eg:  /usr/local/lib/site_perl/Apache/Authn/SoundSoftware.pm
44
   PerlLoadModule Apache::Authn::SoundSoftware
45
46
   # Example when using hgwebdir
47
   ScriptAlias / "/var/hg/hgwebdir.cgi/"
48
49
   <Location />
50
       AuthName "Mercurial"
51
       AuthType Basic
52
       Require valid-user
53
       PerlAccessHandler Apache::Authn::SoundSoftware::access_handler
54
       PerlAuthenHandler Apache::Authn::SoundSoftware::authen_handler
55
       SoundSoftwareDSN "DBI:mysql:database=redmine;host=localhost"
56
       SoundSoftwareDbUser "redmine"
57
       SoundSoftwareDbPass "password"
58
       Options +ExecCGI
59
       AddHandler cgi-script .cgi
60
       ## Optional where clause (fulltext search would be slow and
61
       ## database dependant).
62
       # SoundSoftwareDbWhereClause "and members.role_id IN (1,2)"
63 8:0c83d98252d9 Chris
       ## Optional prefix for local repository URLs
64
       # SoundSoftwareRepoPrefix "/var/hg/"
65 7:3c16ed8faa07 Chris
  </Location>
66
67
See the original Redmine.pm for further configuration notes.
68
69
=cut
70
71
use strict;
72
use warnings FATAL => 'all', NONFATAL => 'redefine';
73
74
use DBI;
75 1331:1e9b1bdd062e Chris
use Digest::SHA;
76 7:3c16ed8faa07 Chris
use Authen::Simple::LDAP;
77
use Apache2::Module;
78
use Apache2::Access;
79
use Apache2::ServerRec qw();
80
use Apache2::RequestRec qw();
81
use Apache2::RequestUtil qw();
82
use Apache2::Const qw(:common :override :cmd_how);
83
use APR::Pool ();
84
use APR::Table ();
85
86
my @directives = (
87
  {
88
    name => 'SoundSoftwareDSN',
89
    req_override => OR_AUTHCFG,
90
    args_how => TAKE1,
91
    errmsg => 'Dsn in format used by Perl DBI. eg: "DBI:Pg:dbname=databasename;host=my.db.server"',
92
  },
93
  {
94
    name => 'SoundSoftwareDbUser',
95
    req_override => OR_AUTHCFG,
96
    args_how => TAKE1,
97
  },
98
  {
99
    name => 'SoundSoftwareDbPass',
100
    req_override => OR_AUTHCFG,
101
    args_how => TAKE1,
102
  },
103
  {
104
    name => 'SoundSoftwareDbWhereClause',
105
    req_override => OR_AUTHCFG,
106
    args_how => TAKE1,
107
  },
108
  {
109 8:0c83d98252d9 Chris
    name => 'SoundSoftwareRepoPrefix',
110 7:3c16ed8faa07 Chris
    req_override => OR_AUTHCFG,
111
    args_how => TAKE1,
112
  },
113 732:897bc2b63bfe Chris
  {
114
    name => 'SoundSoftwareSslRequired',
115
    req_override => OR_AUTHCFG,
116
    args_how => TAKE1,
117
  },
118 7:3c16ed8faa07 Chris
);
119
120
sub SoundSoftwareDSN {
121 8:0c83d98252d9 Chris
    my ($self, $parms, $arg) = @_;
122
    $self->{SoundSoftwareDSN} = $arg;
123
    my $query = "SELECT
124 301:6d3f8aeb51b7 chris
                 hashed_password, salt, auth_source_id, permissions
125 7:3c16ed8faa07 Chris
              FROM members, projects, users, roles, member_roles
126
              WHERE
127
                projects.id=members.project_id
128
                AND member_roles.member_id=members.id
129
                AND users.id=members.user_id
130
                AND roles.id=member_roles.role_id
131
                AND users.status=1
132
                AND login=?
133
                AND identifier=? ";
134 8:0c83d98252d9 Chris
    $self->{SoundSoftwareQuery} = trim($query);
135 7:3c16ed8faa07 Chris
}
136
137
sub SoundSoftwareDbUser { set_val('SoundSoftwareDbUser', @_); }
138
sub SoundSoftwareDbPass { set_val('SoundSoftwareDbPass', @_); }
139
sub SoundSoftwareDbWhereClause {
140 8:0c83d98252d9 Chris
    my ($self, $parms, $arg) = @_;
141
    $self->{SoundSoftwareQuery} = trim($self->{SoundSoftwareQuery}.($arg ? $arg : "")." ");
142 7:3c16ed8faa07 Chris
}
143
144 8:0c83d98252d9 Chris
sub SoundSoftwareRepoPrefix {
145
    my ($self, $parms, $arg) = @_;
146
    if ($arg) {
147
	$self->{SoundSoftwareRepoPrefix} = $arg;
148
    }
149 7:3c16ed8faa07 Chris
}
150
151 732:897bc2b63bfe Chris
sub SoundSoftwareSslRequired { set_val('SoundSoftwareSslRequired', @_); }
152
153 7:3c16ed8faa07 Chris
sub trim {
154 8:0c83d98252d9 Chris
    my $string = shift;
155
    $string =~ s/\s{2,}/ /g;
156
    return $string;
157 7:3c16ed8faa07 Chris
}
158
159
sub set_val {
160 8:0c83d98252d9 Chris
    my ($key, $self, $parms, $arg) = @_;
161
    $self->{$key} = $arg;
162 7:3c16ed8faa07 Chris
}
163
164
Apache2::Module::add(__PACKAGE__, \@directives);
165
166
167
my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/;
168
169
sub access_handler {
170 8:0c83d98252d9 Chris
    my $r = shift;
171 7:3c16ed8faa07 Chris
172 517:bd1d512f9e1b Chris
    print STDERR "SoundSoftware.pm:$$: In access handler at " . scalar localtime() . "\n";
173 7:3c16ed8faa07 Chris
174 8:0c83d98252d9 Chris
    unless ($r->some_auth_required) {
175
	$r->log_reason("No authentication has been configured");
176
	return FORBIDDEN;
177
    }
178 7:3c16ed8faa07 Chris
179 8:0c83d98252d9 Chris
    my $method = $r->method;
180 7:3c16ed8faa07 Chris
181 517:bd1d512f9e1b Chris
    print STDERR "SoundSoftware.pm:$$: Method: $method, uri " . $r->uri . ", location " . $r->location . "\n";
182
    print STDERR "SoundSoftware.pm:$$: Accept: " . $r->headers_in->{Accept} . "\n";
183 7:3c16ed8faa07 Chris
184 8:0c83d98252d9 Chris
    my $dbh = connect_database($r);
185 152:a389c77da9fd Chris
    unless ($dbh) {
186 517:bd1d512f9e1b Chris
	print STDERR "SoundSoftware.pm:$$: Database connection failed!: " . $DBI::errstr . "\n";
187 152:a389c77da9fd Chris
	return FORBIDDEN;
188
    }
189
190 300:034e9b00b341 chris
    print STDERR "Connected to db, dbh is " . $dbh . "\n";
191 7:3c16ed8faa07 Chris
192 8:0c83d98252d9 Chris
    my $project_id = get_project_identifier($dbh, $r);
193 300:034e9b00b341 chris
194 732:897bc2b63bfe Chris
    # We want to delegate most of the work to the authentication
195
    # handler (to ensure that user is asked to login even for
196
    # nonexistent projects -- so they can't tell whether a private
197
    # project exists or not without authenticating). So
198
    #
199
    # * if the project is public
200
    #   - if the method is read-only
201
    #     + set handler to OK, no auth needed
202
    #   - if the method is not read-only
203
    #     + if the repo is read-only, return forbidden
204
    #     + else require auth
205
    # * if the project is not public or does not exist
206
    #     + require auth
207
    #
208
    # If we are requiring auth and are not currently https, and
209
    # https is required, then we must return a redirect to https
210
    # instead of an OK.
211 300:034e9b00b341 chris
212 8:0c83d98252d9 Chris
    my $status = get_project_status($dbh, $project_id, $r);
213 732:897bc2b63bfe Chris
    my $readonly = project_repo_is_readonly($dbh, $project_id, $r);
214 7:3c16ed8faa07 Chris
215 8:0c83d98252d9 Chris
    $dbh->disconnect();
216
    undef $dbh;
217 7:3c16ed8faa07 Chris
218 734:1d1b8170c2f7 Chris
    my $auth_ssl_reqd = will_require_ssl_auth($r);
219
220 732:897bc2b63bfe Chris
    if ($status == 1) { # public
221
222
	print STDERR "SoundSoftware.pm:$$: Project is public\n";
223
224
	if (!defined $read_only_methods{$method}) {
225
226
	    print STDERR "SoundSoftware.pm:$$: Method is not read-only\n";
227
228
	    if ($readonly) {
229
		print STDERR "SoundSoftware.pm:$$: Project repo is read-only, refusing access\n";
230
		return FORBIDDEN;
231
	    } else {
232
		print STDERR "SoundSoftware.pm:$$: Project repo is read-write, auth required\n";
233
		# fall through, this is the normal case
234
	    }
235
236 734:1d1b8170c2f7 Chris
        } elsif ($auth_ssl_reqd and $r->unparsed_uri =~ m/cmd=branchmap/) {
237
238
            # A hac^H^H^Hspecial case. We want to ensure we switch to
239
            # https (if it will be necessarily for authentication)
240
            # before the first POST request, and this is what I think
241
            # will give us suitable warning for Mercurial.
242
243
            print STDERR "SoundSoftware.pm:$$: Switching to HTTPS in preparation\n";
244
            # fall through, this is the normal case
245
246 732:897bc2b63bfe Chris
	} else {
247
	    # Public project, read-only method -- this is the only
248
	    # case we can decide for certain to accept in this function
249
	    print STDERR "SoundSoftware.pm:$$: Method is read-only, no restriction here\n";
250
	    $r->set_handlers(PerlAuthenHandler => [\&OK]);
251
	    return OK;
252
	}
253
254
    } else { # status != 1, i.e. nonexistent or private -- equivalent here
255
256
	print STDERR "SoundSoftware.pm:$$: Project is private or nonexistent, auth required\n";
257
	# fall through
258 8:0c83d98252d9 Chris
    }
259 7:3c16ed8faa07 Chris
260 734:1d1b8170c2f7 Chris
    if ($auth_ssl_reqd) {
261
        my $redir_to = "https://" . $r->hostname() . $r->unparsed_uri();
262
        print STDERR "SoundSoftware.pm:$$: Need to switch to HTTPS, redirecting to $redir_to\n";
263
        $r->headers_out->add('Location' => $redir_to);
264
        return REDIRECT;
265 732:897bc2b63bfe Chris
    } else {
266 734:1d1b8170c2f7 Chris
        return OK;
267 732:897bc2b63bfe Chris
    }
268 7:3c16ed8faa07 Chris
}
269
270
sub authen_handler {
271 8:0c83d98252d9 Chris
    my $r = shift;
272
273 517:bd1d512f9e1b Chris
    print STDERR "SoundSoftware.pm:$$: In authentication handler at " . scalar localtime() . "\n";
274 7:3c16ed8faa07 Chris
275 8:0c83d98252d9 Chris
    my $dbh = connect_database($r);
276 152:a389c77da9fd Chris
    unless ($dbh) {
277 517:bd1d512f9e1b Chris
        print STDERR "SoundSoftware.pm:$$: Database connection failed!: " . $DBI::errstr . "\n";
278 152:a389c77da9fd Chris
        return AUTH_REQUIRED;
279
    }
280 8:0c83d98252d9 Chris
281
    my $project_id = get_project_identifier($dbh, $r);
282
    my $realm = get_realm($dbh, $project_id, $r);
283
    $r->auth_name($realm);
284
285
    my ($res, $redmine_pass) =  $r->get_basic_auth_pw();
286
    unless ($res == OK) {
287
	$dbh->disconnect();
288
	undef $dbh;
289
	return $res;
290
    }
291
292 517:bd1d512f9e1b Chris
    print STDERR "SoundSoftware.pm:$$: User is " . $r->user . ", got password\n";
293 8:0c83d98252d9 Chris
294 732:897bc2b63bfe Chris
    my $status = get_project_status($dbh, $project_id, $r);
295
    if ($status == 0) {
296
	# nonexistent, behave like private project you aren't a member of
297
	print STDERR "SoundSoftware.pm:$$: Project doesn't exist, not permitted\n";
298
	$dbh->disconnect();
299
	undef $dbh;
300
	$r->note_auth_failure();
301
	return AUTH_REQUIRED;
302
    }
303
304 8:0c83d98252d9 Chris
    my $permitted = is_permitted($dbh, $project_id, $r->user, $redmine_pass, $r);
305
306
    $dbh->disconnect();
307
    undef $dbh;
308
309
    if ($permitted) {
310
	return OK;
311
    } else {
312 517:bd1d512f9e1b Chris
	print STDERR "SoundSoftware.pm:$$: Not permitted\n";
313 8:0c83d98252d9 Chris
	$r->note_auth_failure();
314
	return AUTH_REQUIRED;
315
    }
316 7:3c16ed8faa07 Chris
}
317
318
sub get_project_status {
319 8:0c83d98252d9 Chris
    my $dbh = shift;
320 7:3c16ed8faa07 Chris
    my $project_id = shift;
321
    my $r = shift;
322 8:0c83d98252d9 Chris
323
    if (!defined $project_id or $project_id eq '') {
324
	return 0; # nonexistent
325
    }
326 7:3c16ed8faa07 Chris
327
    my $sth = $dbh->prepare(
328
        "SELECT is_public FROM projects WHERE projects.identifier = ?;"
329
    );
330
331
    $sth->execute($project_id);
332 8:0c83d98252d9 Chris
    my $ret = 0; # nonexistent
333 7:3c16ed8faa07 Chris
    if (my @row = $sth->fetchrow_array) {
334
    	if ($row[0] eq "1" || $row[0] eq "t") {
335
	    $ret = 1; # public
336
    	} else {
337 8:0c83d98252d9 Chris
	    $ret = 2; # private
338 7:3c16ed8faa07 Chris
	}
339
    }
340
    $sth->finish();
341
    undef $sth;
342
343
    $ret;
344
}
345
346 734:1d1b8170c2f7 Chris
sub will_require_ssl_auth {
347
    my $r = shift;
348
349
    my $cfg = Apache2::Module::get_config
350
        (__PACKAGE__, $r->server, $r->per_dir_config);
351
352
    if ($cfg->{SoundSoftwareSslRequired} eq "on") {
353
        if ($r->dir_config('HTTPS') eq "on") {
354
            # already have ssl
355
            return 0;
356
        } else {
357
            # require ssl for auth, don't have it yet
358
            return 1;
359
        }
360
    } elsif ($cfg->{SoundSoftwareSslRequired} eq "off") {
361
        # don't require ssl for auth
362
        return 0;
363
    } else {
364
        print STDERR "WARNING: SoundSoftware.pm:$$: SoundSoftwareSslRequired should be either 'on' or 'off'\n";
365
        # this is safer
366
        return 1;
367
    }
368
}
369
370 300:034e9b00b341 chris
sub project_repo_is_readonly {
371
    my $dbh = shift;
372
    my $project_id = shift;
373
    my $r = shift;
374
375
    if (!defined $project_id or $project_id eq '') {
376
        return 0; # nonexistent
377
    }
378
379
    my $sth = $dbh->prepare(
380
        "SELECT repositories.is_external FROM repositories, projects WHERE projects.identifier = ? AND repositories.project_id = projects.id;"
381
    );
382
383
    $sth->execute($project_id);
384
    my $ret = 0; # nonexistent
385
    if (my @row = $sth->fetchrow_array) {
386 301:6d3f8aeb51b7 chris
        if (defined($row[0]) && ($row[0] eq "1" || $row[0] eq "t")) {
387 300:034e9b00b341 chris
            $ret = 1; # read-only (i.e. external)
388
        } else {
389
            $ret = 0; # read-write
390
        }
391
    }
392
    $sth->finish();
393
    undef $sth;
394
395
    $ret;
396
}
397
398 8:0c83d98252d9 Chris
sub is_permitted {
399
    my $dbh = shift;
400
    my $project_id = shift;
401
    my $redmine_user = shift;
402
    my $redmine_pass = shift;
403
    my $r = shift;
404 7:3c16ed8faa07 Chris
405 1331:1e9b1bdd062e Chris
    my $pass_digest = Digest::SHA::sha1_hex($redmine_pass);
406 7:3c16ed8faa07 Chris
407 8:0c83d98252d9 Chris
    my $cfg = Apache2::Module::get_config
408
	(__PACKAGE__, $r->server, $r->per_dir_config);
409 7:3c16ed8faa07 Chris
410 8:0c83d98252d9 Chris
    my $query = $cfg->{SoundSoftwareQuery};
411
    my $sth = $dbh->prepare($query);
412
    $sth->execute($redmine_user, $project_id);
413 7:3c16ed8faa07 Chris
414 8:0c83d98252d9 Chris
    my $ret;
415 301:6d3f8aeb51b7 chris
    while (my ($hashed_password, $salt, $auth_source_id, $permissions) = $sth->fetchrow_array) {
416 7:3c16ed8faa07 Chris
417 8:0c83d98252d9 Chris
	# Test permissions for this user before we verify credentials
418
	# -- if the user is not permitted this action anyway, there's
419
	# not much point in e.g. contacting the LDAP
420 7:3c16ed8faa07 Chris
421 8:0c83d98252d9 Chris
	my $method = $r->method;
422 7:3c16ed8faa07 Chris
423 8:0c83d98252d9 Chris
	if ((defined $read_only_methods{$method} && $permissions =~ /:browse_repository/)
424
	    || $permissions =~ /:commit_access/) {
425
426
	    # User would be permitted this action, if their
427
	    # credentials checked out -- test those now
428
429
	    print STDERR "SoundSoftware.pm: User $redmine_user has required role, checking credentials\n";
430
431
	    unless ($auth_source_id) {
432 1331:1e9b1bdd062e Chris
                my $salted_password = Digest::SHA::sha1_hex($salt.$pass_digest);
433 301:6d3f8aeb51b7 chris
		if ($hashed_password eq $salted_password) {
434 8:0c83d98252d9 Chris
		    print STDERR "SoundSoftware.pm: User $redmine_user authenticated via password\n";
435
		    $ret = 1;
436
		    last;
437
		}
438
	    } else {
439
		my $sthldap = $dbh->prepare(
440
		    "SELECT host,port,tls,account,account_password,base_dn,attr_login FROM auth_sources WHERE id = ?;"
441
		    );
442
		$sthldap->execute($auth_source_id);
443
		while (my @rowldap = $sthldap->fetchrow_array) {
444
		    my $ldap = Authen::Simple::LDAP->new(
445
			host    => ($rowldap[2] eq "1" || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]" : $rowldap[0],
446
			port    => $rowldap[1],
447
			basedn  => $rowldap[5],
448
			binddn  => $rowldap[3] ? $rowldap[3] : "",
449
			bindpw  => $rowldap[4] ? $rowldap[4] : "",
450
			filter  => "(".$rowldap[6]."=%s)"
451
			);
452
		    if ($ldap->authenticate($redmine_user, $redmine_pass)) {
453 517:bd1d512f9e1b Chris
			print STDERR "SoundSoftware.pm:$$: User $redmine_user authenticated via LDAP\n";
454 8:0c83d98252d9 Chris
			$ret = 1;
455
		    }
456
		}
457
		$sthldap->finish();
458
		undef $sthldap;
459 735:8653bddf26a6 Chris
                last if ($ret);
460 8:0c83d98252d9 Chris
	    }
461
	} else {
462 517:bd1d512f9e1b Chris
	    print STDERR "SoundSoftware.pm:$$: User $redmine_user lacks required role for this project\n";
463 8:0c83d98252d9 Chris
	}
464 7:3c16ed8faa07 Chris
    }
465
466 8:0c83d98252d9 Chris
    $sth->finish();
467
    undef $sth;
468
469
    $ret;
470 7:3c16ed8faa07 Chris
}
471
472
sub get_project_identifier {
473 8:0c83d98252d9 Chris
    my $dbh = shift;
474 7:3c16ed8faa07 Chris
    my $r = shift;
475
    my $location = $r->location;
476 737:1ce6efe3db0e Chris
    my ($repo) = $r->uri =~ m{$location/*([^/]*)};
477 10:2c10dc5f122d Chris
478
    return $repo if (!$repo);
479
480 7:3c16ed8faa07 Chris
    $repo =~ s/[^a-zA-Z0-9\._-]//g;
481 736:51c97efbe241 Chris
482 8:0c83d98252d9 Chris
    # The original Redmine.pm returns the string just calculated as
483
    # the project identifier.  That won't do for us -- we may have
484
    # (and in fact already do have, in our test instance) projects
485
    # whose repository names differ from the project identifiers.
486
487
    # This is a rather fundamental change because it means that almost
488
    # every request needs more than one database query -- which
489
    # prompts us to start passing around $dbh instead of connecting
490
    # locally within each function as is done in Redmine.pm.
491
492 7:3c16ed8faa07 Chris
    my $sth = $dbh->prepare(
493
        "SELECT projects.identifier FROM projects, repositories WHERE repositories.project_id = projects.id AND repositories.url LIKE ?;"
494
    );
495
496 8:0c83d98252d9 Chris
    my $cfg = Apache2::Module::get_config
497
	(__PACKAGE__, $r->server, $r->per_dir_config);
498
499
    my $prefix = $cfg->{SoundSoftwareRepoPrefix};
500
    if (!defined $prefix) { $prefix = '%/'; }
501 7:3c16ed8faa07 Chris
    my $identifier = '';
502
503 8:0c83d98252d9 Chris
    $sth->execute($prefix . $repo);
504 7:3c16ed8faa07 Chris
    my $ret = 0;
505
    if (my @row = $sth->fetchrow_array) {
506
	$identifier = $row[0];
507
    }
508
    $sth->finish();
509
    undef $sth;
510
511 517:bd1d512f9e1b Chris
    print STDERR "SoundSoftware.pm:$$: Repository '$repo' belongs to project '$identifier'\n";
512 7:3c16ed8faa07 Chris
513
    $identifier;
514
}
515
516 8:0c83d98252d9 Chris
sub get_realm {
517
    my $dbh = shift;
518
    my $project_id = shift;
519
    my $r = shift;
520
521
    my $sth = $dbh->prepare(
522
        "SELECT projects.name FROM projects WHERE projects.identifier = ?;"
523
    );
524
525
    my $name = $project_id;
526
527
    $sth->execute($project_id);
528
    my $ret = 0;
529
    if (my @row = $sth->fetchrow_array) {
530
	$name = $row[0];
531
    }
532
    $sth->finish();
533
    undef $sth;
534
535
    # be timid about characters not permitted in auth realm and revert
536
    # to project identifier if any are found
537
    if ($name =~ m/[^\w\d\s\._-]/) {
538
	$name = $project_id;
539 733:c7a731db96e5 Chris
    } elsif ($name =~ m/^\s*$/) {
540
	# empty or whitespace
541
	$name = $project_id;
542
    }
543
544
    if ($name =~ m/^\s*$/) {
545
        # nothing even in $project_id -- probably a nonexistent project.
546
        # use repo name instead (don't want to admit to user that project
547
        # doesn't exist)
548
        my $location = $r->location;
549 737:1ce6efe3db0e Chris
        my ($repo) = $r->uri =~ m{$location/*([^/]*)};
550 733:c7a731db96e5 Chris
        $name = $repo;
551 8:0c83d98252d9 Chris
    }
552
553 1271:cf4cc816278a Chris
#    my $realm = '"Mercurial repository for ' . "'$name'" . '"';
554
# see #577:
555
    my $realm = '"Mercurial repository for ' . "$name" . '"';
556 8:0c83d98252d9 Chris
557
    $realm;
558
}
559
560 7:3c16ed8faa07 Chris
sub connect_database {
561
    my $r = shift;
562
563 8:0c83d98252d9 Chris
    my $cfg = Apache2::Module::get_config
564
	(__PACKAGE__, $r->server, $r->per_dir_config);
565
566
    return DBI->connect($cfg->{SoundSoftwareDSN},
567 152:a389c77da9fd Chris
	                $cfg->{SoundSoftwareDbUser},
568
		        $cfg->{SoundSoftwareDbPass});
569 7:3c16ed8faa07 Chris
}
570
571
1;