Project

General

Profile

Patch #1059 » svn_authent.x3n.patch

Lionel Molinier, 2008-04-15 09:00

View differences:

redmine-0.6.3-patched/app/apis/sys_api.rb 2008-04-11 22:10:28.000000000 +0200
22 22
  api_method :repository_created,
23 23
             :expects => [:string, :string],
24 24
             :returns => [:int]
25
  api_method :is_public_project,
26
             :expects => [:string],
27
             :returns => [:int]
28
  api_method :can_access_repository,
29
             :expects => [:string, :string, :string, :string],
30
             :returns => [:int]
25 31
end
redmine-0.6.3-patched/app/controllers/sys_controller.rb 2008-04-11 22:28:10.000000000 +0200
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
require 'openssl'
19
require 'digest/sha2'
20

  
18 21
class SysController < ActionController::Base
19 22
  wsdl_service_name 'Sys'
20 23
  web_service_api SysApi
......
39 50
    repository.id || 0
40 51
  end
41 52

  
53
  def is_public_project(repository)
54
    begin
55
      project = Project.find_by_repository(Regexp.new('.*/'+Regexp.escape(repository)+'/?$'))
56

  
57
      return 0 if project.nil?
58
      return 0 unless project.is_public
59
      return 1
60
    rescue
61
      return 0
62
    end
63
  end
64

  
65
  def can_access_repository(repository,login,ciphered_pwd_hex,iv_hex)
66
    begin
67
      # Decipher the password
68
      key = Digest::SHA256.digest(Setting.repositories_key)
69
      ciphered_pwd = ciphered_pwd_hex.gsub(/(..)/){|h| h.hex.chr}
70
      iv = iv_hex.gsub(/(..)/){|h| h.hex.chr}
71

  
72
      cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
73
      cipher.key = key
74
      cipher.iv = iv
75
      cipher.decrypt
76

  
77
      pwd = cipher.update(ciphered_pwd)
78
      pwd << cipher.final
79

  
80
      # Then, try to login
81
      user = User.try_to_login(login, pwd)
82
      return 0 unless user!=nil
83

  
84
      project = Project.find_by_repository(Regexp.new('.*/'+Regexp.escape(repository)+'/?$'))
85
      logger.debug "Requesting access for project: #{project.name}"
86
      return 0 unless user.member_of?(project)
87
      return 0 unless user.allowed_to?(:browse_repository, project)
88
      return 1 unless user.allowed_to?(:commit_repository, project)
89
      return 2
90
    rescue
91
      return 0
92
    end
93
  end
94

  
42 95
protected
43 96

  
44 97
  def check_enabled(name, args)
redmine-0.6.3-patched/app/models/project.rb 2008-04-09 15:05:50.000000000 +0200
182 182
    end
183 183
  end
184 184

  
185
  def self.find_by_repository(repository_wildcard)
186
    project = Project.find_by_identifier(repository_wildcard)
187
    return project unless project.nil? || project.repository.nil? || !project.repository.url=~repository_wildcard
188
    Project.find(:all, :include => :repository).each do |project|
189
      return project if !project.repository.nil? && project.repository.url=~repository_wildcard
190
    end
191
  end
192

  
185 193
protected
186 194
  def validate
187 195
    errors.add(parent_id, " must be a root project") if parent and parent.parent
redmine-0.6.3-patched/app/views/settings/edit.rhtml 2008-04-11 22:24:01.000000000 +0200
56 56

  
57 57
<p><label><%= l(:setting_repositories_encodings) %></label>
58 58
<%= text_field_tag 'settings[repositories_encodings]', Setting.repositories_encodings, :size => 60 %><br /><em><%= l(:text_comma_separated) %></em></p>
59

  
60
<p><label><%= l(:setting_repositories_key) %></label>
61
<%= text_field_tag 'settings[repositories_key]', Setting.repositories_key, :size => 60 %></p>
59 62
</div>
60 63

  
61 64
<fieldset class="box"><legend><%= l(:setting_issue_list_default_columns) %></legend>
redmine-0.6.3-patched/config/settings.yml 2008-04-14 11:23:36.000000000 +0200
97 97
# multiple values accepted, comma separated
98 98
repositories_encodings:
99 99
  default: ''
100
repositories_key:
101
  default: 'redminedefaultkey'
100 102
ui_theme:
101 103
  default: ''
102 104
emails_footer:
103 105
  default: |-
104 106
    You have received this notification because you have either subscribed to it, or are involved in.
105 107
    To change your notification preferences, please click here: http://hostname/my/account
106
  
redmine-0.6.3-patched/lang/en.yml 2008-04-11 22:20:48.000000000 +0200
196 197
setting_cross_project_issue_relations: Allow cross-project issue relations
197 198
setting_issue_list_default_columns: Default columns displayed on the issue list
198 199
setting_repositories_encodings: Repositories encodings
200
setting_repositories_key: Repositories communication key
199 201
setting_emails_footer: Emails footer
200 202
setting_protocol: Protocol
201 203

  
redmine-0.6.3-patched/lang/fr.yml 2008-04-11 22:20:48.000000000 +0200
196 197
setting_cross_project_issue_relations: Autoriser les relations entre demandes de différents projets
197 198
setting_issue_list_default_columns: Colonnes affichées par défaut sur la liste des demandes
198 199
setting_repositories_encodings: Encodages des dépôts
200
setting_repositories_key: Clé de chiffrement avec les dépôts
199 201
setting_emails_footer: Pied-de-page des emails
200 202
setting_protocol: Protocole
201 203

  
redmine-0.6.3-patched/lib/redmine.rb 2008-04-10 17:30:00.000000000 +0200
77 77
    
78 78
  map.project_module :repository do |map|
79 79
    map.permission :manage_repository, {:repositories => [:edit, :destroy]}, :require => :member
80
    map.permission :commit_repository, {:repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]}, :require => :member
80 81
    map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
81 82
    map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
82 83
  end
redmine-0.6.3-patched/extra/svn/RedmineSOAP.pm 2008-04-14 11:18:15.000000000 +0200
1
package Apache::Authn::RedmineSOAP;
2

  
3
=head1 Apache::Authn::RedmineSOAP
4

  
5
RedmineSOAP - a mod_perl module to authenticate webdav subversion users
6
against redmine authentication system (using WebService)
7

  
8
=head1 SYNOPSIS
9

  
10
This module allow anonymous users to browse public project. Registered
11
users may have read-only or read-write access depending their 'role'
12
in their project.
13

  
14
This method use the same login method as the redmine interface, so it
15
work no matter the auth sources setup in redmine. Password is cyphered
16
to ensure non divulgation in log or during the communication. It is
17
strongly recommended to use HTTPS for the WebService.
18
To improve performance a simple but configurable cache system has been
19
implemented (a 'svn co' requires up to 10 authentication requests)
20

  
21
=head1 INSTALLATION
22

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

  
27
Sorry ruby users but you need some perl modules, at least mod_perl2,
28
SOAP::Lite, File::Cache, Crypt::CBC, Crypt::Rijndael.
29

  
30
On debian/ubuntu you must do :
31

  
32
  aptitude install libapache2-mod-perl2 libsoap-lite-perl libcrypt-cbc-perl \
33
    livcrypt-rijndael-perl libfile-cache-perl
34

  
35
=head1 CONFIGURATION
36

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

  
45
   AuthType Basic
46
   AuthName redmine
47
   Require valid-user
48

  
49
   PerlAccessHandler Apache::Authn::RedmineSOAP::access_handler
50
   PerlAuthenHandler Apache::Authn::RedmineSOAP::authen_handler
51
  
52
   # Verbosity level (0: disabled)  
53
   PerlSetVar verbosity 0
54

  
55
   # secret key for password encryption (the same as redmine 
56
   # configuration)  
57
   PerlSetVar encryption_key "2p7PQ6FCFMWdBNK5SErQFQICUrZtSibJ"
58

  
59
   # Enable/disable cache
60
   # Caching is strongly recommanded, at least with few seconds cache!
61
   PerlSetVar enable_cache 1
62

  
63
   # Caching system configuration
64
   PerlSetVar private_project_expire "60 seconds"
65
   PerlSetVar public_project_expire "60 seconds"
66
   PerlSetVar auth_failed_expire "60 seconds"
67
   PerlSetVar auth_ro_expire "60 seconds"
68
   PerlSetVar auth_rw_neg "60 seconds"
69

  
70
   # Redming WebService URL  
71
   PerlSetVar url https://dev.ginkgo-networks.com
72
  
73
  </Location>
74

  
75
To be able to browse repository inside redmine, you must add something
76
like that :
77

  
78
   <Location /svn-private>
79
   DAV svn
80
   SVNParentPath "/var/svn"
81
   Order deny,allow
82
   Deny from all
83
   # only allow reading orders
84
   <Limit GET PROPFIND OPTIONS REPORT>
85
     Allow from redmine.server.ip
86
   </Limit>
87
   </Location>
88

  
89
and you will have to use this reposman.rb command line to create repository :
90

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

  
93
=head1 MIGRATION FROM OLDER RELEASES
94

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

  
99
  sudo chown -R www-data /var/svn/*
100
  sudo chmod -R u+w /var/svn/*
101

  
102
And you need to upgrade at least reposman.rb (after r860).
103

  
104
=cut
105

  
106
use strict;
107

  
108
use SOAP::Lite;
109
use File::Cache;
110

  
111
use Digest::SHA qw(sha256);
112
use Crypt::CBC;
113

  
114
use Apache2::Module;
115
use Apache2::Access;
116
use Apache2::ServerRec qw();
117
use Apache2::RequestRec qw();
118
use Apache2::RequestUtil qw();
119
use Apache2::Const qw(:common);
120
# use Apache2::Directive qw();
121

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

  
124
my $verbosity = 0;
125
my $file = "/tmp/redmine.auth.log";
126

  
127
my $url = "http://localhost";
128

  
129
## Password encryption
130
my $key = "redminedefaultkey";
131

  
132
## Cache system
133
my $cache = 1;
134
my $public_cache = new File::Cache( { namespace  => 'svn_redmine_public_project' } );
135
my $auth_cache = new File::Cache( { namespace  => 'svn_redmine_auth' } );
136

  
137
# Expiration
138
# 0 --> negative
139
# 1 --> positive
140
my @public_expire = (5, 5);
141
my @auth_expire = (5, 5);
142

  
143

  
144
sub is_set {
145
  my $r = shift;
146
  my $key = shift;  
147
  return defined $r->dir_config($key); 
148
}
149

  
150
sub config {
151
  my $r = shift;  
152

  
153
  $verbosity = $r->dir_config("verbosity") if is_set($r,"verbosity");
154
  $cache = $r->dir_config("enable_cache") if is_set($r,"enable_cache"); 
155
  $key = $r->dir_config("encryption_key") if is_set($r,"encryption_key");
156
  $url = $r->dir_config("url") if is_set($r,"url");
157

  
158
  @public_expire[0] = $r->dir_config("private_project_expire") if is_set($r,"private_project_expire");
159
  @public_expire[1] = $r->dir_config("public_project_expire") if is_set($r,"public_project_expire");
160

  
161
  @auth_expire[0] = $r->dir_config("auth_failed_expire") if is_set($r,"auth_failed_expire");
162
  @auth_expire[1] = $r->dir_config("auth_ro_expire") if is_set($r,"auth_ro_expire");
163
  @auth_expire[2] = $r->dir_config("auth_rw_expire") if is_set($r,"auth_rw_expire");
164
}
165

  
166
sub access_handler {
167
  my $r = shift;
168
  config($r);
169

  
170
  unless ($r->some_auth_required) {
171
    $r->log_reason("No authentication has been configured");
172
    return FORBIDDEN;
173
  }
174

  
175
  my $repository_name = get_repository_name($r);
176

  
177
  $r->set_handlers(PerlAuthenHandler => [\&OK]) 
178
    if is_public_project($repository_name, $r);
179

  
180
  return OK
181
}
182

  
183
sub authen_handler {
184
  my $r = shift;
185
  config($r);
186

  
187
  my ($res, $redmine_pass) =  $r->get_basic_auth_pw();
188
  return $res unless $res == OK;
189
  
190
  Log(level => 1, text => "Checking auth"); 
191
  my $ret = get_credits($r->user, $redmine_pass, $r);
192
  my $method = $r->method;
193

  
194
  if ($ret>=2) {
195
    return OK;
196
  } elsif($ret==1 && $read_only_methods{$method}==1) {
197
    return OK;
198
  } else {
199
    $r->note_auth_failure();
200
    return AUTH_REQUIRED;
201
  }
202
}
203

  
204
sub is_public_project {
205
  my $repository_name = shift;
206
  my $r = shift;
207

  
208
  my $ret;
209
  $ret = $public_cache->get($repository_name) if $cache;
210
  if ( not defined $ret ) {
211
    # We have to request to Redmine SAOP WebService if the project is public or not
212

  
213
    my $service = connect_redmine($r);
214
    Log(level => 3, text => "requesting visibility for project $repository_name"); 
215
    $ret = $service->IsPublicProject($repository_name);
216

  
217
    # Add the response to the cache
218
    if($cache) {
219
      Log(level => 3, text => "project $repository_name is_public saving: $ret"); 
220
      $public_cache->set($repository_name, $ret, $public_expire[$ret]);
221
      Log(level => 3, text => "project $repository_name is_public saved: $ret"); 
222
    }
223
  }
224

  
225
  Log(level => 1, text => "Returned project $repository_name is_public: $ret"); 
226
  return $ret;
227
}
228

  
229
sub get_credits {
230
  my $redmine_user = shift;
231
  my $redmine_pass = shift;
232
  my $r = shift;
233
  my $repository_name  = get_repository_name($r);
234

  
235
  my $ret;
236
  $ret = $auth_cache->get( "$repository_name/$redmine_user/"+sha256($redmine_pass) ) if $cache;
237
  if ( not defined $ret ) {
238
    # We have to request credentials to Redmine
239
    my $service = connect_redmine($r);
240
    Log(level => 2, text => "Access requested for $redmine_user for $repository_name: $ret"); 
241

  
242
    # Encrypt the serial symmetricaly
243
    # We cannot use a digest for password matching in case there are external auth system used in Redmine
244
    my $iv = Crypt::CBC->random_bytes(16);
245
    my $cipher = Crypt::CBC->new(
246
      -cipher => 'Rijndael',
247
      -key => sha256($key),
248
      -iv => $iv,
249
      -literal_key => 1,
250
      -header => 'none');
251
    $cipher->start('encrypt');
252

  
253
    my $ciphered_pass_hex = $cipher->encrypt_hex($redmine_pass);
254
    my $iv_hex = unpack("H*", $iv); 
255
    Log(level => 4, text => "Ciphered password: $ciphered_pass_hex");
256

  
257
    # Then, request:
258
    $ret = $service->CanAccessRepository($repository_name,$redmine_user, $ciphered_pass_hex,$iv_hex);
259

  
260
    # Finally, cache the result
261
    if($cache) {
262
      Log(level => 3, text => "Saving value for ($redmine_user,$repository_name): $ret"); 
263
      $auth_cache->set( "$repository_name/$redmine_user/"+sha256($redmine_pass), $ret, $auth_expire[$ret]);
264
      Log(level => 3, text => "Saved value for ($redmine_user,$repository_name): $ret"); 
265
    }
266
  }
267

  
268
  Log(level => 1, text => "Returned value for ($redmine_user,$repository_name): $ret"); 
269
  return $ret;
270
}
271

  
272
sub get_repository_name {
273
  my $r = shift;
274
  
275
  my $location = $r->location;
276
  my ($repository_name) = $r->uri =~ m{$location/*([^/]+)};
277
  $repository_name;
278
}
279

  
280
sub connect_redmine {
281
  my $r = shift;
282

  
283
  my $wsdl = "$url/sys/service.wsdl";
284

  
285
  return SOAP::Lite->service($wsdl);
286
}
287

  
288
sub Log {
289
  my %args = (level => 0, text => '', @_);
290

  
291
  my $level = delete $args{level};
292
  my $text  = delete $args{text};
293
  return unless $level <= $verbosity;
294

  
295
  open FILE, ">>$file" or die "unable to open $file $!";
296
  print FILE "$text\n";
297
 
298
  exit $args{exit}
299
    if defined $args{exit};
300
}
301

  
302
1;
    (1-1/1)