Project

General

Profile

Defect #879 » Redmine.pm

New version (with LDAP support) - Liwiusz Ociepa, 2008-03-17 16:04

 
1
package Apache::Authn::Redmine;
2

    
3
=head1 Apache::Authn::Redmine
4

    
5
Redmine - a mod_perl module to authenticate webdav subversion users
6
against redmine database
7

    
8
=head1 SYNOPSIS
9

    
10
This module allow anonymous users to browse public project and
11
registred users to browse and commit their project. authentication is
12
done on the redmine database.
13

    
14
This method is far simpler than the one with pam_* and works with all
15
database without an hassle but you need to have apache/mod_perl on the
16
svn server.
17

    
18
=head1 INSTALLATION
19

    
20
For this to automagically work, you need to have a recent reposman.rb
21
(after r860) and if you already use reposman, read the last section to
22
migrate.
23

    
24
Sorry ruby users but you need some perl modules, at least mod_perl2,
25
DBI and DBD::mysql (or the DBD driver for you database as it should
26
work on allmost all databases).
27

    
28
On debian/ubuntu you must do :
29

    
30
  aptitude install libapache-dbi-perl libapache2-mod-perl2 libdbd-mysql-perl
31

    
32
=head1 CONFIGURATION
33

    
34
   ## if the module isn't in your perl path
35
   PerlRequire /usr/local/apache/Redmine.pm
36
   ## else
37
   # PerlModule Apache::Authn::Redmine
38
   <Location /svn>
39
     DAV svn
40
     SVNParentPath "/var/svn"
41

    
42
     AuthType Basic
43
     AuthName redmine
44
     Require valid-user
45

    
46
     PerlAccessHandler Apache::Authn::Redmine::access_handler
47
     PerlAuthenHandler Apache::Authn::Redmine::authen_handler
48
  
49
     ## for mysql
50
     PerlSetVar dsn DBI:mysql:database=databasename;host=my.db.server
51
     ## for postgres
52
     # PerlSetVar dsn DBI:Pg:dbname=databasename;host=my.db.server
53

    
54
     PerlSetVar db_user redmine
55
     PerlSetVar db_pass password
56
  </Location>
57

    
58
To be able to browse repository inside redmine, you must add something
59
like that :
60

    
61
   <Location /svn-private>
62
     DAV svn
63
     SVNParentPath "/var/svn"
64
     Order deny,allow
65
     Deny from all
66
     # only allow reading orders
67
     <Limit GET PROPFIND OPTIONS REPORT>
68
       Allow from redmine.server.ip
69
     </Limit>
70
   </Location>
71

    
72
and you will have to use this reposman.rb command line to create repository :
73

    
74
  reposman.rb --redmine my.redmine.server --svn-dir /var/svn --owner www-data -u http://svn.server/svn-private/
75

    
76
=head1 MIGRATION FROM OLDER RELEASES
77

    
78
If you use an older reposman.rb (r860 or before), you need to change
79
rights on repositories to allow the apache user to read and write
80
S<them :>
81

    
82
  sudo chown -R www-data /var/svn/*
83
  sudo chmod -R u+w /var/svn/*
84

    
85
And you need to upgrade at least reposman.rb (after r860).
86

    
87
=cut
88

    
89
use strict;
90

    
91
use DBI;
92
use Digest::SHA1;
93
use Net::LDAP;
94

    
95
use Apache2::Module;
96
use Apache2::Access;
97
use Apache2::ServerRec qw();
98
use Apache2::RequestRec qw();
99
use Apache2::RequestUtil qw();
100
use Apache2::Const qw(:common);
101
# use Apache2::Directive qw();
102

    
103
my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/;
104

    
105
sub access_handler {
106
  my $r = shift;
107

    
108
  unless ($r->some_auth_required) {
109
      $r->log_reason("No authentication has been configured");
110
      return FORBIDDEN;
111
  }
112

    
113
  my $method = $r->method;
114
  return OK unless 1 == $read_only_methods{$method};
115

    
116
  my $project_id = get_project_identifier($r);
117

    
118
  $r->set_handlers(PerlAuthenHandler => [\&OK])
119
      if is_public_project($project_id, $r);
120

    
121
  return OK
122
}
123

    
124
sub authen_handler {
125
  my $r = shift;
126
  
127
  my ($res, $redmine_pass) =  $r->get_basic_auth_pw();
128
  return $res unless $res == OK;
129
  
130
  if (is_member($r->user, $redmine_pass, $r)) {
131
      return OK;
132
  } else {
133
      $r->note_auth_failure();
134
      return AUTH_REQUIRED;
135
  }
136
}
137

    
138
sub is_public_project {
139
    my $project_id = shift;
140
    my $r = shift;
141

    
142
    my $dbh = connect_database($r);
143
    my $sth = $dbh->prepare(
144
        "SELECT * FROM projects WHERE projects.identifier=? and projects.is_public=true;"
145
    );
146

    
147
    $sth->execute($project_id);
148
    my $ret = $sth->fetchrow_array ? 1 : 0;
149
    $dbh->disconnect();
150

    
151
    $ret;
152
}
153

    
154
# perhaps we should use repository right (other read right) to check public access.
155
# it could be faster BUT it doesn't work for the moment.
156
# sub is_public_project_by_file {
157
#     my $project_id = shift;
158
#     my $r = shift;
159

    
160
#     my $tree = Apache2::Directive::conftree();
161
#     my $node = $tree->lookup('Location', $r->location);
162
#     my $hash = $node->as_hash;
163

    
164
#     my $svnparentpath = $hash->{SVNParentPath};
165
#     my $repos_path = $svnparentpath . "/" . $project_id;
166
#     return 1 if (stat($repos_path))[2] & 00007;
167
# }
168

    
169
sub is_member {
170
  my $redmine_user = shift;
171
  my $redmine_pass = shift;
172
  my $r = shift;
173

    
174
  my $dbh         = connect_database($r);
175
  my $project_id  = get_project_identifier($r);
176

    
177
  my $pass_digest = Digest::SHA1::sha1_hex($redmine_pass);
178

    
179
  my $sth = $dbh->prepare(
180
      "SELECT hashed_password,coalesce(auth_source_id,0) FROM members, projects, users WHERE projects.id=members.project_id AND users.id=members.user_id AND users.status=1 AND login=? AND identifier=?;"
181
  );
182
  $sth->execute($redmine_user, $project_id);
183

    
184
  my $ret;
185
  while (my @row = $sth->fetchrow_array) {
186
      if ($row[1] eq 0) {
187
          if ($row[0] eq $pass_digest) {
188
              $ret = 1;
189
              last;
190
          }
191
      } else {
192
          my $sthldap = $dbh->prepare(
193
              "SELECT host,port,account,account_password,base_dn,attr_login from auth_sources WHERE id = ?;"
194
          );
195
          $sthldap->execute($row[1]);
196
          while (my @rowldap = $sthldap->fetchrow_array) {
197
              my $ldap = Net::LDAP->new($rowldap[0], port => $rowldap[1]);
198
              my $res = $rowldap[2] ? $ldap->bind($rowldap[2], password => $rowldap[3]) : $ldap->bind();
199
 
200
              unless ($res->code) {
201
                  my $res = $ldap->search(
202
                      base => $rowldap[4],
203
                      filter => "(".$rowldap[5]."=".$redmine_user.")",
204
                      attrs => ['dn']
205
                  );
206
                  unless ($res->code) { 
207
                      foreach my $entry ($res->entries) { 
208
                          my $mesg = $ldap->bind($entry->dn, password => $redmine_pass);
209
                          $ret = 1 and last unless $mesg->code;
210
                      }
211
                  }
212
              }
213
              $res = $ldap->unbind();
214
              $ldap->disconnect();
215
          }
216
          $sthldap->finish();
217
      }
218
  }
219
  $sth->finish();
220
  $dbh->disconnect();
221

    
222
  $ret;
223
}
224

    
225
sub get_project_identifier {
226
    my $r = shift;
227
    
228
    my $location = $r->location;
229
    my ($identifier) = $r->uri =~ m{$location/*([^/]+)};
230
    $identifier;
231
}
232

    
233
sub connect_database {
234
    my $r = shift;
235

    
236
    my ($dsn, $db_user, $db_pass) = map { $r->dir_config($_) } qw/dsn db_user db_pass/;
237
    return DBI->connect($dsn, $db_user, $db_pass);
238
}
239

    
240
1;
(1-1/6)