Redmine.pm

Derrick Rapp, 2011-04-21 01:48

Download (6.01 KB)

 
1

    
2
package Apache::Authn::Redmine2;
3

    
4
use strict;
5
use warnings FATAL => 'all', NONFATAL => 'redefine';
6

    
7
use DBI;
8
use Digest::SHA1;
9
use Authen::Simple::LDAP;
10
use Apache2::Module;
11
use Apache2::Access;
12
use Apache2::ServerRec qw();
13
use Apache2::RequestRec qw();
14
use Apache2::RequestUtil qw();
15
use Apache2::Const qw(:common :override :cmd_how);
16
use APR::Pool ();
17
use APR::Table ();
18

    
19
my %READ_ONLY_METHODS = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/;
20
my $DSN = "DBI:mysql:database=<enter database name>;host=localhost";
21
my $DB_USER = "<enter database username>";
22
my $DB_PASS = "<enter database password>";
23
my %AUTH_CACHE = ();
24

    
25
sub access_handler {
26
  return OK;
27
}
28

    
29
sub authen_handler {
30

    
31
  my $r = shift;
32

    
33
  unless ($r->some_auth_required) {
34
    $r->log_reason("No authentication has been configured");
35
    return FORBIDDEN;
36
  }
37

    
38
  my $project_id = get_project_identifier($r);
39
  my $is_read_only = is_read_only($r);
40
  
41
  my ($res, $redmine_pass) = $r->get_basic_auth_pw();
42
  my $redmine_user = $r->user;
43

    
44
  return $res unless $res == OK;
45

    
46
  return OK unless $project_id;
47
  return OK if is_cached($redmine_user, $redmine_pass, $project_id, $is_read_only);
48

    
49
  my $dbh = connect_database();
50

    
51
  my $is_public = is_public_project($dbh, $project_id);
52
  my $is_anonymous = is_anonymous($dbh);
53

    
54
  if ($is_public && $is_anonymous && $is_read_only) {
55
    $dbh->disconnect();
56
    cache($redmine_user, $redmine_pass, $project_id, $is_read_only);
57
    return OK; 
58
  }
59
  
60
  my $valid_credentials = are_valid_credentials($dbh, $redmine_user, $redmine_pass);
61

    
62
  unless ($valid_credentials) {
63
    $r->note_auth_failure();
64
    $r->log_reason("Invalid Credentials");
65
    $dbh->disconnect();
66
    return AUTH_REQUIRED;
67
  }
68

    
69
  if ($is_public && $is_read_only) {
70
    $dbh->disconnect();
71
    cache($redmine_user, $redmine_pass, $project_id, $is_read_only);
72
    return OK;
73
  }
74

    
75
  my $permissions = get_permissions($dbh, $redmine_user, $project_id);
76
  my $can_read = can_read($permissions);
77
  my $can_write = can_write($permissions);
78

    
79
  $dbh->disconnect();
80

    
81
  if ($is_read_only && $can_read || $can_write) {
82
    cache($redmine_user, $redmine_pass, $project_id, $is_read_only);
83
    return OK;
84
  }
85

    
86
  $r->log_reason("Forbidden request : " . $project_id . ", " . $is_read_only);
87
  return FORBIDDEN;
88
}
89

    
90
sub get_project_identifier {
91
  my $r = shift;
92
  
93
  my $location = $r->location;
94
  my ($identifier) = $r->uri =~ m{$location/*([^/]+)};
95

    
96
  return $identifier;
97
}
98

    
99
sub is_anonymous {
100
  my $dbh = shift;
101

    
102
  my $sth = $dbh->prepare(
103
    "SELECT value FROM settings WHERE settings.name='login_required';" 
104
  );
105

    
106
  $sth->execute();
107
  my @ret = $sth->fetchrow_array();
108
  $sth->finish();
109

    
110
  return $ret[0] == 0 ? 1 : 0;
111
}
112

    
113
sub is_read_only {
114
  my $r = shift;
115
  return defined $READ_ONLY_METHODS{$r->method};
116
}
117

    
118
sub is_public_project {
119
  my $dbh = shift;
120
  my $project_id = shift;
121

    
122
  my $sth = $dbh->prepare(
123
    "SELECT * FROM projects WHERE projects.identifier=? and projects.is_public=true;"
124
  );
125

    
126
  $sth->execute($project_id);
127
  my $ret = $sth->fetchrow_array ? 1 : 0;
128
  $sth->finish();
129

    
130
  return $ret;
131
}
132

    
133
sub are_valid_credentials {
134
  my $dbh = shift;
135
  my $redmine_user = shift;
136
  my $redmine_pass = shift;
137

    
138
  return 0 unless $redmine_user;
139

    
140
  my $pass_digest = Digest::SHA1::sha1_hex($redmine_pass);
141

    
142
  my $sth = $dbh->prepare(
143
    "SELECT hashed_password, auth_source_id FROM users WHERE users.login=? and users.status=1;"
144
  );
145

    
146
  $sth->execute($redmine_user);
147
  my ($hashed_password, $auth_source_id) = $sth->fetchrow_array;
148
  $sth->finish();
149

    
150
  return ldap_authenticate($dbh, $auth_source_id, $redmine_user, $redmine_pass) if $auth_source_id;
151

    
152
  return 0 unless $hashed_password;
153

    
154
  return ($pass_digest eq $hashed_password) ? 1 : 0;
155
}
156

    
157
sub ldap_authenticate {
158
  my $dbh = shift;
159
  my $auth_source_id = shift;
160
  my $username = shift;
161
  my $password = shift;
162

    
163
  my $sth = $dbh->prepare(
164
    "SELECT host,port,tls,account,account_password,base_dn,attr_login from auth_sources WHERE id = ?;"
165
  );
166

    
167
  $sth->execute($auth_source_id);
168
  my @rowldap = $sth->fetchrow_array;
169
  $sth->finish();
170
  
171
  my $ldap = Authen::Simple::LDAP->new(
172
    host => ($rowldap[2] == 1 || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]" : $rowldap[0],
173
    port => $rowldap[1],
174
    basedn => $rowldap[5],
175
    binddn => $rowldap[3] ? $rowldap[3] : "",
176
    bindpw => $rowldap[4] ? $rowldap[4] : "",
177
    filter => "(".$rowldap[6]."=%s)"
178
  );
179

    
180
  return 1 if $ldap->authenticate($username, $password);
181
  return 0;
182
}
183

    
184
sub get_permissions {
185
  my $dbh = shift;
186
  my $redmine_user = shift;
187
  my $project_id = shift;
188

    
189
  my $sth = $dbh->prepare(
190
    "SELECT r.permissions FROM projects p, users u, members m, roles r, member_roles mr
191
     WHERE p.identifier=? AND u.login=? AND m.project_id=p.id AND m.user_id=u.id AND mr.member_id = m.id AND r.id=mr.role_id
192
     UNION
193
     SELECT r.permissions FROM projects p, roles r
194
     WHERE p.identifier=? AND p.is_public=1 AND r.id=1;"
195
  );
196

    
197
  $sth->execute($project_id, $redmine_user, $project_id);
198
  my $permissions = $sth->fetchrow_array;
199
  $sth->finish();
200

    
201
  return $permissions;
202
}
203

    
204
sub can_read {
205
  my $permissions = shift;
206
  return 0 unless $permissions;
207
  return $permissions =~ /:browse_repository/ ? 1 : 0;
208
}
209

    
210
sub can_write {
211
  my $permissions = shift;
212
  return 0 unless $permissions;
213
  return $permissions =~ /:commit_access/ ? 1 : 0;
214
}
215

    
216
sub cache {
217
  my $redmine_user = shift;
218
  my $redmine_pass = shift;
219
  my $project_id = shift;
220
  my $is_read_only = shift;
221

    
222
  my $key = $redmine_user . $redmine_pass . $project_id . $is_read_only;
223

    
224
  $AUTH_CACHE{$key} = time();
225
}
226

    
227
sub is_cached {
228
  my $redmine_user = shift;
229
  my $redmine_pass = shift;
230
  my $project_id = shift;
231
  my $is_read_only = shift;
232

    
233
  my $key = $redmine_user . $redmine_pass . $project_id . $is_read_only;
234

    
235
  return 1 if exists($AUTH_CACHE{$key}) && $AUTH_CACHE{$key} >= time() - 60;
236
  return 0;
237
}
238

    
239
sub connect_database {
240
  return DBI->connect($DSN, $DB_USER, $DB_PASS);
241
}
242

    
243
1;