Index: app/helpers/repositories_helper.rb =================================================================== --- app/helpers/repositories_helper.rb (revision 12119) +++ app/helpers/repositories_helper.rb (working copy) @@ -150,18 +150,37 @@ end def subversion_field_tags(form, repository) - content_tag('p', form.text_field(:url, :size => 60, :required => true, - :disabled => !repository.safe_attribute?('url')) + - '
'.html_safe + - '(file:///, http://, https://, svn://, svn+[tunnelscheme]://)') + - content_tag('p', form.text_field(:login, :size => 30)) + - content_tag('p', form.password_field( - :password, :size => 30, :name => 'ignore', - :value => ((repository.new_record? || repository.password.blank?) ? '' : ('x'*15)), - :onfocus => "this.value=''; this.name='repository[password]';", - :onchange => "this.name='repository[password]';")) + login_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']] + login_options << [l("repository_login_usernamepassword"), 0] + login_options << [l("repository_login_current_username"), 1] + login_options << [l("repository_login_current_role"), 2] + + content_tag('p', form.text_field(:url, :size => 60, :required => true, + :disabled => (repository && !repository.root_url.blank?)) + + '
'.html_safe + + '(file:///, http://, https://, svn://, svn+[tunnelscheme]://)') + + content_tag('p', form.text_field(:root_url, :size => 60) + + '
'.html_safe + '(this needs to be set to the to the highest level in the SVN tree allowed if not the URL)') + + content_tag('p', form.select(:login_method, login_options) + + '
'.html_safe + + '(Where method is a current user, credentials should be provided for the authz access handler)') + + content_tag('p', form.text_field(:login, :size => 30)) + + content_tag('p', form.password_field( + :password, :size => 30, :name => 'ignore', + :value => ((repository.new_record? || repository.password.blank?) ? '' : ('x'*15)), + :onfocus => "this.value=''; this.name='repository[password]';", + :onchange => "this.name='repository[password]';")) + + content_tag('p', form.password_field(:security_token, :size => 30, :name => 'ignore', + :value => ((repository.new_record? || repository.security_token.blank?) ? '' : ('x'*15)), + :onfocus => "this.value=''; this.name='repository[security_token]';", + :onchange => "this.name='repository[security_token]';") + + '
'.html_safe + + '(The security token for the authz access handler needed for current user logon methods)') end +# select_tag('login_method', options_for_select(login_method, repository.class.name.demodulize), +# :onchange => remote_function(:url => { :controller => 'repositories', :action => 'edit', :id => @project }, :method => :get, :with => "Form.serialize(this.form)") ) + + def darcs_field_tags(form, repository) content_tag('p', form.text_field( :url, :label => l(:field_path_to_repository), Index: app/models/repository.rb =================================================================== --- app/models/repository.rb (revision 12119) +++ app/models/repository.rb (working copy) @@ -51,7 +51,10 @@ 'password', 'path_encoding', 'log_encoding', - 'is_default' + 'is_default', + 'login_method', + 'root_url', + 'security_token' safe_attributes 'url', :if => lambda {|repository, user| repository.new_record?} @@ -94,8 +97,7 @@ def scm unless @scm - @scm = self.scm_adapter.new(url, root_url, - login, password, path_encoding) + @scm = self.scm_adapter.new(url, root_url, login, password, path_encoding, login_method, security_token, project) if root_url.blank? && @scm.root_url.present? update_attribute(:root_url, @scm.root_url) end Index: app/models/repository/subversion.rb =================================================================== --- app/models/repository/subversion.rb (revision 12119) +++ app/models/repository/subversion.rb (working copy) @@ -18,7 +18,7 @@ require 'redmine/scm/adapters/subversion_adapter' class Repository::Subversion < Repository - attr_protected :root_url + #attr_protected :root_url validates_presence_of :url validates_format_of :url, :with => /\A(http|https|svn(\+[^\s:\/\\]+)?|file):\/\/.+/i Index: app/controllers/repositories_controller.rb =================================================================== --- app/controllers/repositories_controller.rb (revision 12119) +++ app/controllers/repositories_controller.rb (working copy) @@ -70,6 +70,7 @@ end @repository.project = @project if request.put? && @repository.save + @repository.save redirect_to settings_project_path(@project, :tab => 'repositories') else render :action => 'edit' Index: extra/svn/Redmine.pm =================================================================== --- extra/svn/Redmine.pm (revision 12119) +++ extra/svn/Redmine.pm (working copy) @@ -1,10 +1,15 @@ -package Apache::Authn::Redmine; +package Apache::Redmine; -=head1 Apache::Authn::Redmine +=head1 Apache::Redmine Redmine - a mod_perl module to authenticate webdav subversion users against redmine database +In addition this module allows to authenticate a redmine server +for webdav subversion access, bypassing full authentication, still +allowing to apply repository based security (e.g. .authz files) + + =head1 SYNOPSIS This module allow anonymous users to browse public project and @@ -37,8 +42,8 @@ =head1 CONFIGURATION ## This module has to be in your perl path - ## eg: /usr/lib/perl5/Apache/Authn/Redmine.pm - PerlLoadModule Apache::Authn::Redmine + ## eg: /usr/lib/perl5/Apache/Redmine.pm + PerlLoadModule Apache::Redmine DAV svn SVNParentPath "/var/svn" @@ -47,8 +52,8 @@ AuthName redmine Require valid-user - PerlAccessHandler Apache::Authn::Redmine::access_handler - PerlAuthenHandler Apache::Authn::Redmine::authen_handler + PerlAccessHandler Apache::Redmine::access_handler + PerlAuthenHandler Apache::Redmine::authen_handler ## for mysql RedmineDSN "DBI:mysql:database=databasename;host=my.db.server" @@ -73,6 +78,16 @@ Order deny,allow Deny from all # only allow reading orders + + AuthType Basic + AuthName redmine + Require valid-user + + PerlAccessHandler Apache::Redmine::redmine_access_handler + PerlAuthenHandler Apache::Redmine::redmine_authen_handler + + RedmineSecurityToken "redmine" + Allow from redmine.server.ip @@ -229,6 +244,11 @@ errmsg => 'RedmineCacheCredsMax must be decimal number', }, { + name => 'RedmineSecurityToken', + req_override => OR_AUTHCFG, + args_how => TAKE1, + errmsg => 'RedmineSecurityToken additional authentication token', + }, { name => 'RedmineGitSmartHttp', req_override => OR_AUTHCFG, args_how => TAKE1, @@ -256,6 +276,7 @@ sub RedmineDbUser { set_val('RedmineDbUser', @_); } sub RedmineDbPass { set_val('RedmineDbPass', @_); } +sub RedmineSecurityToken { set_val('RedmineSecurityToken', @_); } sub RedmineDbWhereClause { my ($self, $parms, $arg) = @_; $self->{RedmineQuery} = trim($self->{RedmineQuery}.($arg ? $arg : "")." "); @@ -319,7 +340,7 @@ my $r = shift; unless ($r->some_auth_required) { - $r->log_reason("No authentication has been configured"); + $r->log_reason("Apache::Redmine - No authentication has been configured in webserver (access_handler)"); return FORBIDDEN; } @@ -541,4 +562,43 @@ return DBI->connect($cfg->{RedmineDSN}, $cfg->{RedmineDbUser}, $cfg->{RedmineDbPass}); } +sub redmine_access_handler { + my $r = shift; + + unless ($r->some_auth_required) { + $r->log_reason("Apache::Redmine - No auth has been configured in vhost conf (redmine_access_handler)"); + return FORBIDDEN; + } + + my $method = $r->method; + return OK unless defined $read_only_methods{$method}; + + my $project_id = get_project_identifier($r); + + $r->set_handlers(PerlAuthenHandler => [\&OK]) + if is_public_project($project_id, $r); + + return OK +} + +sub redmine_authen_handler { + my $r = shift; + + my ($res, $redmine_pass) = $r->get_basic_auth_pw(); + return $res unless $res == OK; + + my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config); + + if ($cfg->{RedmineSecurityToken}) { + my $securityToken = $cfg->{RedmineSecurityToken}; + + if ($securityToken ne $redmine_pass) { + $r->log_error("Apache::Redmine - Provided SecurityToken did not match (redmine_authen_handler)"); + $r->note_auth_failure(); + return AUTH_REQUIRED; + } + } + return OK; +} + 1; Index: db/migrate/109_add_special_repository_security.rb =================================================================== --- db/migrate/109_add_special_repository_security.rb (revision 0) +++ db/migrate/109_add_special_repository_security.rb (revision 0) @@ -0,0 +1,11 @@ +class AddSpecialRepositorySecurity < ActiveRecord::Migration + def self.down + remove_column :repositories, :login_method + remove_column :repositories, :security_token + end + + def self.up + add_column :repositories, :login_method, :int, :default => 0, :null => true + add_column :repositories, :security_token, :string, :limit => 60, :default => "", :null => true + end +end \ No newline at end of file Index: config/locales/en-GB.yml =================================================================== --- config/locales/en-GB.yml (revision 12119) +++ config/locales/en-GB.yml (working copy) @@ -312,7 +312,14 @@ field_text: Text field field_visible: Visible field_warn_on_leaving_unsaved: "Warn me when leaving a page with unsaved text" + field_login_method: Authentication method + field_security_token: Security token + field_root_url: Root URL + repository_login_usernamepassword: Provided credentials + repository_login_current_username: Name of current user + repository_login_current_role: Role of current user + setting_app_title: Application title setting_app_subtitle: Application subtitle setting_welcome_text: Welcome text Index: config/locales/en.yml =================================================================== --- config/locales/en.yml (revision 12119) +++ config/locales/en.yml (working copy) @@ -331,7 +331,14 @@ field_board_parent: Parent forum field_private_notes: Private notes field_inherit_members: Inherit members + field_login_method: Authentication method + field_security_token: Security token + field_root_url: Root URL + repository_login_usernamepassword: Provided credentials + repository_login_current_username: Name of current user + repository_login_current_role: Role of current user + setting_app_title: Application title setting_app_subtitle: Application subtitle setting_welcome_text: Welcome text Index: lib/redmine/scm/adapters/subversion_adapter.rb =================================================================== --- lib/redmine/scm/adapters/subversion_adapter.rb (revision 12119) +++ lib/redmine/scm/adapters/subversion_adapter.rb (working copy) @@ -257,9 +257,32 @@ private def credentials_string - str = '' - str << " --username #{shell_quote(@login)}" unless @login.blank? - str << " --password #{shell_quote(@password)}" unless @login.blank? || @password.blank? + if !(User.current.is_a?(AnonymousUser)) + + if (@login_method == 1) + str = '' + str << " --username #{shell_quote(User.current.login)}" + str << " --password #{shell_quote( (@security_token.blank? ? "foo" : @security_token) )}" + end + + if (@login_method == 2) && (!@project.nil?) + role = User.current.role_for_project(@project) + + if !role.blank? + str = '' + str << " --username #{shell_quote(role.name)}" + str << " --password #{shell_quote( (@security_token.blank? ? "foo" : @security_token) )}" + end + end + + end + + if (str.blank?) + str = '' + str << " --username #{shell_quote(@login)}" unless @login.blank? + str << " --password #{shell_quote(@password)}" unless @login.blank? || @password.blank? + end + str << " --no-auth-cache --non-interactive" str end Index: lib/redmine/scm/adapters/abstract_adapter.rb =================================================================== --- lib/redmine/scm/adapters/abstract_adapter.rb (revision 12119) +++ lib/redmine/scm/adapters/abstract_adapter.rb (working copy) @@ -79,12 +79,14 @@ end end - def initialize(url, root_url=nil, login=nil, password=nil, - path_encoding=nil) + def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil, login_method=nil, security_token=nil, project=nil) @url = url @login = login if login && !login.empty? @password = (password || "") if @login @root_url = root_url.blank? ? retrieve_root_url : root_url + @login_method = login_method.blank? ? 0 : login_method + @security_token = security_token.blank? ? "" : security_token + @project = project end def adapter_name @@ -99,6 +101,10 @@ respond_to?('annotate') end + def use_current_user + @use_current_user + end + def root_url @root_url end