]> source.dussan.org Git - redmine.git/commitdiff
Merged nbc branch @ r1812 (commit access permission and reposman improvements).
authorJean-Philippe Lang <jp_lang@yahoo.fr>
Sat, 13 Sep 2008 16:31:11 +0000 (16:31 +0000)
committerJean-Philippe Lang <jp_lang@yahoo.fr>
Sat, 13 Sep 2008 16:31:11 +0000 (16:31 +0000)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1814 e93f8b46-1217-0410-a6f0-8f06a7374b81

12 files changed:
app/apis/sys_api.rb
app/controllers/sys_controller.rb
app/models/project.rb
app/models/role.rb
db/migrate/096_add_commit_access_permission.rb [new file with mode: 0644]
extra/svn/Redmine.pm
extra/svn/reposman.pl [deleted file]
extra/svn/reposman.rb
lib/redmine.rb
lib/redmine/default_data/loader.rb
test/functional/sys_api_test.rb
test/unit/role_test.rb

index f52f9e7ef58c0dd0c0e4397401a2cc133d0c94d4..fcee616b511499325fc13e4f6e7c6cbaf794d525 100644 (file)
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
+class AWSProjectWithRepository < ActionWebService::Struct
+  member :id,              :int
+  member :identifier,      :string
+  member :name,            :string
+  member :is_public,       :bool
+  member :repository,      Repository
+end
+
 class SysApi < ActionWebService::API::Base
-  api_method :projects,
+  api_method :projects_with_repository_enabled,
              :expects => [],
-             :returns => [[Project]]
+             :returns => [[AWSProjectWithRepository]]
   api_method :repository_created,
-             :expects => [:string, :string],
+             :expects => [:string, :string, :string],
              :returns => [:int]
 end
index 6065c28339ff8c79daeadf7e2994cffa97c7869e..8aff3bd15a35e23089548d1b7251c9aeb5acbe3b 100644 (file)
@@ -23,18 +23,17 @@ class SysController < ActionController::Base
   before_invocation :check_enabled
   
   # Returns the projects list, with their repositories
-  def projects
-    Project.find(:all, :include => :repository)
+  def projects_with_repository_enabled
+    Project.has_module(:repository).find(:all, :include => :repository, :order => 'identifier')
   end
 
   # Registers a repository for the given project identifier
-  # (Subversion specific)
-  def repository_created(identifier, url)
+  def repository_created(identifier, vendor, url)
     project = Project.find_by_identifier(identifier)
     # Do not create the repository if the project has already one
     return 0 unless project && project.repository.nil?
     logger.debug "Repository for #{project.name} was created"
-    repository = Repository.factory('Subversion', :project => project, :url => url)
+    repository = Repository.factory(vendor, :project => project, :url => url)
     repository.save
     repository.id || 0
   end
index adc70c644984df8009a73ca01ece416f03ee5932..e40af9967f99673c49b310a835bbff166671dba5 100644 (file)
@@ -62,6 +62,8 @@ class Project < ActiveRecord::Base
   validates_format_of :identifier, :with => /^[a-z0-9\-]*$/
   
   before_destroy :delete_all_members
+
+  named_scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } }
   
   def identifier=(identifier)
     super unless identifier_frozen?
index 6f1fb476868e7288edb4e03d5351ad34dac0cea5..5ff9470f91f2348f71f8f2b72b78905a7ec6e400 100644 (file)
@@ -19,6 +19,11 @@ class Role < ActiveRecord::Base
   # Built-in roles
   BUILTIN_NON_MEMBER = 1
   BUILTIN_ANONYMOUS  = 2
+
+  named_scope :builtin, lambda { |*args|
+    compare = 'not' if args.first == true
+    { :conditions => "#{compare} builtin = 0" }
+  }
   
   before_destroy :check_deletable
   has_many :workflows, :dependent => :delete_all do
@@ -36,7 +41,7 @@ class Role < ActiveRecord::Base
   has_many :members
   acts_as_list
   
-  serialize :permissions
+  serialize :permissions, Array
   attr_protected :builtin
 
   validates_presence_of :name
@@ -49,9 +54,27 @@ class Role < ActiveRecord::Base
   end
   
   def permissions=(perms)
-    perms = perms.collect {|p| p.to_sym unless p.blank? }.compact if perms
+    perms = perms.collect {|p| p.to_sym unless p.blank? }.compact.uniq if perms
     write_attribute(:permissions, perms)
   end
+
+  def add_permission!(*perms)
+    self.permissions = [] unless permissions.is_a?(Array)
+
+    permissions_will_change!
+    perms.each do |p|
+      p = p.to_sym
+      permissions << p unless permissions.include?(p)
+    end
+    save!
+  end
+
+  def remove_permission!(*perms)
+    return unless permissions.is_a?(Array)
+    permissions_will_change!
+    perms.each { |p| permissions.delete(p.to_sym) }
+    save!
+  end
   
   def <=>(role)
     position <=> role.position
diff --git a/db/migrate/096_add_commit_access_permission.rb b/db/migrate/096_add_commit_access_permission.rb
new file mode 100644 (file)
index 0000000..f73af2c
--- /dev/null
@@ -0,0 +1,14 @@
+class AddCommitAccessPermission < ActiveRecord::Migration
+
+  def self.up
+       Role.find(:all).select { |r| not r.builtin? }.each do |r|
+            r.add_permission!(:commit_access)
+       end
+  end
+
+  def self.down
+       Role.find(:all).select { |r| not r.builtin? }.each do |r|
+            r.remove_permission!(:commit_access)
+       end
+  end
+end
index 2619196c7a1e77c32251f30c70f3e9047209af26..a15b482e80e130714a47d7de7a985df502080619 100644 (file)
@@ -148,11 +148,12 @@ sub RedmineDSN {
   my ($self, $parms, $arg) = @_;
   $self->{RedmineDSN} = $arg;
   my $query = "SELECT 
-                 hashed_password, auth_source_id 
-              FROM members, projects, users 
+                 hashed_password, auth_source_id, permissions
+              FROM members, projects, users, roles
               WHERE 
                 projects.id=members.project_id 
                 AND users.id=members.user_id 
+                AND roles.id=members.role_id
                 AND users.status=1 
                 AND login=? 
                 AND identifier=? ";
@@ -277,9 +278,11 @@ sub is_member {
   $sth->execute($redmine_user, $project_id);
 
   my $ret;
-  while (my @row = $sth->fetchrow_array) {
-      unless ($row[1]) {
-          if ($row[0] eq $pass_digest) {
+  while (my ($hashed_password, $auth_source_id, $permissions) = $sth->fetchrow_array) {
+
+      unless ($auth_source_id) {
+         my $method = $r->method;
+          if ($hashed_password eq $pass_digest && (defined $read_only_methods{$method} || $permissions =~ /:commit_access/) ) {
               $ret = 1;
               last;
           }
@@ -287,7 +290,7 @@ sub is_member {
           my $sthldap = $dbh->prepare(
               "SELECT host,port,tls,account,account_password,base_dn,attr_login from auth_sources WHERE id = ?;"
           );
-          $sthldap->execute($row[1]);
+          $sthldap->execute($auth_source_id);
           while (my @rowldap = $sthldap->fetchrow_array) {
             my $ldap = Authen::Simple::LDAP->new(
                 host    =>      ($rowldap[2] == 1 || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]" : $rowldap[0],
diff --git a/extra/svn/reposman.pl b/extra/svn/reposman.pl
deleted file mode 100755 (executable)
index b8ce8f8..0000000
+++ /dev/null
@@ -1,142 +0,0 @@
-#!/usr/bin/perl
-#
-# redMine is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-# 
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-# 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-
-use strict;
-use SOAP::Lite;
-use Getopt::Long;
-Getopt::Long::Configure ("bundling", "no_auto_abbrev", "no_ignore_case");
-use Pod::Usage;
-use vars qw/$VERSION/;
-
-$VERSION = "1.0";
-
-my $warning = "This program is now deprecated. Use the reposman.rb for new features";
-print STDERR "*" x length($warning), "\n",
-    $warning, "\n",
-    "*" x length($warning), "\n\n";
-
-my %opts = (verbose => 0);
-GetOptions(\%opts, 'verbose|v+', 'version|V', 'help|h', 'man|m', 'quiet|q', 'svn-dir|s=s', 'redmine-host|r=s') or pod2usage(2);
-
-die "$VERSION\n"           if $opts{version};
-pod2usage(1)               if $opts{help};
-pod2usage( -verbose => 2 ) if $opts{man};
-
-my $repos_base = $opts{'svn-dir'};
-my $redmine_host = $opts{'redmine-host'};
-
-pod2usage(2) unless $repos_base and $redmine_host;
-
-unless (-d $repos_base) {
-    Log(text => "$repos_base doesn't exist", exit => 1);
-}
-
-Log(level => 1, text => "querying redMine for projects...");
-my $wdsl = "http://$redmine_host/sys/service.wsdl";
-my $service = SOAP::Lite->service($wdsl);
-
-my $projects = $service->Projects('');
-my $project_count = @{$projects};
-Log(level => 1, text => "retrieved $project_count projects");
-
-foreach my $project (@{$projects}) {
-    Log(level => 1, text => "treating project $project->{name}");
-    my $repos_name = $project->{identifier};
-
-    if ($repos_name eq "") {
-        Log(text => "\tno identifier for project $project->{name}");
-        next;
-    }
-
-    unless ($repos_name =~ /^[a-z0-9\-]+$/) {
-        Log(text => "\tinvalid identifier for project $project->{name}");
-        next;
-    }
-
-    my $repos_path = "$repos_base/$repos_name";
-
-    if (-e $repos_path) {
-       # check unix right and change them if needed
-       my $other_read = (stat($repos_path))[2] & 00007;
-           my $right;
-
-           if ($project->{is_public} and not $other_read) {
-               $right = "0775";
-           } elsif (not $project->{is_public} and $other_read) {
-               $right = "0770";
-           } else {
-               next;
-           }
-               
-               # change mode   
-           system('chmod', '-R', $right, $repos_path) == 0 or
-               warn("\tunable to change mode on $repos_path : $?\n"), next;
-       
-           Log(text => "\tmode change on $repos_path");
-           
-    } else {    
-           # change umask to suit the repository's privacy
-           $project->{is_public} ? umask 0002 : umask 0007;
-           
-               # create the repository
-           system('svnadmin', 'create', $repos_path) == 0 or
-               warn("\tsystem svnadmin failed unable to create $repos_path\n"), next;
-               
-               # set the group owner
-           system('chown', '-R', "root:$repos_name", $repos_path) == 0 or
-               warn("\tunable to create $repos_path : $?\n"), next;
-
-           Log(text => "\trepository $repos_path created");
-       }
-}
-
-
-sub Log {
-    my %args = (level => 0, text => '', @_);
-
-    my $level = delete $args{level};
-    my $text  = delete $args{text};
-    return unless $level <= $opts{verbose};
-    return if $opts{quiet};
-    print "$text\n";
-
-    exit $args{exit}
-        if defined $args{exit};
-}
-
-
-__END__
-
-=head1 NAME
-
- reposman - manages your svn repositories with redMine
-
-=head1 SYNOPSIS
-
- reposman [options] arguments
- example: reposman --svn-dir=/var/svn --redmine-host=redmine.mydomain.foo
-          reposman -s /var/svn -r redmine.mydomain.foo
-
-=head1 ARGUMENTS
-
- -s, --svn-dir=DIR        use DIR as base directory for svn repositories
- -r, --redmine-host=HOST  assume redMine is hosted on HOST
-
-=head1 OPTIONS
-
- -v                       verbose
- -V                       print version and exit
-
index 0b476cdc46b6291fc0b3d255f4b39a52d225b864..76804d65066ec9393e6a82ffcc0d09ae6cb8b50e 100755 (executable)
@@ -6,52 +6,49 @@
 #
 # == Usage
 #
-#     reposman [ -h | --help ] [ -v | --verbose ] [ -V | --version ] [ -q | --quiet ] -s /var/svn -r redmine.host.org
-#     example: reposman --svn-dir=/var/svn --redmine-host=redmine.mydomain.foo
-#              reposman -s /var/svn -r redmine.mydomain.foo
+#    reposman [OPTIONS...] -s [DIR] -r [HOST]
+#     
+#  Examples:
+#    reposman --svn-dir=/var/svn --redmine-host=redmine.example.net
+#    reposman -s /var/svn -r redmine.example.net -u http://svn.example.net
 #
 # == Arguments (mandatory)
-# 
-# -s, --svn-dir=DIR
-#    use DIR as base directory for svn repositories
 #
-# -r, --redmine-host=HOST
-#    assume Redmine is hosted on HOST.
-#    you can use :
-#    * -r redmine.mydomain.foo        (will add http://)
-#    * -r http://redmine.mydomain.foo
-#    * -r https://mydomain.foo/redmine
+#   -s, --svn-dir=DIR         use DIR as base directory for svn repositories
+#   -r, --redmine-host=HOST   assume Redmine is hosted on HOST. Examples:
+#                             -r redmine.example.net
+#                             -r http://redmine.example.net
+#                             -r https://example.net/redmine
 #
 # == Options
 #
-# -o, --owner=OWNER
-#    owner of the repository. using the rails login allow user to browse
-#    the repository in Redmine even for private project
-#
-# -u, --url=URL
-#    the base url Redmine will use to access your repositories. This
-#    will be used to register the repository in Redmine so that user
-#    doesn't need to do anything. reposman will add the identifier to this url :
-#
-#    -u https://my.svn.server/my/reposity/root # if the repository can be access by http
-#    -u file:///var/svn/                       # if the repository is local
-#    if this option isn't set, reposman won't register the repository
-#
-# -t, --test
-#    only show what should be done
-#
-# -h, --help:
-#    show help and exit
-#
-# -v, --verbose
-#    verbose
-#
-# -V, --version
-#    print version and exit
-#
-# -q, --quiet
-#    no log
-#
+#   -o, --owner=OWNER         owner of the repository. using the rails login
+#                             allow user to browse the repository within
+#                             Redmine even for private project
+#   -u, --url=URL             the base url Redmine will use to access your
+#                             repositories. This option is used to automatically
+#                             register the repositories in Redmine. The project
+#                             identifier will be appended to this url. Examples:
+#                             -u https://example.net/svn
+#                             -u file:///var/svn/
+#                             if this option isn't set, reposman won't register
+#                             the repositories in Redmine
+#   -c, --command=COMMAND     use this command instead of "svnadmin create" to
+#                             create a repository. This option can be used to
+#                             create non-subversion repositories
+#       --scm                 SCM vendor used to register the repository in
+#                             Redmine (default: Subversion). Can be one of the
+#                             other supported SCM: Bazaar, Darcs, Filesystem,
+#                             Git, Mercurial (case sensitive).
+#                             This option should be used when both options --url
+#                             and --command are used.
+#   -f, --force               force repository creation even if the project
+#                             repository is already declared in Redmine
+#   -t, --test                only show what should be done
+#   -h, --help                show help and exit
+#   -v, --verbose             verbose
+#   -V, --version             print version and exit
+#   -q, --quiet               no log
 
 require 'getoptlong'
 require 'rdoc/usage'
@@ -59,14 +56,18 @@ require 'soap/wsdlDriver'
 require 'find'
 require 'etc'
 
-Version = "1.0"
+Version = "1.1"
+SUPPORTED_SCM = %w( Subversion Darcs Mercurial Bazaar Git Filesystem )
 
 opts = GetoptLong.new(
                       ['--svn-dir',      '-s', GetoptLong::REQUIRED_ARGUMENT],
                       ['--redmine-host', '-r', GetoptLong::REQUIRED_ARGUMENT],
                       ['--owner',        '-o', GetoptLong::REQUIRED_ARGUMENT],
                       ['--url',          '-u', GetoptLong::REQUIRED_ARGUMENT],
+                      ['--command' ,     '-c', GetoptLong::REQUIRED_ARGUMENT],
+                      ['--scm',                GetoptLong::REQUIRED_ARGUMENT],
                       ['--test',         '-t', GetoptLong::NO_ARGUMENT],
+                      ['--force',        '-f', GetoptLong::NO_ARGUMENT],
                       ['--verbose',      '-v', GetoptLong::NO_ARGUMENT],
                       ['--version',      '-V', GetoptLong::NO_ARGUMENT],
                       ['--help'   ,      '-h', GetoptLong::NO_ARGUMENT],
@@ -81,6 +82,9 @@ $svn_owner    = 'root'
 $use_groupid  = true
 $svn_url      = false
 $test         = false
+$command      = "svnadmin create"
+$force        = false
+$scm          = 'Subversion'
 
 def log(text,level=0, exit=false)
   return if $quiet or level > $verbose
@@ -95,8 +99,11 @@ begin
     when '--redmine-host';   $redmine_host = arg.dup
     when '--owner';          $svn_owner    = arg.dup; $use_groupid = false;
     when '--url';            $svn_url      = arg.dup
+    when '--scm';            $scm          = arg.dup; log("Invalid SCM: #{$scm}", 0, true) unless SUPPORTED_SCM.include?($scm)
+    when '--command';        $command =      arg.dup
     when '--verbose';        $verbose += 1
     when '--test';           $test = true
+    when '--force';          $force = true
     when '--version';        puts Version; exit
     when '--help';           RDoc::usage
     when '--quiet';          $quiet = true
@@ -110,6 +117,12 @@ if $test
   log("running in test mode")
 end
 
+# Make sure command is overridden if SCM vendor is not Subversion
+if $scm != 'Subversion' && $command == 'svnadmin create'
+  log("Please use --command option to specify how to create a #{$scm} repository.", 0, true)
+end
+
+
 $svn_url += "/" if $svn_url and not $svn_url.match(/\/$/)
 
 if ($redmine_host.empty? or $repos_base.empty?)
@@ -133,7 +146,7 @@ rescue => e
   log("Unable to connect to #{wsdl_url} : #{e}", 0, true)
 end
 
-projects = soap.Projects
+projects = soap.ProjectsWithRepositoryEnabled
 
 if projects.nil?
   log('no project found, perhaps you forgot to "Enable WS for repository management"', 0, true)
@@ -201,6 +214,13 @@ projects.each do |project|
     log("\tmode change on #{repos_path}");
 
   else
+    # if repository is already declared in redmine, we don't create
+    # unless user use -f with reposman
+    if $force == false and not project.repository.nil?
+      log("\trepository for project #{project.identifier} already exists in Redmine", 1)
+      next
+    end
+
     project.is_public ? File.umask(0002) : File.umask(0007)
 
     if $test
@@ -211,7 +231,8 @@ projects.each do |project|
 
     begin
       set_owner_and_rights(project, repos_path) do
-        raise "svnadmin create #{repos_path} failed" unless system("svnadmin", "create", repos_path)
+        command = "#{$command} #{repos_path}"
+        raise "#{command} failed" unless system( command  )
       end
     rescue => e
       log("\tunable to create #{repos_path} : #{e}\n")
@@ -219,7 +240,7 @@ projects.each do |project|
     end
 
     if $svn_url
-      ret = soap.RepositoryCreated project.identifier, "#{$svn_url}#{project.identifier}"
+      ret = soap.RepositoryCreated project.identifier, $scm, "#{$svn_url}#{project.identifier}"
       if ret > 0
         log("\trepository #{repos_path} registered in Redmine with url #{$svn_url}#{project.identifier}");
       else
index 8045f30f1db66a3e438c4c7e7e513d5d2d4e1287..fdacb23b90f36abe215e2dc3882d06cfcd497ad3 100644 (file)
@@ -88,6 +88,7 @@ Redmine::AccessControl.map do |map|
     map.permission :manage_repository, {:repositories => [:edit, :destroy]}, :require => :member
     map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
     map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
+    map.permission :commit_access, {}
   end
 
   map.project_module :boards do |map|
index 11bd2a0b4086038f60a51945384bfe8d0f2a600c..dd3b9e7ecadc69ae15ba549a3bf49f932c9671a7 100644 (file)
@@ -67,7 +67,8 @@ module Redmine
                                                       :view_files,
                                                       :manage_files,
                                                       :browse_repository,
-                                                      :view_changesets]
+                                                      :view_changesets,
+                                                      :commit_access]
             
             reporter = Role.create! :name => l(:default_role_reporter),
                                     :position => 3,
index ec8d0964ec7a12af14cb8b77d93b592eb95ff07d..48ed780d0f096edb44b18944b3401388961843bb 100644 (file)
@@ -5,7 +5,7 @@ require 'sys_controller'
 class SysController; def rescue_action(e) raise e end; end
 
 class SysControllerTest < Test::Unit::TestCase
-  fixtures :projects, :repositories
+  fixtures :projects, :enabled_modules, :repositories
   
   def setup
     @controller = SysController.new
@@ -15,17 +15,36 @@ class SysControllerTest < Test::Unit::TestCase
     Setting.sys_api_enabled = 1
   end
   
-  def test_projects
-    result = invoke :projects
-    assert_equal Project.count, result.size 
-    assert result.first.is_a?(Project)
+  def test_projects_with_repository_enabled
+    result = invoke :projects_with_repository_enabled
+    assert_equal EnabledModule.count(:all, :conditions => {:name => 'repository'}), result.size
+    
+    project = result.first
+    assert project.is_a?(AWSProjectWithRepository)
+    
+    assert project.respond_to?(:id)
+    assert_equal 1, project.id
+    
+    assert project.respond_to?(:identifier)
+    assert_equal 'ecookbook', project.identifier
+    
+    assert project.respond_to?(:name)
+    assert_equal 'eCookbook', project.name
+    
+    assert project.respond_to?(:is_public)
+    assert project.is_public
+    
+    assert project.respond_to?(:repository)
+    assert project.repository.is_a?(Repository)
   end
 
   def test_repository_created
     project = Project.find(3)
     assert_nil project.repository
-    assert invoke(:repository_created, project.identifier, 'http://localhost/svn')
+    assert invoke(:repository_created, project.identifier, 'Subversion', 'http://localhost/svn')
     project.reload
     assert_not_nil project.repository
+    assert project.repository.is_a?(Repository::Subversion)
+    assert_equal 'http://localhost/svn', project.repository.url
   end
 end
index b98af2e362eaa4150bcf39bc70f6ea1a8b66099a..cab668c504f6d71d1f57a2068721e6f78e3887b0 100644 (file)
@@ -30,4 +30,24 @@ class RoleTest < Test::Unit::TestCase
     target.reload
     assert_equal 90, target.workflows.size
   end
+
+  def test_add_permission
+    role = Role.find(1)
+    size = role.permissions.size
+    role.add_permission!("apermission", "anotherpermission")
+    role.reload
+    assert role.permissions.include?(:anotherpermission)
+    assert_equal size + 2, role.permissions.size
+  end
+
+  def test_remove_permission
+    role = Role.find(1)
+    size = role.permissions.size
+    perm = role.permissions[0..1]
+    role.remove_permission!(*perm)
+    role.reload
+    assert ! role.permissions.include?(perm[0])
+    assert_equal size - 2, role.permissions.size
+  end
+
 end