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 @ 1586:d0d59d12db94

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