##// END OF EJS Templates
Merged r3246 and r3247 from trunk....
Jean-Philippe Lang -
r3134:718cd596e061
parent child
Show More
@@ -1,317 +1,316
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class MailHandler < ActionMailer::Base
19 19 include ActionView::Helpers::SanitizeHelper
20 20
21 21 class UnauthorizedAction < StandardError; end
22 22 class MissingInformation < StandardError; end
23 23
24 24 attr_reader :email, :user
25 25
26 26 def self.receive(email, options={})
27 27 @@handler_options = options.dup
28 28
29 29 @@handler_options[:issue] ||= {}
30 30
31 31 @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) if @@handler_options[:allow_override].is_a?(String)
32 32 @@handler_options[:allow_override] ||= []
33 33 # Project needs to be overridable if not specified
34 34 @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project)
35 35 # Status overridable by default
36 36 @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status)
37 37
38 38 @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1' ? true : false)
39 39 super email
40 40 end
41 41
42 42 # Processes incoming emails
43 43 # Returns the created object (eg. an issue, a message) or false
44 44 def receive(email)
45 45 @email = email
46 46 sender_email = email.from.to_a.first.to_s.strip
47 47 # Ignore emails received from the application emission address to avoid hell cycles
48 48 if sender_email.downcase == Setting.mail_from.to_s.strip.downcase
49 49 logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]" if logger && logger.info
50 50 return false
51 51 end
52 52 @user = User.find_by_mail(sender_email)
53 53 if @user && !@user.active?
54 54 logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]" if logger && logger.info
55 55 return false
56 56 end
57 57 if @user.nil?
58 58 # Email was submitted by an unknown user
59 59 case @@handler_options[:unknown_user]
60 60 when 'accept'
61 61 @user = User.anonymous
62 62 when 'create'
63 63 @user = MailHandler.create_user_from_email(email)
64 64 if @user
65 65 logger.info "MailHandler: [#{@user.login}] account created" if logger && logger.info
66 66 Mailer.deliver_account_information(@user, @user.password)
67 67 else
68 68 logger.error "MailHandler: could not create account for [#{sender_email}]" if logger && logger.error
69 69 return false
70 70 end
71 71 else
72 72 # Default behaviour, emails from unknown users are ignored
73 73 logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]" if logger && logger.info
74 74 return false
75 75 end
76 76 end
77 77 User.current = @user
78 78 dispatch
79 79 end
80 80
81 81 private
82 82
83 83 MESSAGE_ID_RE = %r{^<redmine\.([a-z0-9_]+)\-(\d+)\.\d+@}
84 84 ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]*#(\d+)\]}
85 85 MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]*msg(\d+)\]}
86 86
87 87 def dispatch
88 88 headers = [email.in_reply_to, email.references].flatten.compact
89 89 if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
90 90 klass, object_id = $1, $2.to_i
91 91 method_name = "receive_#{klass}_reply"
92 92 if self.class.private_instance_methods.collect(&:to_s).include?(method_name)
93 93 send method_name, object_id
94 94 else
95 95 # ignoring it
96 96 end
97 97 elsif m = email.subject.match(ISSUE_REPLY_SUBJECT_RE)
98 98 receive_issue_reply(m[1].to_i)
99 99 elsif m = email.subject.match(MESSAGE_REPLY_SUBJECT_RE)
100 100 receive_message_reply(m[1].to_i)
101 101 else
102 102 receive_issue
103 103 end
104 104 rescue ActiveRecord::RecordInvalid => e
105 105 # TODO: send a email to the user
106 106 logger.error e.message if logger
107 107 false
108 108 rescue MissingInformation => e
109 109 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
110 110 false
111 111 rescue UnauthorizedAction => e
112 112 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
113 113 false
114 114 end
115 115
116 116 # Creates a new issue
117 117 def receive_issue
118 118 project = target_project
119 119 tracker = (get_keyword(:tracker) && project.trackers.find_by_name(get_keyword(:tracker))) || project.trackers.find(:first)
120 120 category = (get_keyword(:category) && project.issue_categories.find_by_name(get_keyword(:category)))
121 121 priority = (get_keyword(:priority) && IssuePriority.find_by_name(get_keyword(:priority)))
122 122 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
123 123
124 124 # check permission
125 125 unless @@handler_options[:no_permission_check]
126 126 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
127 127 end
128 128
129 129 issue = Issue.new(:author => user, :project => project, :tracker => tracker, :category => category, :priority => priority)
130 130 # check workflow
131 131 if status && issue.new_statuses_allowed_to(user).include?(status)
132 132 issue.status = status
133 133 end
134 134 issue.subject = email.subject.chomp
135 issue.subject = issue.subject.toutf8 if issue.subject.respond_to?(:toutf8)
136 135 if issue.subject.blank?
137 136 issue.subject = '(no subject)'
138 137 end
139 138 # custom fields
140 139 issue.custom_field_values = issue.available_custom_fields.inject({}) do |h, c|
141 140 if value = get_keyword(c.name, :override => true)
142 141 h[c.id] = value
143 142 end
144 143 h
145 144 end
146 145 issue.description = cleaned_up_text_body
147 146 # add To and Cc as watchers before saving so the watchers can reply to Redmine
148 147 add_watchers(issue)
149 148 issue.save!
150 149 add_attachments(issue)
151 150 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
152 151 issue
153 152 end
154 153
155 154 def target_project
156 155 # TODO: other ways to specify project:
157 156 # * parse the email To field
158 157 # * specific project (eg. Setting.mail_handler_target_project)
159 158 target = Project.find_by_identifier(get_keyword(:project))
160 159 raise MissingInformation.new('Unable to determine target project') if target.nil?
161 160 target
162 161 end
163 162
164 163 # Adds a note to an existing issue
165 164 def receive_issue_reply(issue_id)
166 165 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
167 166
168 167 issue = Issue.find_by_id(issue_id)
169 168 return unless issue
170 169 # check permission
171 170 unless @@handler_options[:no_permission_check]
172 171 raise UnauthorizedAction unless user.allowed_to?(:add_issue_notes, issue.project) || user.allowed_to?(:edit_issues, issue.project)
173 172 raise UnauthorizedAction unless status.nil? || user.allowed_to?(:edit_issues, issue.project)
174 173 end
175 174
176 175 # add the note
177 176 journal = issue.init_journal(user, cleaned_up_text_body)
178 177 add_attachments(issue)
179 178 # check workflow
180 179 if status && issue.new_statuses_allowed_to(user).include?(status)
181 180 issue.status = status
182 181 end
183 182 issue.save!
184 183 logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
185 184 journal
186 185 end
187 186
188 187 # Reply will be added to the issue
189 188 def receive_journal_reply(journal_id)
190 189 journal = Journal.find_by_id(journal_id)
191 190 if journal && journal.journalized_type == 'Issue'
192 191 receive_issue_reply(journal.journalized_id)
193 192 end
194 193 end
195 194
196 195 # Receives a reply to a forum message
197 196 def receive_message_reply(message_id)
198 197 message = Message.find_by_id(message_id)
199 198 if message
200 199 message = message.root
201 200
202 201 unless @@handler_options[:no_permission_check]
203 202 raise UnauthorizedAction unless user.allowed_to?(:add_messages, message.project)
204 203 end
205 204
206 205 if !message.locked?
207 206 reply = Message.new(:subject => email.subject.gsub(%r{^.*msg\d+\]}, '').strip,
208 207 :content => cleaned_up_text_body)
209 208 reply.author = user
210 209 reply.board = message.board
211 210 message.children << reply
212 211 add_attachments(reply)
213 212 reply
214 213 else
215 214 logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic" if logger && logger.info
216 215 end
217 216 end
218 217 end
219 218
220 219 def add_attachments(obj)
221 220 if email.has_attachments?
222 221 email.attachments.each do |attachment|
223 222 Attachment.create(:container => obj,
224 223 :file => attachment,
225 224 :author => user,
226 225 :content_type => attachment.content_type)
227 226 end
228 227 end
229 228 end
230 229
231 230 # Adds To and Cc as watchers of the given object if the sender has the
232 231 # appropriate permission
233 232 def add_watchers(obj)
234 233 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
235 234 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
236 235 unless addresses.empty?
237 236 watchers = User.active.find(:all, :conditions => ['LOWER(mail) IN (?)', addresses])
238 237 watchers.each {|w| obj.add_watcher(w)}
239 238 end
240 239 end
241 240 end
242 241
243 242 def get_keyword(attr, options={})
244 243 @keywords ||= {}
245 244 if @keywords.has_key?(attr)
246 245 @keywords[attr]
247 246 else
248 247 @keywords[attr] = begin
249 248 if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && plain_text_body.gsub!(/^#{attr}[ \t]*:[ \t]*(.+)\s*$/i, '')
250 249 $1.strip
251 250 elsif !@@handler_options[:issue][attr].blank?
252 251 @@handler_options[:issue][attr]
253 252 end
254 253 end
255 254 end
256 255 end
257 256
258 257 # Returns the text/plain part of the email
259 258 # If not found (eg. HTML-only email), returns the body with tags removed
260 259 def plain_text_body
261 260 return @plain_text_body unless @plain_text_body.nil?
262 261 parts = @email.parts.collect {|c| (c.respond_to?(:parts) && !c.parts.empty?) ? c.parts : c}.flatten
263 262 if parts.empty?
264 263 parts << @email
265 264 end
266 265 plain_text_part = parts.detect {|p| p.content_type == 'text/plain'}
267 266 if plain_text_part.nil?
268 267 # no text/plain part found, assuming html-only email
269 268 # strip html tags and remove doctype directive
270 269 @plain_text_body = strip_tags(@email.body.to_s)
271 270 @plain_text_body.gsub! %r{^<!DOCTYPE .*$}, ''
272 271 else
273 272 @plain_text_body = plain_text_part.body.to_s
274 273 end
275 274 @plain_text_body.strip!
276 275 @plain_text_body
277 276 end
278 277
279 278 def cleaned_up_text_body
280 279 cleanup_body(plain_text_body)
281 280 end
282 281
283 282 def self.full_sanitizer
284 283 @full_sanitizer ||= HTML::FullSanitizer.new
285 284 end
286 285
287 286 # Creates a user account for the +email+ sender
288 287 def self.create_user_from_email(email)
289 288 addr = email.from_addrs.to_a.first
290 289 if addr && !addr.spec.blank?
291 290 user = User.new
292 291 user.mail = addr.spec
293 292
294 293 names = addr.name.blank? ? addr.spec.gsub(/@.*$/, '').split('.') : addr.name.split
295 294 user.firstname = names.shift
296 295 user.lastname = names.join(' ')
297 296 user.lastname = '-' if user.lastname.blank?
298 297
299 298 user.login = user.mail
300 299 user.password = ActiveSupport::SecureRandom.hex(5)
301 300 user.language = Setting.default_language
302 301 user.save ? user : nil
303 302 end
304 303 end
305 304
306 305 private
307 306
308 307 # Removes the email body of text after the truncation configurations.
309 308 def cleanup_body(body)
310 309 delimiters = Setting.mail_handler_body_delimiters.to_s.split(/[\r\n]+/).reject(&:blank?).map {|s| Regexp.escape(s)}
311 310 unless delimiters.empty?
312 311 regex = Regexp.new("^(#{ delimiters.join('|') })\s*[\r\n].*", Regexp::MULTILINE)
313 312 body = body.gsub(regex, '')
314 313 end
315 314 body.strip
316 315 end
317 316 end
@@ -1,345 +1,350
1 1 package Apache::Authn::Redmine;
2 2
3 3 =head1 Apache::Authn::Redmine
4 4
5 5 Redmine - a mod_perl module to authenticate webdav subversion users
6 6 against redmine database
7 7
8 8 =head1 SYNOPSIS
9 9
10 10 This module allow anonymous users to browse public project and
11 11 registred users to browse and commit their project. Authentication is
12 12 done against the redmine database or the LDAP configured in redmine.
13 13
14 14 This method is far simpler than the one with pam_* and works with all
15 15 database without an hassle but you need to have apache/mod_perl on the
16 16 svn server.
17 17
18 18 =head1 INSTALLATION
19 19
20 20 For this to automagically work, you need to have a recent reposman.rb
21 21 (after r860) and if you already use reposman, read the last section to
22 22 migrate.
23 23
24 24 Sorry ruby users but you need some perl modules, at least mod_perl2,
25 25 DBI and DBD::mysql (or the DBD driver for you database as it should
26 26 work on allmost all databases).
27 27
28 28 On debian/ubuntu you must do :
29 29
30 30 aptitude install libapache-dbi-perl libapache2-mod-perl2 libdbd-mysql-perl
31 31
32 32 If your Redmine users use LDAP authentication, you will also need
33 33 Authen::Simple::LDAP (and IO::Socket::SSL if LDAPS is used):
34 34
35 35 aptitude install libauthen-simple-ldap-perl libio-socket-ssl-perl
36 36
37 37 =head1 CONFIGURATION
38 38
39 39 ## This module has to be in your perl path
40 40 ## eg: /usr/lib/perl5/Apache/Authn/Redmine.pm
41 41 PerlLoadModule Apache::Authn::Redmine
42 42 <Location /svn>
43 43 DAV svn
44 44 SVNParentPath "/var/svn"
45 45
46 46 AuthType Basic
47 47 AuthName redmine
48 48 Require valid-user
49 49
50 50 PerlAccessHandler Apache::Authn::Redmine::access_handler
51 51 PerlAuthenHandler Apache::Authn::Redmine::authen_handler
52 52
53 53 ## for mysql
54 54 RedmineDSN "DBI:mysql:database=databasename;host=my.db.server"
55 55 ## for postgres
56 56 # RedmineDSN "DBI:Pg:dbname=databasename;host=my.db.server"
57 57
58 58 RedmineDbUser "redmine"
59 59 RedmineDbPass "password"
60 60 ## Optional where clause (fulltext search would be slow and
61 61 ## database dependant).
62 62 # RedmineDbWhereClause "and members.role_id IN (1,2)"
63 63 ## Optional credentials cache size
64 64 # RedmineCacheCredsMax 50
65 65 </Location>
66 66
67 67 To be able to browse repository inside redmine, you must add something
68 68 like that :
69 69
70 70 <Location /svn-private>
71 71 DAV svn
72 72 SVNParentPath "/var/svn"
73 73 Order deny,allow
74 74 Deny from all
75 75 # only allow reading orders
76 76 <Limit GET PROPFIND OPTIONS REPORT>
77 77 Allow from redmine.server.ip
78 78 </Limit>
79 79 </Location>
80 80
81 81 and you will have to use this reposman.rb command line to create repository :
82 82
83 83 reposman.rb --redmine my.redmine.server --svn-dir /var/svn --owner www-data -u http://svn.server/svn-private/
84 84
85 85 =head1 MIGRATION FROM OLDER RELEASES
86 86
87 87 If you use an older reposman.rb (r860 or before), you need to change
88 88 rights on repositories to allow the apache user to read and write
89 89 S<them :>
90 90
91 91 sudo chown -R www-data /var/svn/*
92 92 sudo chmod -R u+w /var/svn/*
93 93
94 94 And you need to upgrade at least reposman.rb (after r860).
95 95
96 96 =cut
97 97
98 98 use strict;
99 99 use warnings FATAL => 'all', NONFATAL => 'redefine';
100 100
101 101 use DBI;
102 102 use Digest::SHA1;
103 103 # optional module for LDAP authentication
104 104 my $CanUseLDAPAuth = eval("use Authen::Simple::LDAP; 1");
105 105
106 106 use Apache2::Module;
107 107 use Apache2::Access;
108 108 use Apache2::ServerRec qw();
109 109 use Apache2::RequestRec qw();
110 110 use Apache2::RequestUtil qw();
111 111 use Apache2::Const qw(:common :override :cmd_how);
112 112 use APR::Pool ();
113 113 use APR::Table ();
114 114
115 115 # use Apache2::Directive qw();
116 116
117 117 my @directives = (
118 118 {
119 119 name => 'RedmineDSN',
120 120 req_override => OR_AUTHCFG,
121 121 args_how => TAKE1,
122 122 errmsg => 'Dsn in format used by Perl DBI. eg: "DBI:Pg:dbname=databasename;host=my.db.server"',
123 123 },
124 124 {
125 125 name => 'RedmineDbUser',
126 126 req_override => OR_AUTHCFG,
127 127 args_how => TAKE1,
128 128 },
129 129 {
130 130 name => 'RedmineDbPass',
131 131 req_override => OR_AUTHCFG,
132 132 args_how => TAKE1,
133 133 },
134 134 {
135 135 name => 'RedmineDbWhereClause',
136 136 req_override => OR_AUTHCFG,
137 137 args_how => TAKE1,
138 138 },
139 139 {
140 140 name => 'RedmineCacheCredsMax',
141 141 req_override => OR_AUTHCFG,
142 142 args_how => TAKE1,
143 143 errmsg => 'RedmineCacheCredsMax must be decimal number',
144 144 },
145 145 );
146 146
147 147 sub RedmineDSN {
148 148 my ($self, $parms, $arg) = @_;
149 149 $self->{RedmineDSN} = $arg;
150 150 my $query = "SELECT
151 151 hashed_password, auth_source_id, permissions
152 152 FROM members, projects, users, roles, member_roles
153 153 WHERE
154 154 projects.id=members.project_id
155 155 AND member_roles.member_id=members.id
156 156 AND users.id=members.user_id
157 157 AND roles.id=member_roles.role_id
158 158 AND users.status=1
159 159 AND login=?
160 160 AND identifier=? ";
161 161 $self->{RedmineQuery} = trim($query);
162 162 }
163 163
164 164 sub RedmineDbUser { set_val('RedmineDbUser', @_); }
165 165 sub RedmineDbPass { set_val('RedmineDbPass', @_); }
166 166 sub RedmineDbWhereClause {
167 167 my ($self, $parms, $arg) = @_;
168 168 $self->{RedmineQuery} = trim($self->{RedmineQuery}.($arg ? $arg : "")." ");
169 169 }
170 170
171 171 sub RedmineCacheCredsMax {
172 172 my ($self, $parms, $arg) = @_;
173 173 if ($arg) {
174 174 $self->{RedmineCachePool} = APR::Pool->new;
175 175 $self->{RedmineCacheCreds} = APR::Table::make($self->{RedmineCachePool}, $arg);
176 176 $self->{RedmineCacheCredsCount} = 0;
177 177 $self->{RedmineCacheCredsMax} = $arg;
178 178 }
179 179 }
180 180
181 181 sub trim {
182 182 my $string = shift;
183 183 $string =~ s/\s{2,}/ /g;
184 184 return $string;
185 185 }
186 186
187 187 sub set_val {
188 188 my ($key, $self, $parms, $arg) = @_;
189 189 $self->{$key} = $arg;
190 190 }
191 191
192 192 Apache2::Module::add(__PACKAGE__, \@directives);
193 193
194 194
195 195 my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/;
196 196
197 197 sub access_handler {
198 198 my $r = shift;
199 199
200 200 unless ($r->some_auth_required) {
201 201 $r->log_reason("No authentication has been configured");
202 202 return FORBIDDEN;
203 203 }
204 204
205 205 my $method = $r->method;
206 206 return OK unless defined $read_only_methods{$method};
207 207
208 208 my $project_id = get_project_identifier($r);
209 209
210 210 $r->set_handlers(PerlAuthenHandler => [\&OK])
211 211 if is_public_project($project_id, $r);
212 212
213 213 return OK
214 214 }
215 215
216 216 sub authen_handler {
217 217 my $r = shift;
218 218
219 219 my ($res, $redmine_pass) = $r->get_basic_auth_pw();
220 220 return $res unless $res == OK;
221 221
222 222 if (is_member($r->user, $redmine_pass, $r)) {
223 223 return OK;
224 224 } else {
225 225 $r->note_auth_failure();
226 226 return AUTH_REQUIRED;
227 227 }
228 228 }
229 229
230 230 sub is_public_project {
231 231 my $project_id = shift;
232 232 my $r = shift;
233 233
234 234 my $dbh = connect_database($r);
235 235 my $sth = $dbh->prepare(
236 "SELECT * FROM projects WHERE projects.identifier=? and projects.is_public=true;"
236 "SELECT is_public FROM projects WHERE projects.identifier = ?;"
237 237 );
238 238
239 239 $sth->execute($project_id);
240 my $ret = $sth->fetchrow_array ? 1 : 0;
240 my $ret = 0;
241 if (my @row = $sth->fetchrow_array) {
242 if ($row[0] eq "1" || $row[0] eq "t") {
243 $ret = 1;
244 }
245 }
241 246 $sth->finish();
242 247 $dbh->disconnect();
243 248
244 249 $ret;
245 250 }
246 251
247 252 # perhaps we should use repository right (other read right) to check public access.
248 253 # it could be faster BUT it doesn't work for the moment.
249 254 # sub is_public_project_by_file {
250 255 # my $project_id = shift;
251 256 # my $r = shift;
252 257
253 258 # my $tree = Apache2::Directive::conftree();
254 259 # my $node = $tree->lookup('Location', $r->location);
255 260 # my $hash = $node->as_hash;
256 261
257 262 # my $svnparentpath = $hash->{SVNParentPath};
258 263 # my $repos_path = $svnparentpath . "/" . $project_id;
259 264 # return 1 if (stat($repos_path))[2] & 00007;
260 265 # }
261 266
262 267 sub is_member {
263 268 my $redmine_user = shift;
264 269 my $redmine_pass = shift;
265 270 my $r = shift;
266 271
267 272 my $dbh = connect_database($r);
268 273 my $project_id = get_project_identifier($r);
269 274
270 275 my $pass_digest = Digest::SHA1::sha1_hex($redmine_pass);
271 276
272 277 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
273 278 my $usrprojpass;
274 279 if ($cfg->{RedmineCacheCredsMax}) {
275 280 $usrprojpass = $cfg->{RedmineCacheCreds}->get($redmine_user.":".$project_id);
276 281 return 1 if (defined $usrprojpass and ($usrprojpass eq $pass_digest));
277 282 }
278 283 my $query = $cfg->{RedmineQuery};
279 284 my $sth = $dbh->prepare($query);
280 285 $sth->execute($redmine_user, $project_id);
281 286
282 287 my $ret;
283 288 while (my ($hashed_password, $auth_source_id, $permissions) = $sth->fetchrow_array) {
284 289
285 290 unless ($auth_source_id) {
286 291 my $method = $r->method;
287 292 if ($hashed_password eq $pass_digest && ((defined $read_only_methods{$method} && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/) ) {
288 293 $ret = 1;
289 294 last;
290 295 }
291 296 } elsif ($CanUseLDAPAuth) {
292 297 my $sthldap = $dbh->prepare(
293 298 "SELECT host,port,tls,account,account_password,base_dn,attr_login from auth_sources WHERE id = ?;"
294 299 );
295 300 $sthldap->execute($auth_source_id);
296 301 while (my @rowldap = $sthldap->fetchrow_array) {
297 302 my $ldap = Authen::Simple::LDAP->new(
298 host => ($rowldap[2] == 1 || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]" : $rowldap[0],
303 host => ($rowldap[2] eq "1" || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]" : $rowldap[0],
299 304 port => $rowldap[1],
300 305 basedn => $rowldap[5],
301 306 binddn => $rowldap[3] ? $rowldap[3] : "",
302 307 bindpw => $rowldap[4] ? $rowldap[4] : "",
303 308 filter => "(".$rowldap[6]."=%s)"
304 309 );
305 310 $ret = 1 if ($ldap->authenticate($redmine_user, $redmine_pass));
306 311 }
307 312 $sthldap->finish();
308 313 }
309 314 }
310 315 $sth->finish();
311 316 $dbh->disconnect();
312 317
313 318 if ($cfg->{RedmineCacheCredsMax} and $ret) {
314 319 if (defined $usrprojpass) {
315 320 $cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id, $pass_digest);
316 321 } else {
317 322 if ($cfg->{RedmineCacheCredsCount} < $cfg->{RedmineCacheCredsMax}) {
318 323 $cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id, $pass_digest);
319 324 $cfg->{RedmineCacheCredsCount}++;
320 325 } else {
321 326 $cfg->{RedmineCacheCreds}->clear();
322 327 $cfg->{RedmineCacheCredsCount} = 0;
323 328 }
324 329 }
325 330 }
326 331
327 332 $ret;
328 333 }
329 334
330 335 sub get_project_identifier {
331 336 my $r = shift;
332 337
333 338 my $location = $r->location;
334 339 my ($identifier) = $r->uri =~ m{$location/*([^/]+)};
335 340 $identifier;
336 341 }
337 342
338 343 sub connect_database {
339 344 my $r = shift;
340 345
341 346 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
342 347 return DBI->connect($cfg->{RedmineDSN}, $cfg->{RedmineDbUser}, $cfg->{RedmineDbPass});
343 348 }
344 349
345 350 1;
General Comments 0
You need to be logged in to leave comments. Login now