diff --git a/extra/svn/OpenProjectAuthentication.pm b/extra/svn/OpenProjectAuthentication.pm new file mode 100644 index 0000000000..9c8aaafc45 --- /dev/null +++ b/extra/svn/OpenProjectAuthentication.pm @@ -0,0 +1,188 @@ +package Apache::Authn::Redmine; + +=head1 Apache::Authn::Redmine + +Redmine - a mod_perl module to authenticate webdav subversion users +against an OpenProject web service + +=head1 SYNOPSIS + +This module allow anonymous users to browse public project and +registred users to browse and commit their project. Authentication is +done against an OpenProject web service. + +=head1 INSTALLATION + +For this to automagically work, you need to have a recent reposman.rb +(after r860) and if you already use reposman, read the last section to +migrate. + +Sorry ruby users but you need some perl modules, at least mod_perl2. + +On debian/ubuntu you must do : + + aptitude install libapache2-mod-perl2 + +=head1 CONFIGURATION + + ## This module has to be in your perl path + ## eg: /usr/lib/perl5/Apache/Authn/OpenProjectAuthentication.pm + PerlLoadModule Apache::Authn::OpenProjectAuthentication + + DAV svn + SVNParentPath "/var/svn" + + AuthType Basic + AuthName OpenProject + Require valid-user + + PerlAccessHandler Apache::Authn::Redmine::access_handler + PerlAuthenHandler Apache::Authn::Redmine::authen_handler + + RedmineUrl "http://example.com/openproject/" + RedmineApiKey "" + + +To be able to browse repository inside redmine, you must add something +like that : + + + DAV svn + SVNParentPath "/var/svn" + Order deny,allow + Deny from all + # only allow reading orders + + Allow from redmine.server.ip + + + +and you will have to use this reposman.rb command line to create repository : + + reposman.rb --redmine my.redmine.server --svn-dir /var/svn --owner www-data -u http://svn.server/svn-private/ + +=head1 MIGRATION FROM OLDER RELEASES + +If you use an older reposman.rb (r860 or before), you need to change +rights on repositories to allow the apache user to read and write +S + + sudo chown -R www-data /var/svn/* + sudo chmod -R u+w /var/svn/* + +And you need to upgrade at least reposman.rb (after r860). + +=cut + +use strict; +use warnings FATAL => 'all', NONFATAL => 'redefine'; + +use Digest::SHA1; + +use Apache2::Module; +use Apache2::Access; +use Apache2::ServerRec qw(); +use Apache2::RequestRec qw(); +use Apache2::RequestUtil qw(); +use Apache2::Const qw(:common :override :cmd_how); +use APR::Pool (); +use APR::Table (); + +use HTTP::Request::Common qw(POST); +use LWP::UserAgent; + +# use Apache2::Directive qw(); + +my @directives = ( + { + name => 'RedmineUrl', + req_override => OR_AUTHCFG, + args_how => TAKE1, + errmsg => 'URL of your (local) OpenProject. (e.g. http://localhost/ or http://www.example.com/openproject/)', + }, + { + name => 'RedmineApiKey', + req_override => OR_AUTHCFG, + args_how => TAKE1, + }, +); + +sub RedmineUrl { set_val('RedmineUrl', @_); } +sub RedmineApiKey { set_val('RedmineApiKey', @_); } + +sub trim { + my $string = shift; + $string =~ s/\s{2,}/ /g; + return $string; +} + +sub set_val { + my ($key, $self, $parms, $arg) = @_; + $self->{$key} = $arg; +} + +Apache2::Module::add(__PACKAGE__, \@directives); + +sub access_handler { + my $r = shift; + + unless ($r->some_auth_required) { + $r->log_reason("No authentication has been configured"); + return FORBIDDEN; + } + + return OK +} + +sub authen_handler { + my $r = shift; + + my ($status, $password) = $r->get_basic_auth_pw(); + my $login = $r->user; + + return $status unless $status == OK; + + my $identifier = get_project_identifier($r); + my $method = $r->method; + + if( is_access_allowed( $login, $password, $identifier, $method, $r ) ) { + return OK; + } else { + $r->note_auth_failure(); + return AUTH_REQUIRED; + } +} + +# we send a request to the redmine sys api +# and use the user's given login and password for basic auth +# for accessing the redmine sys api an api key is needed +sub is_access_allowed { + my $login = shift; + my $password = shift; + my $identifier = shift; + my $method = shift; + my $r = shift; + + my $cfg = Apache2::Module::get_config( __PACKAGE__, $r->server, $r->per_dir_config ); + + my $key = $cfg->{RedmineApiKey}; + my $redmine_url = $cfg->{RedmineUrl} . '/sys/repo_auth'; + + my $redmine_req = POST $redmine_url , [ repository => $identifier, key => $key, method => $method ]; + $redmine_req->authorization_basic( $login, $password ); + + my $ua = LWP::UserAgent->new; + my $response = $ua->request($redmine_req); + + return $response->is_success(); +} + +sub get_project_identifier { + my $r = shift; + + my $location = $r->location; + my ($identifier) = $r->uri =~ m{$location/*([^/]+)}; + $identifier; +} + +1; diff --git a/extra/svn/reposman.rb b/extra/svn/reposman.rb index cc6e75761d..a56d0aded6 100755 --- a/extra/svn/reposman.rb +++ b/extra/svn/reposman.rb @@ -2,7 +2,7 @@ #-- encoding: UTF-8 #-- copyright # OpenProject is a project management system. -# Copyright (C) 2012-2014 the OpenProject Foundation (OPF) +# Copyright (C) 2012-2013 the OpenProject Foundation (OPF) # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. @@ -31,8 +31,11 @@ require 'optparse' require 'find' require 'etc' +require 'json' +require 'net/http' +require 'uri' -Version = "1.3" +Version = "1.4" SUPPORTED_SCM = %w( Subversion Git Filesystem ) $verbose = 0 @@ -161,28 +164,22 @@ unless File.directory?($repos_base) log("directory '#{$repos_base}' doesn't exists", :exit => true) end -begin - require 'active_resource' -rescue LoadError - log("This script requires activeresource.\nRun 'gem install activeresource' to install it.", :exit => true) -end - -class Project < ActiveResource::Base - self.headers["User-agent"] = "Redmine repository manager/#{Version}" -end - log("querying Redmine for projects...", :level => 1); $redmine_host.gsub!(/^/, "http://") unless $redmine_host.match("^https?://") $redmine_host.gsub!(/\/$/, '') -Project.site = "#{$redmine_host}/sys"; +api_uri = URI.parse("#{$redmine_host}/sys") +http = Net::HTTP.new(api_uri.host, api_uri.port) +http.use_ssl = (api_uri.scheme == 'https') +http_headers = {'User-Agent' => "OpenProject-Repository-Manager/#{Version}"} begin # Get all active projects that have the Repository module enabled - projects = Project.find(:all, :params => {:key => $api_key}) + response = http.get("#{api_uri.path}/projects.json?key=#{$api_key}", http_headers) + projects = JSON.parse(response.body) rescue => e - log("Unable to connect to #{Project.site}: #{e}", :exit => true) + log("Unable to connect to #{$redmine_host}: #{e}", :exit => true) end if projects.nil? @@ -195,8 +192,8 @@ def set_owner_and_rights(project, repos_path, &block) if mswin? yield if block_given? else - uid, gid = Etc.getpwnam($svn_owner).uid, ($use_groupid ? Etc.getgrnam(project.identifier).gid : Etc.getgrnam($svn_group).gid) - right = project.is_public ? $public_mode : $private_mode + uid, gid = Etc.getpwnam($svn_owner).uid, ($use_groupid ? Etc.getgrnam(project['identifier']).gid : Etc.getgrnam($svn_group).gid) + right = project['is_public'] ? $public_mode : $private_mode right = right.to_i(8) & 007777 yield if block_given? Find.find(repos_path) do |f| @@ -221,17 +218,17 @@ def mswin? end projects.each do |project| - log("treating project #{project.name}", :level => 1) + log("treating project #{project['name']}", :level => 1) - if project.identifier.empty? - log("\tno identifier for project #{project.name}") + if project['identifier'].empty? + log("\tno identifier for project #{project['name']}") next - elsif not project.identifier.match(/^[a-z0-9\-_]+$/) - log("\tinvalid identifier for project #{project.name} : #{project.identifier}"); + elsif not project['identifier'].match(/^[a-z0-9\-_]+$/) + log("\tinvalid identifier for project #{project['name']} : #{project['identifier']}"); next; end - repos_path = File.join($repos_base, project.identifier).gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR) + repos_path = File.join($repos_base, project['identifier']).gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR) if File.directory?(repos_path) @@ -239,7 +236,7 @@ projects.each do |project| # rights before leaving other_read = other_read_right?(repos_path) owner = owner_name(repos_path) - next if project.is_public == other_read and owner == $svn_owner + next if project['is_public'] == other_read and owner == $svn_owner if $test log("\tchange mode on #{repos_path}") @@ -258,16 +255,16 @@ projects.each do |project| else # if repository is already declared in redmine, we don't create # unless user use -f with reposman - if $force == false and project.respond_to?(:repository) - log("\trepository for project #{project.identifier} already exists in Redmine", :level => 1) + if $force == false and project.has_key?('repository') + log("\trepository for project #{project['identifier']} already exists in Redmine", :level => 1) next end - project.is_public ? File.umask(0002) : File.umask(0007) + project['is_public'] ? File.umask(0002) : File.umask(0007) if $test log("\tcreate repository #{repos_path}") - log("\trepository #{repos_path} registered in Redmine with url #{$svn_url}#{project.identifier}") if $svn_url; + log("\trepository #{repos_path} registered in Redmine with url #{$svn_url}#{project['identifier']}") if $svn_url; next end @@ -286,8 +283,11 @@ projects.each do |project| if $svn_url begin - project.post(:repository, :vendor => $scm, :repository => {:url => "#{$svn_url}#{project.identifier}"}, :key => $api_key) - log("\trepository #{repos_path} registered in Redmine with url #{$svn_url}#{project.identifier}"); + http.post("#{api_uri.path}/projects/#{project['identifier']}/repository.json?" + + "vendor=#{$scm}&repository[url]=#{$svn_url}#{project['identifier']}&key=#{$api_key}", + "", # empty data + http_headers) + log("\trepository #{repos_path} registered in Redmine with url #{$svn_url}#{project['identifier']}"); rescue => e log("\trepository #{repos_path} not registered in Redmine: #{e.message}"); end