Project

General

Profile

Patch #4905 » 2.0-stable.diff

Toshi MARUYAMA, 2012-07-17 00:45

View differences:

extra/svn/Redmine.pm
102 102

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

  
105
=head1 GIT SMART HTTP SUPPORT
106

  
107
Git's smart HTTP protocol (available since Git 1.7.0) will not work with the
108
above settings. Redmine.pm normally does access control depending on the HTTP
109
method used: read-only methods are OK for everyone in public projects and
110
members with read rights in private projects. The rest require membership with
111
commit rights in the project.
112

  
113
However, this scheme doesn't work for Git's smart HTTP protocol, as it will use
114
POST even for a simple clone. Instead, read-only requests must be detected using
115
the full URL (including the query string): anything that doesn't belong to the
116
git-receive-pack service is read-only.
117

  
118
To activate this mode of operation, add this line inside your <Location /git>
119
block:
120

  
121
  RedmineGitSmartHttp yes
122

  
123
Here's a sample Apache configuration which integrates git-http-backend with
124
a MySQL database and this new option:
125

  
126
   SetEnv GIT_PROJECT_ROOT /var/www/git/
127
   SetEnv GIT_HTTP_EXPORT_ALL
128
   ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/
129
   <Location /git>
130
       Order allow,deny
131
       Allow from all
132

  
133
       AuthType Basic
134
       AuthName Git
135
       Require valid-user
136

  
137
       PerlAccessHandler Apache::Authn::Redmine::access_handler
138
       PerlAuthenHandler Apache::Authn::Redmine::authen_handler
139
       # for mysql
140
       RedmineDSN "DBI:mysql:database=redmine;host=127.0.0.1"
141
       RedmineDbUser "redmine"
142
       RedmineDbPass "xxx"
143
       RedmineGitSmartHttp yes
144
    </Location>
145

  
146
Make sure that all the names of the repositories under /var/www/git/ have a
147
matching identifier for some project: /var/www/git/myproject and
148
/var/www/git/myproject.git will work. You can put both bare and non-bare
149
repositories in /var/www/git, though bare repositories are strongly
150
recommended. You should create them with the rights of the user running Redmine,
151
like this:
152

  
153
  cd /var/www/git
154
  sudo -u user-running-redmine mkdir myproject
155
  cd myproject
156
  sudo -u user-running-redmine git init --bare
157

  
158
Once you have activated this option, you have three options when cloning a
159
repository:
160

  
161
- Cloning using "http://user@host/git/repo(.git)" works, but will ask for the password
162
  all the time.
163

  
164
- Cloning with "http://user:pass@host/git/repo(.git)" does not have this problem, but
165
  this could reveal accidentally your password to the console in some versions
166
  of Git, and you would have to ensure that .git/config is not readable except
167
  by the owner for each of your projects.
168

  
169
- Use "http://host/git/repo(.git)", and store your credentials in the ~/.netrc
170
  file. This is the recommended solution, as you only have one file to protect
171
  and passwords will not be leaked accidentally to the console.
172

  
173
  IMPORTANT NOTE: It is *very important* that the file cannot be read by other
174
  users, as it will contain your password in cleartext. To create the file, you
175
  can use the following commands, replacing yourhost, youruser and yourpassword
176
  with the right values:
177

  
178
    touch ~/.netrc
179
    chmod 600 ~/.netrc
180
    echo -e "machine yourhost\nlogin youruser\npassword yourpassword" > ~/.netrc
181

  
105 182
=cut
106 183

  
107 184
use strict;
......
151 228
    args_how => TAKE1,
152 229
    errmsg => 'RedmineCacheCredsMax must be decimal number',
153 230
  },
231
  {
232
    name => 'RedmineGitSmartHttp',
233
    req_override => OR_AUTHCFG,
234
    args_how => TAKE1,
235
  },
154 236
);
155 237

  
156 238
sub RedmineDSN {
......
188 270
  }
189 271
}
190 272

  
273
sub RedmineGitSmartHttp {
274
  my ($self, $parms, $arg) = @_;
275
  $arg = lc $arg;
276

  
277
  if ($arg eq "yes" || $arg eq "true") {
278
    $self->{RedmineGitSmartHttp} = 1;
279
  } else {
280
    $self->{RedmineGitSmartHttp} = 0;
281
  }
282
}
283

  
191 284
sub trim {
192 285
  my $string = shift;
193 286
  $string =~ s/\s{2,}/ /g;
......
204 297

  
205 298
my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/;
206 299

  
300
sub request_is_read_only {
301
  my ($r) = @_;
302
  my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
303

  
304
  # Do we use Git's smart HTTP protocol, or not?
305
  if (defined $cfg->{RedmineGitSmartHttp} and $cfg->{RedmineGitSmartHttp}) {
306
    my $uri = $r->unparsed_uri;
307
    my $location = $r->location;
308
    my $is_read_only = $uri !~ m{^$location/*[^/]+/+(info/refs\?service=)?git\-receive\-pack$}o;
309
    return $is_read_only;
310
  } else {
311
    # Standard behaviour: check the HTTP method
312
    my $method = $r->method;
313
    return defined $read_only_methods{$method};
314
  }
315
}
316

  
207 317
sub access_handler {
208 318
  my $r = shift;
209 319

  
......
212 322
      return FORBIDDEN;
213 323
  }
214 324

  
215
  my $method = $r->method;
216
  return OK unless defined $read_only_methods{$method};
325
  return OK unless request_is_read_only($r);
217 326

  
218 327
  my $project_id = get_project_identifier($r);
219 328

  
......
338 447

  
339 448
  my $pass_digest = Digest::SHA::sha1_hex($redmine_pass);
340 449

  
341
  my $access_mode = defined $read_only_methods{$r->method} ? "R" : "W";
450
  my $access_mode = request_is_read_only($r) ? "R" : "W";
342 451

  
343 452
  my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
344 453
  my $usrprojpass;
......
414 523
sub get_project_identifier {
415 524
    my $r = shift;
416 525

  
526
    my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
417 527
    my $location = $r->location;
528
    $location =~ s/\.git$// if (defined $cfg->{RedmineGitSmartHttp} and $cfg->{RedmineGitSmartHttp});
418 529
    my ($identifier) = $r->uri =~ m{$location/*([^/.]+)};
419 530
    $identifier;
420 531
}
test/extra/redmine_pm/repository_git_test.rb
1
# Redmine - project management software
2
# Copyright (C) 2006-2012  Jean-Philippe Lang
3
#
4
# This program is free software; you can redistribute it and/or
5
# modify it under the terms of the GNU General Public License
6
# as published by the Free Software Foundation; either version 2
7
# of the License, or (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17

  
18
require File.expand_path('../test_case', __FILE__)
19
require 'tmpdir'
20

  
21
class RedminePmTest::RepositoryGitTest < RedminePmTest::TestCase
22
  fixtures :projects, :users, :members, :roles, :member_roles
23

  
24
  GIT_BIN = Redmine::Configuration['scm_git_command'] || "git"
25

  
26
  def test_anonymous_read_on_public_repo_with_permission_should_succeed
27
    assert_success "ls-remote", git_url
28
  end
29

  
30
  def test_anonymous_read_on_public_repo_without_permission_should_fail
31
    Role.anonymous.remove_permission! :browse_repository
32
    assert_failure "ls-remote", git_url
33
  end
34

  
35
  def test_invalid_credentials_should_fail
36
    Project.find(1).update_attribute :is_public, false
37
    with_credentials "dlopper", "foo" do
38
      assert_success "ls-remote", git_url
39
    end
40
    with_credentials "dlopper", "wrong" do
41
      assert_failure "ls-remote", git_url
42
    end
43
  end
44

  
45
  def test_clone
46
    Dir.mktmpdir do |dir|
47
      Dir.chdir(dir) do
48
        assert_success "clone", git_url
49
      end
50
    end
51
  end
52

  
53
  def test_write_commands
54
    Role.find(2).add_permission! :commit_access
55
    filename = random_filename
56

  
57
    Dir.mktmpdir do |dir|
58
      assert_success "clone", git_url, dir
59
      Dir.chdir(dir) do
60
        f = File.new(File.join(dir, filename), "w")
61
        f.write "test file content"
62
        f.close
63

  
64
        with_credentials "dlopper", "foo" do
65
          assert_success "add", filename
66
          assert_success "commit -a --message Committing_a_file"
67
          assert_success "push", git_url, "--all"
68
        end
69
      end
70
    end
71

  
72
    Dir.mktmpdir do |dir|
73
      assert_success "clone", git_url, dir
74
      Dir.chdir(dir) do
75
        assert File.exists?(File.join(dir, "#{filename}"))
76
      end
77
    end
78
  end
79

  
80
  protected
81

  
82
  def execute(*args)
83
    a = [GIT_BIN]
84
    super a, *args
85
  end
86

  
87
  def git_url(path=nil)
88
    host = ENV['REDMINE_TEST_DAV_SERVER'] || '127.0.0.1'
89
    credentials = nil
90
    if username && password
91
      credentials = "#{username}:#{password}"
92
    end
93
    url = "http://#{credentials}@#{host}/git/ecookbook"
94
    url << "/#{path}" if path
95
    url
96
  end
97
end
(21-21/24)