##// END OF EJS Templates
Merged nbc branch @ r1812 (commit access permission and reposman improvements)....
Jean-Philippe Lang -
r1812:cc643ce932b2
parent child
Show More
@@ -0,0 +1,14
1 class AddCommitAccessPermission < ActiveRecord::Migration
2
3 def self.up
4 Role.find(:all).select { |r| not r.builtin? }.each do |r|
5 r.add_permission!(:commit_access)
6 end
7 end
8
9 def self.down
10 Role.find(:all).select { |r| not r.builtin? }.each do |r|
11 r.remove_permission!(:commit_access)
12 end
13 end
14 end
@@ -1,25 +1,33
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
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 class AWSProjectWithRepository < ActionWebService::Struct
19 member :id, :int
20 member :identifier, :string
21 member :name, :string
22 member :is_public, :bool
23 member :repository, Repository
24 end
25
18 26 class SysApi < ActionWebService::API::Base
19 api_method :projects,
27 api_method :projects_with_repository_enabled,
20 28 :expects => [],
21 :returns => [[Project]]
29 :returns => [[AWSProjectWithRepository]]
22 30 api_method :repository_created,
23 :expects => [:string, :string],
31 :expects => [:string, :string, :string],
24 32 :returns => [:int]
25 33 end
@@ -1,47 +1,46
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
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 18 class SysController < ActionController::Base
19 19 wsdl_service_name 'Sys'
20 20 web_service_api SysApi
21 21 web_service_scaffold :invoke
22 22
23 23 before_invocation :check_enabled
24 24
25 25 # Returns the projects list, with their repositories
26 def projects
27 Project.find(:all, :include => :repository)
26 def projects_with_repository_enabled
27 Project.has_module(:repository).find(:all, :include => :repository, :order => 'identifier')
28 28 end
29 29
30 30 # Registers a repository for the given project identifier
31 # (Subversion specific)
32 def repository_created(identifier, url)
31 def repository_created(identifier, vendor, url)
33 32 project = Project.find_by_identifier(identifier)
34 33 # Do not create the repository if the project has already one
35 34 return 0 unless project && project.repository.nil?
36 35 logger.debug "Repository for #{project.name} was created"
37 repository = Repository.factory('Subversion', :project => project, :url => url)
36 repository = Repository.factory(vendor, :project => project, :url => url)
38 37 repository.save
39 38 repository.id || 0
40 39 end
41 40
42 41 protected
43 42
44 43 def check_enabled(name, args)
45 44 Setting.sys_api_enabled?
46 45 end
47 46 end
@@ -1,266 +1,268
1 1 # redMine - project management software
2 2 # Copyright (C) 2006 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
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 18 class Project < ActiveRecord::Base
19 19 # Project statuses
20 20 STATUS_ACTIVE = 1
21 21 STATUS_ARCHIVED = 9
22 22
23 23 has_many :members, :include => :user, :conditions => "#{User.table_name}.status=#{User::STATUS_ACTIVE}"
24 24 has_many :users, :through => :members
25 25 has_many :enabled_modules, :dependent => :delete_all
26 26 has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
27 27 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
28 28 has_many :issue_changes, :through => :issues, :source => :journals
29 29 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
30 30 has_many :time_entries, :dependent => :delete_all
31 31 has_many :queries, :dependent => :delete_all
32 32 has_many :documents, :dependent => :destroy
33 33 has_many :news, :dependent => :delete_all, :include => :author
34 34 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
35 35 has_many :boards, :dependent => :destroy, :order => "position ASC"
36 36 has_one :repository, :dependent => :destroy
37 37 has_many :changesets, :through => :repository
38 38 has_one :wiki, :dependent => :destroy
39 39 # Custom field for the project issues
40 40 has_and_belongs_to_many :issue_custom_fields,
41 41 :class_name => 'IssueCustomField',
42 42 :order => "#{CustomField.table_name}.position",
43 43 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
44 44 :association_foreign_key => 'custom_field_id'
45 45
46 46 acts_as_tree :order => "name", :counter_cache => true
47 47
48 48 acts_as_customizable
49 49 acts_as_searchable :columns => ['name', 'description'], :project_key => 'id', :permission => nil
50 50 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
51 51 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o.id}},
52 52 :author => nil
53 53
54 54 attr_protected :status, :enabled_module_names
55 55
56 56 validates_presence_of :name, :identifier
57 57 validates_uniqueness_of :name, :identifier
58 58 validates_associated :repository, :wiki
59 59 validates_length_of :name, :maximum => 30
60 60 validates_length_of :homepage, :maximum => 255
61 61 validates_length_of :identifier, :in => 3..20
62 62 validates_format_of :identifier, :with => /^[a-z0-9\-]*$/
63 63
64 64 before_destroy :delete_all_members
65
66 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] } }
65 67
66 68 def identifier=(identifier)
67 69 super unless identifier_frozen?
68 70 end
69 71
70 72 def identifier_frozen?
71 73 errors[:identifier].nil? && !(new_record? || identifier.blank?)
72 74 end
73 75
74 76 def issues_with_subprojects(include_subprojects=false)
75 77 conditions = nil
76 78 if include_subprojects
77 79 ids = [id] + child_ids
78 80 conditions = ["#{Project.table_name}.id IN (#{ids.join(',')}) AND #{Project.visible_by}"]
79 81 end
80 82 conditions ||= ["#{Project.table_name}.id = ?", id]
81 83 # Quick and dirty fix for Rails 2 compatibility
82 84 Issue.send(:with_scope, :find => { :conditions => conditions }) do
83 85 Version.send(:with_scope, :find => { :conditions => conditions }) do
84 86 yield
85 87 end
86 88 end
87 89 end
88 90
89 91 # returns latest created projects
90 92 # non public projects will be returned only if user is a member of those
91 93 def self.latest(user=nil, count=5)
92 94 find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
93 95 end
94 96
95 97 def self.visible_by(user=nil)
96 98 user ||= User.current
97 99 if user && user.admin?
98 100 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
99 101 elsif user && user.memberships.any?
100 102 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND (#{Project.table_name}.is_public = #{connection.quoted_true} or #{Project.table_name}.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')}))"
101 103 else
102 104 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
103 105 end
104 106 end
105 107
106 108 def self.allowed_to_condition(user, permission, options={})
107 109 statements = []
108 110 base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
109 111 if options[:project]
110 112 project_statement = "#{Project.table_name}.id = #{options[:project].id}"
111 113 project_statement << " OR #{Project.table_name}.parent_id = #{options[:project].id}" if options[:with_subprojects]
112 114 base_statement = "(#{project_statement}) AND (#{base_statement})"
113 115 end
114 116 if user.admin?
115 117 # no restriction
116 118 else
117 119 statements << "1=0"
118 120 if user.logged?
119 121 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" if Role.non_member.allowed_to?(permission)
120 122 allowed_project_ids = user.memberships.select {|m| m.role.allowed_to?(permission)}.collect {|m| m.project_id}
121 123 statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any?
122 124 elsif Role.anonymous.allowed_to?(permission)
123 125 # anonymous user allowed on public project
124 126 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
125 127 else
126 128 # anonymous user is not authorized
127 129 end
128 130 end
129 131 statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))"
130 132 end
131 133
132 134 def project_condition(with_subprojects)
133 135 cond = "#{Project.table_name}.id = #{id}"
134 136 cond = "(#{cond} OR #{Project.table_name}.parent_id = #{id})" if with_subprojects
135 137 cond
136 138 end
137 139
138 140 def self.find(*args)
139 141 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
140 142 project = find_by_identifier(*args)
141 143 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
142 144 project
143 145 else
144 146 super
145 147 end
146 148 end
147 149
148 150 def to_param
149 151 # id is used for projects with a numeric identifier (compatibility)
150 152 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id : identifier)
151 153 end
152 154
153 155 def active?
154 156 self.status == STATUS_ACTIVE
155 157 end
156 158
157 159 def archive
158 160 # Archive subprojects if any
159 161 children.each do |subproject|
160 162 subproject.archive
161 163 end
162 164 update_attribute :status, STATUS_ARCHIVED
163 165 end
164 166
165 167 def unarchive
166 168 return false if parent && !parent.active?
167 169 update_attribute :status, STATUS_ACTIVE
168 170 end
169 171
170 172 def active_children
171 173 children.select {|child| child.active?}
172 174 end
173 175
174 176 # Returns an array of the trackers used by the project and its sub projects
175 177 def rolled_up_trackers
176 178 @rolled_up_trackers ||=
177 179 Tracker.find(:all, :include => :projects,
178 180 :select => "DISTINCT #{Tracker.table_name}.*",
179 181 :conditions => ["#{Project.table_name}.id = ? OR #{Project.table_name}.parent_id = ?", id, id],
180 182 :order => "#{Tracker.table_name}.position")
181 183 end
182 184
183 185 # Deletes all project's members
184 186 def delete_all_members
185 187 Member.delete_all(['project_id = ?', id])
186 188 end
187 189
188 190 # Users issues can be assigned to
189 191 def assignable_users
190 192 members.select {|m| m.role.assignable?}.collect {|m| m.user}.sort
191 193 end
192 194
193 195 # Returns the mail adresses of users that should be always notified on project events
194 196 def recipients
195 197 members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user.mail}
196 198 end
197 199
198 200 # Returns an array of all custom fields enabled for project issues
199 201 # (explictly associated custom fields and custom fields enabled for all projects)
200 202 def all_issue_custom_fields
201 203 @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort
202 204 end
203 205
204 206 def project
205 207 self
206 208 end
207 209
208 210 def <=>(project)
209 211 name.downcase <=> project.name.downcase
210 212 end
211 213
212 214 def to_s
213 215 name
214 216 end
215 217
216 218 # Returns a short description of the projects (first lines)
217 219 def short_description(length = 255)
218 220 description.gsub(/^(.{#{length}}[^\n]*).*$/m, '\1').strip if description
219 221 end
220 222
221 223 def allows_to?(action)
222 224 if action.is_a? Hash
223 225 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
224 226 else
225 227 allowed_permissions.include? action
226 228 end
227 229 end
228 230
229 231 def module_enabled?(module_name)
230 232 module_name = module_name.to_s
231 233 enabled_modules.detect {|m| m.name == module_name}
232 234 end
233 235
234 236 def enabled_module_names=(module_names)
235 237 enabled_modules.clear
236 238 module_names = [] unless module_names && module_names.is_a?(Array)
237 239 module_names.each do |name|
238 240 enabled_modules << EnabledModule.new(:name => name.to_s)
239 241 end
240 242 end
241 243
242 244 # Returns an auto-generated project identifier based on the last identifier used
243 245 def self.next_identifier
244 246 p = Project.find(:first, :order => 'created_on DESC')
245 247 p.nil? ? nil : p.identifier.to_s.succ
246 248 end
247 249
248 250 protected
249 251 def validate
250 252 errors.add(parent_id, " must be a root project") if parent and parent.parent
251 253 errors.add_to_base("A project with subprojects can't be a subproject") if parent and children.size > 0
252 254 errors.add(:identifier, :activerecord_error_invalid) if !identifier.blank? && identifier.match(/^\d*$/)
253 255 end
254 256
255 257 private
256 258 def allowed_permissions
257 259 @allowed_permissions ||= begin
258 260 module_names = enabled_modules.collect {|m| m.name}
259 261 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
260 262 end
261 263 end
262 264
263 265 def allowed_actions
264 266 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
265 267 end
266 268 end
@@ -1,119 +1,142
1 1 # redMine - project management software
2 2 # Copyright (C) 2006 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
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 18 class Role < ActiveRecord::Base
19 19 # Built-in roles
20 20 BUILTIN_NON_MEMBER = 1
21 21 BUILTIN_ANONYMOUS = 2
22
23 named_scope :builtin, lambda { |*args|
24 compare = 'not' if args.first == true
25 { :conditions => "#{compare} builtin = 0" }
26 }
22 27
23 28 before_destroy :check_deletable
24 29 has_many :workflows, :dependent => :delete_all do
25 30 def copy(role)
26 31 raise "Can not copy workflow from a #{role.class}" unless role.is_a?(Role)
27 32 raise "Can not copy workflow from/to an unsaved role" if proxy_owner.new_record? || role.new_record?
28 33 clear
29 34 connection.insert "INSERT INTO workflows (tracker_id, old_status_id, new_status_id, role_id)" +
30 35 " SELECT tracker_id, old_status_id, new_status_id, #{proxy_owner.id}" +
31 36 " FROM workflows" +
32 37 " WHERE role_id = #{role.id}"
33 38 end
34 39 end
35 40
36 41 has_many :members
37 42 acts_as_list
38 43
39 serialize :permissions
44 serialize :permissions, Array
40 45 attr_protected :builtin
41 46
42 47 validates_presence_of :name
43 48 validates_uniqueness_of :name
44 49 validates_length_of :name, :maximum => 30
45 50 validates_format_of :name, :with => /^[\w\s\'\-]*$/i
46 51
47 52 def permissions
48 53 read_attribute(:permissions) || []
49 54 end
50 55
51 56 def permissions=(perms)
52 perms = perms.collect {|p| p.to_sym unless p.blank? }.compact if perms
57 perms = perms.collect {|p| p.to_sym unless p.blank? }.compact.uniq if perms
53 58 write_attribute(:permissions, perms)
54 59 end
60
61 def add_permission!(*perms)
62 self.permissions = [] unless permissions.is_a?(Array)
63
64 permissions_will_change!
65 perms.each do |p|
66 p = p.to_sym
67 permissions << p unless permissions.include?(p)
68 end
69 save!
70 end
71
72 def remove_permission!(*perms)
73 return unless permissions.is_a?(Array)
74 permissions_will_change!
75 perms.each { |p| permissions.delete(p.to_sym) }
76 save!
77 end
55 78
56 79 def <=>(role)
57 80 position <=> role.position
58 81 end
59 82
60 83 # Return true if the role is a builtin role
61 84 def builtin?
62 85 self.builtin != 0
63 86 end
64 87
65 88 # Return true if the role is a project member role
66 89 def member?
67 90 !self.builtin?
68 91 end
69 92
70 93 # Return true if role is allowed to do the specified action
71 94 # action can be:
72 95 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
73 96 # * a permission Symbol (eg. :edit_project)
74 97 def allowed_to?(action)
75 98 if action.is_a? Hash
76 99 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
77 100 else
78 101 allowed_permissions.include? action
79 102 end
80 103 end
81 104
82 105 # Return all the permissions that can be given to the role
83 106 def setable_permissions
84 107 setable_permissions = Redmine::AccessControl.permissions - Redmine::AccessControl.public_permissions
85 108 setable_permissions -= Redmine::AccessControl.members_only_permissions if self.builtin == BUILTIN_NON_MEMBER
86 109 setable_permissions -= Redmine::AccessControl.loggedin_only_permissions if self.builtin == BUILTIN_ANONYMOUS
87 110 setable_permissions
88 111 end
89 112
90 113 # Find all the roles that can be given to a project member
91 114 def self.find_all_givable
92 115 find(:all, :conditions => {:builtin => 0}, :order => 'position')
93 116 end
94 117
95 118 # Return the builtin 'non member' role
96 119 def self.non_member
97 120 find(:first, :conditions => {:builtin => BUILTIN_NON_MEMBER}) || raise('Missing non-member builtin role.')
98 121 end
99 122
100 123 # Return the builtin 'anonymous' role
101 124 def self.anonymous
102 125 find(:first, :conditions => {:builtin => BUILTIN_ANONYMOUS}) || raise('Missing anonymous builtin role.')
103 126 end
104 127
105 128
106 129 private
107 130 def allowed_permissions
108 131 @allowed_permissions ||= permissions + Redmine::AccessControl.public_permissions.collect {|p| p.name}
109 132 end
110 133
111 134 def allowed_actions
112 135 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
113 136 end
114 137
115 138 def check_deletable
116 139 raise "Can't delete role" if members.any?
117 140 raise "Can't delete builtin role" if builtin?
118 141 end
119 142 end
@@ -1,340 +1,343
1 1 package Apache::Authn::Redmine;
2 2
3 3 =head1 Apache::Authn::Redmine
4 4
5 5 Redmine - a mod_perl module to authenticate webdav subversion users
6 6 against redmine database
7 7
8 8 =head1 SYNOPSIS
9 9
10 10 This module allow anonymous users to browse public project and
11 11 registred users to browse and commit their project. Authentication is
12 12 done against the redmine database or the LDAP configured in redmine.
13 13
14 14 This method is far simpler than the one with pam_* and works with all
15 15 database without an hassle but you need to have apache/mod_perl on the
16 16 svn server.
17 17
18 18 =head1 INSTALLATION
19 19
20 20 For this to automagically work, you need to have a recent reposman.rb
21 21 (after r860) and if you already use reposman, read the last section to
22 22 migrate.
23 23
24 24 Sorry ruby users but you need some perl modules, at least mod_perl2,
25 25 DBI and DBD::mysql (or the DBD driver for you database as it should
26 26 work on allmost all databases).
27 27
28 28 On debian/ubuntu you must do :
29 29
30 30 aptitude install libapache-dbi-perl libapache2-mod-perl2 libdbd-mysql-perl
31 31
32 32 If your Redmine users use LDAP authentication, you will also need
33 33 Authen::Simple::LDAP (and IO::Socket::SSL if LDAPS is used):
34 34
35 35 aptitude install libauthen-simple-ldap-perl libio-socket-ssl-perl
36 36
37 37 =head1 CONFIGURATION
38 38
39 39 ## This module has to be in your perl path
40 40 ## eg: /usr/lib/perl5/Apache/Authn/Redmine.pm
41 41 PerlLoadModule Apache::Authn::Redmine
42 42 <Location /svn>
43 43 DAV svn
44 44 SVNParentPath "/var/svn"
45 45
46 46 AuthType Basic
47 47 AuthName redmine
48 48 Require valid-user
49 49
50 50 PerlAccessHandler Apache::Authn::Redmine::access_handler
51 51 PerlAuthenHandler Apache::Authn::Redmine::authen_handler
52 52
53 53 ## for mysql
54 54 RedmineDSN "DBI:mysql:database=databasename;host=my.db.server"
55 55 ## for postgres
56 56 # RedmineDSN "DBI:Pg:dbname=databasename;host=my.db.server"
57 57
58 58 RedmineDbUser "redmine"
59 59 RedmineDbPass "password"
60 60 ## Optional where clause (fulltext search would be slow and
61 61 ## database dependant).
62 62 # RedmineDbWhereClause "and members.role_id IN (1,2)"
63 63 ## Optional credentials cache size
64 64 # RedmineCacheCredsMax 50
65 65 </Location>
66 66
67 67 To be able to browse repository inside redmine, you must add something
68 68 like that :
69 69
70 70 <Location /svn-private>
71 71 DAV svn
72 72 SVNParentPath "/var/svn"
73 73 Order deny,allow
74 74 Deny from all
75 75 # only allow reading orders
76 76 <Limit GET PROPFIND OPTIONS REPORT>
77 77 Allow from redmine.server.ip
78 78 </Limit>
79 79 </Location>
80 80
81 81 and you will have to use this reposman.rb command line to create repository :
82 82
83 83 reposman.rb --redmine my.redmine.server --svn-dir /var/svn --owner www-data -u http://svn.server/svn-private/
84 84
85 85 =head1 MIGRATION FROM OLDER RELEASES
86 86
87 87 If you use an older reposman.rb (r860 or before), you need to change
88 88 rights on repositories to allow the apache user to read and write
89 89 S<them :>
90 90
91 91 sudo chown -R www-data /var/svn/*
92 92 sudo chmod -R u+w /var/svn/*
93 93
94 94 And you need to upgrade at least reposman.rb (after r860).
95 95
96 96 =cut
97 97
98 98 use strict;
99 99 use warnings FATAL => 'all', NONFATAL => 'redefine';
100 100
101 101 use DBI;
102 102 use Digest::SHA1;
103 103 # optional module for LDAP authentication
104 104 my $CanUseLDAPAuth = eval("use Authen::Simple::LDAP; 1");
105 105
106 106 use Apache2::Module;
107 107 use Apache2::Access;
108 108 use Apache2::ServerRec qw();
109 109 use Apache2::RequestRec qw();
110 110 use Apache2::RequestUtil qw();
111 111 use Apache2::Const qw(:common :override :cmd_how);
112 112 use APR::Pool ();
113 113 use APR::Table ();
114 114
115 115 # use Apache2::Directive qw();
116 116
117 117 my @directives = (
118 118 {
119 119 name => 'RedmineDSN',
120 120 req_override => OR_AUTHCFG,
121 121 args_how => TAKE1,
122 122 errmsg => 'Dsn in format used by Perl DBI. eg: "DBI:Pg:dbname=databasename;host=my.db.server"',
123 123 },
124 124 {
125 125 name => 'RedmineDbUser',
126 126 req_override => OR_AUTHCFG,
127 127 args_how => TAKE1,
128 128 },
129 129 {
130 130 name => 'RedmineDbPass',
131 131 req_override => OR_AUTHCFG,
132 132 args_how => TAKE1,
133 133 },
134 134 {
135 135 name => 'RedmineDbWhereClause',
136 136 req_override => OR_AUTHCFG,
137 137 args_how => TAKE1,
138 138 },
139 139 {
140 140 name => 'RedmineCacheCredsMax',
141 141 req_override => OR_AUTHCFG,
142 142 args_how => TAKE1,
143 143 errmsg => 'RedmineCacheCredsMax must be decimal number',
144 144 },
145 145 );
146 146
147 147 sub RedmineDSN {
148 148 my ($self, $parms, $arg) = @_;
149 149 $self->{RedmineDSN} = $arg;
150 150 my $query = "SELECT
151 hashed_password, auth_source_id
152 FROM members, projects, users
151 hashed_password, auth_source_id, permissions
152 FROM members, projects, users, roles
153 153 WHERE
154 154 projects.id=members.project_id
155 155 AND users.id=members.user_id
156 AND roles.id=members.role_id
156 157 AND users.status=1
157 158 AND login=?
158 159 AND identifier=? ";
159 160 $self->{RedmineQuery} = trim($query);
160 161 }
161 162 sub RedmineDbUser { set_val('RedmineDbUser', @_); }
162 163 sub RedmineDbPass { set_val('RedmineDbPass', @_); }
163 164 sub RedmineDbWhereClause {
164 165 my ($self, $parms, $arg) = @_;
165 166 $self->{RedmineQuery} = trim($self->{RedmineQuery}.($arg ? $arg : "")." ");
166 167 }
167 168
168 169 sub RedmineCacheCredsMax {
169 170 my ($self, $parms, $arg) = @_;
170 171 if ($arg) {
171 172 $self->{RedmineCachePool} = APR::Pool->new;
172 173 $self->{RedmineCacheCreds} = APR::Table::make($self->{RedmineCachePool}, $arg);
173 174 $self->{RedmineCacheCredsCount} = 0;
174 175 $self->{RedmineCacheCredsMax} = $arg;
175 176 }
176 177 }
177 178
178 179 sub trim {
179 180 my $string = shift;
180 181 $string =~ s/\s{2,}/ /g;
181 182 return $string;
182 183 }
183 184
184 185 sub set_val {
185 186 my ($key, $self, $parms, $arg) = @_;
186 187 $self->{$key} = $arg;
187 188 }
188 189
189 190 Apache2::Module::add(__PACKAGE__, \@directives);
190 191
191 192
192 193 my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/;
193 194
194 195 sub access_handler {
195 196 my $r = shift;
196 197
197 198 unless ($r->some_auth_required) {
198 199 $r->log_reason("No authentication has been configured");
199 200 return FORBIDDEN;
200 201 }
201 202
202 203 my $method = $r->method;
203 204 return OK unless defined $read_only_methods{$method};
204 205
205 206 my $project_id = get_project_identifier($r);
206 207
207 208 $r->set_handlers(PerlAuthenHandler => [\&OK])
208 209 if is_public_project($project_id, $r);
209 210
210 211 return OK
211 212 }
212 213
213 214 sub authen_handler {
214 215 my $r = shift;
215 216
216 217 my ($res, $redmine_pass) = $r->get_basic_auth_pw();
217 218 return $res unless $res == OK;
218 219
219 220 if (is_member($r->user, $redmine_pass, $r)) {
220 221 return OK;
221 222 } else {
222 223 $r->note_auth_failure();
223 224 return AUTH_REQUIRED;
224 225 }
225 226 }
226 227
227 228 sub is_public_project {
228 229 my $project_id = shift;
229 230 my $r = shift;
230 231
231 232 my $dbh = connect_database($r);
232 233 my $sth = $dbh->prepare(
233 234 "SELECT * FROM projects WHERE projects.identifier=? and projects.is_public=true;"
234 235 );
235 236
236 237 $sth->execute($project_id);
237 238 my $ret = $sth->fetchrow_array ? 1 : 0;
238 239 $sth->finish();
239 240 $dbh->disconnect();
240 241
241 242 $ret;
242 243 }
243 244
244 245 # perhaps we should use repository right (other read right) to check public access.
245 246 # it could be faster BUT it doesn't work for the moment.
246 247 # sub is_public_project_by_file {
247 248 # my $project_id = shift;
248 249 # my $r = shift;
249 250
250 251 # my $tree = Apache2::Directive::conftree();
251 252 # my $node = $tree->lookup('Location', $r->location);
252 253 # my $hash = $node->as_hash;
253 254
254 255 # my $svnparentpath = $hash->{SVNParentPath};
255 256 # my $repos_path = $svnparentpath . "/" . $project_id;
256 257 # return 1 if (stat($repos_path))[2] & 00007;
257 258 # }
258 259
259 260 sub is_member {
260 261 my $redmine_user = shift;
261 262 my $redmine_pass = shift;
262 263 my $r = shift;
263 264
264 265 my $dbh = connect_database($r);
265 266 my $project_id = get_project_identifier($r);
266 267
267 268 my $pass_digest = Digest::SHA1::sha1_hex($redmine_pass);
268 269
269 270 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
270 271 my $usrprojpass;
271 272 if ($cfg->{RedmineCacheCredsMax}) {
272 273 $usrprojpass = $cfg->{RedmineCacheCreds}->get($redmine_user.":".$project_id);
273 274 return 1 if (defined $usrprojpass and ($usrprojpass eq $pass_digest));
274 275 }
275 276 my $query = $cfg->{RedmineQuery};
276 277 my $sth = $dbh->prepare($query);
277 278 $sth->execute($redmine_user, $project_id);
278 279
279 280 my $ret;
280 while (my @row = $sth->fetchrow_array) {
281 unless ($row[1]) {
282 if ($row[0] eq $pass_digest) {
281 while (my ($hashed_password, $auth_source_id, $permissions) = $sth->fetchrow_array) {
282
283 unless ($auth_source_id) {
284 my $method = $r->method;
285 if ($hashed_password eq $pass_digest && (defined $read_only_methods{$method} || $permissions =~ /:commit_access/) ) {
283 286 $ret = 1;
284 287 last;
285 288 }
286 289 } elsif ($CanUseLDAPAuth) {
287 290 my $sthldap = $dbh->prepare(
288 291 "SELECT host,port,tls,account,account_password,base_dn,attr_login from auth_sources WHERE id = ?;"
289 292 );
290 $sthldap->execute($row[1]);
293 $sthldap->execute($auth_source_id);
291 294 while (my @rowldap = $sthldap->fetchrow_array) {
292 295 my $ldap = Authen::Simple::LDAP->new(
293 296 host => ($rowldap[2] == 1 || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]" : $rowldap[0],
294 297 port => $rowldap[1],
295 298 basedn => $rowldap[5],
296 299 binddn => $rowldap[3] ? $rowldap[3] : "",
297 300 bindpw => $rowldap[4] ? $rowldap[4] : "",
298 301 filter => "(".$rowldap[6]."=%s)"
299 302 );
300 303 $ret = 1 if ($ldap->authenticate($redmine_user, $redmine_pass));
301 304 }
302 305 $sthldap->finish();
303 306 }
304 307 }
305 308 $sth->finish();
306 309 $dbh->disconnect();
307 310
308 311 if ($cfg->{RedmineCacheCredsMax} and $ret) {
309 312 if (defined $usrprojpass) {
310 313 $cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id, $pass_digest);
311 314 } else {
312 315 if ($cfg->{RedmineCacheCredsCount} < $cfg->{RedmineCacheCredsMax}) {
313 316 $cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id, $pass_digest);
314 317 $cfg->{RedmineCacheCredsCount}++;
315 318 } else {
316 319 $cfg->{RedmineCacheCreds}->clear();
317 320 $cfg->{RedmineCacheCredsCount} = 0;
318 321 }
319 322 }
320 323 }
321 324
322 325 $ret;
323 326 }
324 327
325 328 sub get_project_identifier {
326 329 my $r = shift;
327 330
328 331 my $location = $r->location;
329 332 my ($identifier) = $r->uri =~ m{$location/*([^/]+)};
330 333 $identifier;
331 334 }
332 335
333 336 sub connect_database {
334 337 my $r = shift;
335 338
336 339 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
337 340 return DBI->connect($cfg->{RedmineDSN}, $cfg->{RedmineDbUser}, $cfg->{RedmineDbPass});
338 341 }
339 342
340 343 1;
@@ -1,234 +1,255
1 1 #!/usr/bin/ruby
2 2
3 3 # == Synopsis
4 4 #
5 5 # reposman: manages your svn repositories with Redmine
6 6 #
7 7 # == Usage
8 8 #
9 # reposman [ -h | --help ] [ -v | --verbose ] [ -V | --version ] [ -q | --quiet ] -s /var/svn -r redmine.host.org
10 # example: reposman --svn-dir=/var/svn --redmine-host=redmine.mydomain.foo
11 # reposman -s /var/svn -r redmine.mydomain.foo
9 # reposman [OPTIONS...] -s [DIR] -r [HOST]
10 #
11 # Examples:
12 # reposman --svn-dir=/var/svn --redmine-host=redmine.example.net
13 # reposman -s /var/svn -r redmine.example.net -u http://svn.example.net
12 14 #
13 15 # == Arguments (mandatory)
14 #
15 # -s, --svn-dir=DIR
16 # use DIR as base directory for svn repositories
17 16 #
18 # -r, --redmine-host=HOST
19 # assume Redmine is hosted on HOST.
20 # you can use :
21 # * -r redmine.mydomain.foo (will add http://)
22 # * -r http://redmine.mydomain.foo
23 # * -r https://mydomain.foo/redmine
17 # -s, --svn-dir=DIR use DIR as base directory for svn repositories
18 # -r, --redmine-host=HOST assume Redmine is hosted on HOST. Examples:
19 # -r redmine.example.net
20 # -r http://redmine.example.net
21 # -r https://example.net/redmine
24 22 #
25 23 # == Options
26 24 #
27 # -o, --owner=OWNER
28 # owner of the repository. using the rails login allow user to browse
29 # the repository in Redmine even for private project
30 #
31 # -u, --url=URL
32 # the base url Redmine will use to access your repositories. This
33 # will be used to register the repository in Redmine so that user
34 # doesn't need to do anything. reposman will add the identifier to this url :
35 #
36 # -u https://my.svn.server/my/reposity/root # if the repository can be access by http
37 # -u file:///var/svn/ # if the repository is local
38 # if this option isn't set, reposman won't register the repository
39 #
40 # -t, --test
41 # only show what should be done
42 #
43 # -h, --help:
44 # show help and exit
45 #
46 # -v, --verbose
47 # verbose
48 #
49 # -V, --version
50 # print version and exit
51 #
52 # -q, --quiet
53 # no log
54 #
25 # -o, --owner=OWNER owner of the repository. using the rails login
26 # allow user to browse the repository within
27 # Redmine even for private project
28 # -u, --url=URL the base url Redmine will use to access your
29 # repositories. This option is used to automatically
30 # register the repositories in Redmine. The project
31 # identifier will be appended to this url. Examples:
32 # -u https://example.net/svn
33 # -u file:///var/svn/
34 # if this option isn't set, reposman won't register
35 # the repositories in Redmine
36 # -c, --command=COMMAND use this command instead of "svnadmin create" to
37 # create a repository. This option can be used to
38 # create non-subversion repositories
39 # --scm SCM vendor used to register the repository in
40 # Redmine (default: Subversion). Can be one of the
41 # other supported SCM: Bazaar, Darcs, Filesystem,
42 # Git, Mercurial (case sensitive).
43 # This option should be used when both options --url
44 # and --command are used.
45 # -f, --force force repository creation even if the project
46 # repository is already declared in Redmine
47 # -t, --test only show what should be done
48 # -h, --help show help and exit
49 # -v, --verbose verbose
50 # -V, --version print version and exit
51 # -q, --quiet no log
55 52
56 53 require 'getoptlong'
57 54 require 'rdoc/usage'
58 55 require 'soap/wsdlDriver'
59 56 require 'find'
60 57 require 'etc'
61 58
62 Version = "1.0"
59 Version = "1.1"
60 SUPPORTED_SCM = %w( Subversion Darcs Mercurial Bazaar Git Filesystem )
63 61
64 62 opts = GetoptLong.new(
65 63 ['--svn-dir', '-s', GetoptLong::REQUIRED_ARGUMENT],
66 64 ['--redmine-host', '-r', GetoptLong::REQUIRED_ARGUMENT],
67 65 ['--owner', '-o', GetoptLong::REQUIRED_ARGUMENT],
68 66 ['--url', '-u', GetoptLong::REQUIRED_ARGUMENT],
67 ['--command' , '-c', GetoptLong::REQUIRED_ARGUMENT],
68 ['--scm', GetoptLong::REQUIRED_ARGUMENT],
69 69 ['--test', '-t', GetoptLong::NO_ARGUMENT],
70 ['--force', '-f', GetoptLong::NO_ARGUMENT],
70 71 ['--verbose', '-v', GetoptLong::NO_ARGUMENT],
71 72 ['--version', '-V', GetoptLong::NO_ARGUMENT],
72 73 ['--help' , '-h', GetoptLong::NO_ARGUMENT],
73 74 ['--quiet' , '-q', GetoptLong::NO_ARGUMENT]
74 75 )
75 76
76 77 $verbose = 0
77 78 $quiet = false
78 79 $redmine_host = ''
79 80 $repos_base = ''
80 81 $svn_owner = 'root'
81 82 $use_groupid = true
82 83 $svn_url = false
83 84 $test = false
85 $command = "svnadmin create"
86 $force = false
87 $scm = 'Subversion'
84 88
85 89 def log(text,level=0, exit=false)
86 90 return if $quiet or level > $verbose
87 91 puts text
88 92 exit 1 if exit
89 93 end
90 94
91 95 begin
92 96 opts.each do |opt, arg|
93 97 case opt
94 98 when '--svn-dir'; $repos_base = arg.dup
95 99 when '--redmine-host'; $redmine_host = arg.dup
96 100 when '--owner'; $svn_owner = arg.dup; $use_groupid = false;
97 101 when '--url'; $svn_url = arg.dup
102 when '--scm'; $scm = arg.dup; log("Invalid SCM: #{$scm}", 0, true) unless SUPPORTED_SCM.include?($scm)
103 when '--command'; $command = arg.dup
98 104 when '--verbose'; $verbose += 1
99 105 when '--test'; $test = true
106 when '--force'; $force = true
100 107 when '--version'; puts Version; exit
101 108 when '--help'; RDoc::usage
102 109 when '--quiet'; $quiet = true
103 110 end
104 111 end
105 112 rescue
106 113 exit 1
107 114 end
108 115
109 116 if $test
110 117 log("running in test mode")
111 118 end
112 119
120 # Make sure command is overridden if SCM vendor is not Subversion
121 if $scm != 'Subversion' && $command == 'svnadmin create'
122 log("Please use --command option to specify how to create a #{$scm} repository.", 0, true)
123 end
124
125
113 126 $svn_url += "/" if $svn_url and not $svn_url.match(/\/$/)
114 127
115 128 if ($redmine_host.empty? or $repos_base.empty?)
116 129 RDoc::usage
117 130 end
118 131
119 132 unless File.directory?($repos_base)
120 133 log("directory '#{$repos_base}' doesn't exists", 0, true)
121 134 end
122 135
123 136 log("querying Redmine for projects...", 1);
124 137
125 138 $redmine_host.gsub!(/^/, "http://") unless $redmine_host.match("^https?://")
126 139 $redmine_host.gsub!(/\/$/, '')
127 140
128 141 wsdl_url = "#{$redmine_host}/sys/service.wsdl";
129 142
130 143 begin
131 144 soap = SOAP::WSDLDriverFactory.new(wsdl_url).create_rpc_driver
132 145 rescue => e
133 146 log("Unable to connect to #{wsdl_url} : #{e}", 0, true)
134 147 end
135 148
136 projects = soap.Projects
149 projects = soap.ProjectsWithRepositoryEnabled
137 150
138 151 if projects.nil?
139 152 log('no project found, perhaps you forgot to "Enable WS for repository management"', 0, true)
140 153 end
141 154
142 155 log("retrieved #{projects.size} projects", 1)
143 156
144 157 def set_owner_and_rights(project, repos_path, &block)
145 158 if RUBY_PLATFORM =~ /mswin/
146 159 yield if block_given?
147 160 else
148 161 uid, gid = Etc.getpwnam($svn_owner).uid, ($use_groupid ? Etc.getgrnam(project.identifier).gid : 0)
149 162 right = project.is_public ? 0775 : 0770
150 163 yield if block_given?
151 164 Find.find(repos_path) do |f|
152 165 File.chmod right, f
153 166 File.chown uid, gid, f
154 167 end
155 168 end
156 169 end
157 170
158 171 def other_read_right?(file)
159 172 (File.stat(file).mode & 0007).zero? ? false : true
160 173 end
161 174
162 175 def owner_name(file)
163 176 RUBY_PLATFORM =~ /mswin/ ?
164 177 $svn_owner :
165 178 Etc.getpwuid( File.stat(file).uid ).name
166 179 end
167 180
168 181 projects.each do |project|
169 182 log("treating project #{project.name}", 1)
170 183
171 184 if project.identifier.empty?
172 185 log("\tno identifier for project #{project.name}")
173 186 next
174 187 elsif not project.identifier.match(/^[a-z0-9\-]+$/)
175 188 log("\tinvalid identifier for project #{project.name} : #{project.identifier}");
176 189 next;
177 190 end
178 191
179 192 repos_path = $repos_base + "/" + project.identifier
180 193
181 194 if File.directory?(repos_path)
182 195
183 196 # we must verify that repository has the good owner and the good
184 197 # rights before leaving
185 198 other_read = other_read_right?(repos_path)
186 199 owner = owner_name(repos_path)
187 200 next if project.is_public == other_read and owner == $svn_owner
188 201
189 202 if $test
190 203 log("\tchange mode on #{repos_path}")
191 204 next
192 205 end
193 206
194 207 begin
195 208 set_owner_and_rights(project, repos_path)
196 209 rescue Errno::EPERM => e
197 210 log("\tunable to change mode on #{repos_path} : #{e}\n")
198 211 next
199 212 end
200 213
201 214 log("\tmode change on #{repos_path}");
202 215
203 216 else
217 # if repository is already declared in redmine, we don't create
218 # unless user use -f with reposman
219 if $force == false and not project.repository.nil?
220 log("\trepository for project #{project.identifier} already exists in Redmine", 1)
221 next
222 end
223
204 224 project.is_public ? File.umask(0002) : File.umask(0007)
205 225
206 226 if $test
207 227 log("\tcreate repository #{repos_path}")
208 228 log("\trepository #{repos_path} registered in Redmine with url #{$svn_url}#{project.identifier}") if $svn_url;
209 229 next
210 230 end
211 231
212 232 begin
213 233 set_owner_and_rights(project, repos_path) do
214 raise "svnadmin create #{repos_path} failed" unless system("svnadmin", "create", repos_path)
234 command = "#{$command} #{repos_path}"
235 raise "#{command} failed" unless system( command )
215 236 end
216 237 rescue => e
217 238 log("\tunable to create #{repos_path} : #{e}\n")
218 239 next
219 240 end
220 241
221 242 if $svn_url
222 ret = soap.RepositoryCreated project.identifier, "#{$svn_url}#{project.identifier}"
243 ret = soap.RepositoryCreated project.identifier, $scm, "#{$svn_url}#{project.identifier}"
223 244 if ret > 0
224 245 log("\trepository #{repos_path} registered in Redmine with url #{$svn_url}#{project.identifier}");
225 246 else
226 247 log("\trepository #{repos_path} not registered in Redmine. Look in your log to find why.");
227 248 end
228 249 end
229 250
230 251 log("\trepository #{repos_path} created");
231 252 end
232 253
233 254 end
234 255
@@ -1,149 +1,150
1 1 require 'redmine/access_control'
2 2 require 'redmine/menu_manager'
3 3 require 'redmine/activity'
4 4 require 'redmine/mime_type'
5 5 require 'redmine/core_ext'
6 6 require 'redmine/themes'
7 7 require 'redmine/hook'
8 8 require 'redmine/plugin'
9 9
10 10 begin
11 11 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
12 12 rescue LoadError
13 13 # RMagick is not available
14 14 end
15 15
16 16 REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar Git Filesystem )
17 17
18 18 # Permissions
19 19 Redmine::AccessControl.map do |map|
20 20 map.permission :view_project, {:projects => [:show, :activity]}, :public => true
21 21 map.permission :search_project, {:search => :index}, :public => true
22 22 map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
23 23 map.permission :select_project_modules, {:projects => :modules}, :require => :member
24 24 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy]}, :require => :member
25 25 map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :destroy]}, :require => :member
26 26
27 27 map.project_module :issue_tracking do |map|
28 28 # Issue categories
29 29 map.permission :manage_categories, {:projects => [:settings, :add_issue_category], :issue_categories => [:edit, :destroy]}, :require => :member
30 30 # Issues
31 31 map.permission :view_issues, {:projects => [:changelog, :roadmap],
32 32 :issues => [:index, :changes, :show, :context_menu],
33 33 :versions => [:show, :status_by],
34 34 :queries => :index,
35 35 :reports => :issue_report}, :public => true
36 36 map.permission :add_issues, {:issues => :new}
37 37 map.permission :edit_issues, {:issues => [:edit, :reply, :bulk_edit, :destroy_attachment]}
38 38 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
39 39 map.permission :add_issue_notes, {:issues => [:edit, :reply]}
40 40 map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin
41 41 map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin
42 42 map.permission :move_issues, {:issues => :move}, :require => :loggedin
43 43 map.permission :delete_issues, {:issues => :destroy}, :require => :member
44 44 # Queries
45 45 map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
46 46 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
47 47 # Gantt & calendar
48 48 map.permission :view_gantt, :issues => :gantt
49 49 map.permission :view_calendar, :issues => :calendar
50 50 # Watchers
51 51 map.permission :view_issue_watchers, {}
52 52 map.permission :add_issue_watchers, {:watchers => :new}
53 53 end
54 54
55 55 map.project_module :time_tracking do |map|
56 56 map.permission :log_time, {:timelog => :edit}, :require => :loggedin
57 57 map.permission :view_time_entries, :timelog => [:details, :report]
58 58 map.permission :edit_time_entries, {:timelog => [:edit, :destroy]}, :require => :member
59 59 map.permission :edit_own_time_entries, {:timelog => [:edit, :destroy]}, :require => :loggedin
60 60 end
61 61
62 62 map.project_module :news do |map|
63 63 map.permission :manage_news, {:news => [:new, :edit, :destroy, :destroy_comment]}, :require => :member
64 64 map.permission :view_news, {:news => [:index, :show]}, :public => true
65 65 map.permission :comment_news, {:news => :add_comment}
66 66 end
67 67
68 68 map.project_module :documents do |map|
69 69 map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment, :destroy_attachment]}, :require => :loggedin
70 70 map.permission :view_documents, :documents => [:index, :show, :download]
71 71 end
72 72
73 73 map.project_module :files do |map|
74 74 map.permission :manage_files, {:projects => :add_file, :versions => :destroy_file}, :require => :loggedin
75 75 map.permission :view_files, :projects => :list_files, :versions => :download
76 76 end
77 77
78 78 map.project_module :wiki do |map|
79 79 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
80 80 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
81 81 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
82 82 map.permission :view_wiki_pages, :wiki => [:index, :history, :diff, :annotate, :special]
83 83 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment, :destroy_attachment]
84 84 map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
85 85 end
86 86
87 87 map.project_module :repository do |map|
88 88 map.permission :manage_repository, {:repositories => [:edit, :destroy]}, :require => :member
89 89 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
90 90 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
91 map.permission :commit_access, {}
91 92 end
92 93
93 94 map.project_module :boards do |map|
94 95 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
95 96 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
96 97 map.permission :add_messages, {:messages => [:new, :reply, :quote]}
97 98 map.permission :edit_messages, {:messages => :edit}, :require => :member
98 99 map.permission :delete_messages, {:messages => :destroy}, :require => :member
99 100 end
100 101 end
101 102
102 103 Redmine::MenuManager.map :top_menu do |menu|
103 104 menu.push :home, :home_path, :html => { :class => 'home' }
104 105 menu.push :my_page, { :controller => 'my', :action => 'page' }, :html => { :class => 'mypage' }, :if => Proc.new { User.current.logged? }
105 106 menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural, :html => { :class => 'projects' }
106 107 menu.push :administration, { :controller => 'admin', :action => 'index' }, :html => { :class => 'admin' }, :if => Proc.new { User.current.admin? }, :last => true
107 108 menu.push :help, Redmine::Info.help_url, :html => { :class => 'help' }, :last => true
108 109 end
109 110
110 111 Redmine::MenuManager.map :account_menu do |menu|
111 112 menu.push :login, :signin_path, :html => { :class => 'login' }, :if => Proc.new { !User.current.logged? }
112 113 menu.push :register, { :controller => 'account', :action => 'register' }, :html => { :class => 'register' }, :if => Proc.new { !User.current.logged? && Setting.self_registration? }
113 114 menu.push :my_account, { :controller => 'my', :action => 'account' }, :html => { :class => 'myaccount' }, :if => Proc.new { User.current.logged? }
114 115 menu.push :logout, :signout_path, :html => { :class => 'logout' }, :if => Proc.new { User.current.logged? }
115 116 end
116 117
117 118 Redmine::MenuManager.map :application_menu do |menu|
118 119 # Empty
119 120 end
120 121
121 122 Redmine::MenuManager.map :project_menu do |menu|
122 123 menu.push :overview, { :controller => 'projects', :action => 'show' }
123 124 menu.push :activity, { :controller => 'projects', :action => 'activity' }
124 125 menu.push :roadmap, { :controller => 'projects', :action => 'roadmap' },
125 126 :if => Proc.new { |p| p.versions.any? }
126 127 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
127 128 menu.push :new_issue, { :controller => 'issues', :action => 'new' }, :param => :project_id, :caption => :label_issue_new,
128 129 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) }
129 130 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
130 131 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
131 132 menu.push :wiki, { :controller => 'wiki', :action => 'index', :page => nil },
132 133 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
133 134 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
134 135 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
135 136 menu.push :files, { :controller => 'projects', :action => 'list_files' }, :caption => :label_attachment_plural
136 137 menu.push :repository, { :controller => 'repositories', :action => 'show' },
137 138 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
138 139 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true
139 140 end
140 141
141 142 Redmine::Activity.map do |activity|
142 143 activity.register :issues, :class_name => %w(Issue Journal)
143 144 activity.register :changesets
144 145 activity.register :news
145 146 activity.register :documents, :class_name => %w(Document Attachment)
146 147 activity.register :files, :class_name => 'Attachment'
147 148 activity.register :wiki_pages, :class_name => 'WikiContent::Version', :default => false
148 149 activity.register :messages, :default => false
149 150 end
@@ -1,169 +1,170
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
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 18 module Redmine
19 19 module DefaultData
20 20 class DataAlreadyLoaded < Exception; end
21 21
22 22 module Loader
23 23 include GLoc
24 24
25 25 class << self
26 26 # Returns true if no data is already loaded in the database
27 27 # otherwise false
28 28 def no_data?
29 29 !Role.find(:first, :conditions => {:builtin => 0}) &&
30 30 !Tracker.find(:first) &&
31 31 !IssueStatus.find(:first) &&
32 32 !Enumeration.find(:first)
33 33 end
34 34
35 35 # Loads the default data
36 36 # Raises a RecordNotSaved exception if something goes wrong
37 37 def load(lang=nil)
38 38 raise DataAlreadyLoaded.new("Some configuration data is already loaded.") unless no_data?
39 39 set_language_if_valid(lang)
40 40
41 41 Role.transaction do
42 42 # Roles
43 43 manager = Role.create! :name => l(:default_role_manager),
44 44 :position => 1
45 45 manager.permissions = manager.setable_permissions.collect {|p| p.name}
46 46 manager.save!
47 47
48 48 developper = Role.create! :name => l(:default_role_developper),
49 49 :position => 2,
50 50 :permissions => [:manage_versions,
51 51 :manage_categories,
52 52 :add_issues,
53 53 :edit_issues,
54 54 :manage_issue_relations,
55 55 :add_issue_notes,
56 56 :save_queries,
57 57 :view_gantt,
58 58 :view_calendar,
59 59 :log_time,
60 60 :view_time_entries,
61 61 :comment_news,
62 62 :view_documents,
63 63 :view_wiki_pages,
64 64 :edit_wiki_pages,
65 65 :delete_wiki_pages,
66 66 :add_messages,
67 67 :view_files,
68 68 :manage_files,
69 69 :browse_repository,
70 :view_changesets]
70 :view_changesets,
71 :commit_access]
71 72
72 73 reporter = Role.create! :name => l(:default_role_reporter),
73 74 :position => 3,
74 75 :permissions => [:add_issues,
75 76 :add_issue_notes,
76 77 :save_queries,
77 78 :view_gantt,
78 79 :view_calendar,
79 80 :log_time,
80 81 :view_time_entries,
81 82 :comment_news,
82 83 :view_documents,
83 84 :view_wiki_pages,
84 85 :add_messages,
85 86 :view_files,
86 87 :browse_repository,
87 88 :view_changesets]
88 89
89 90 Role.non_member.update_attribute :permissions, [:add_issues,
90 91 :add_issue_notes,
91 92 :save_queries,
92 93 :view_gantt,
93 94 :view_calendar,
94 95 :view_time_entries,
95 96 :comment_news,
96 97 :view_documents,
97 98 :view_wiki_pages,
98 99 :add_messages,
99 100 :view_files,
100 101 :browse_repository,
101 102 :view_changesets]
102 103
103 104 Role.anonymous.update_attribute :permissions, [:view_gantt,
104 105 :view_calendar,
105 106 :view_time_entries,
106 107 :view_documents,
107 108 :view_wiki_pages,
108 109 :view_files,
109 110 :browse_repository,
110 111 :view_changesets]
111 112
112 113 # Trackers
113 114 Tracker.create!(:name => l(:default_tracker_bug), :is_in_chlog => true, :is_in_roadmap => false, :position => 1)
114 115 Tracker.create!(:name => l(:default_tracker_feature), :is_in_chlog => true, :is_in_roadmap => true, :position => 2)
115 116 Tracker.create!(:name => l(:default_tracker_support), :is_in_chlog => false, :is_in_roadmap => false, :position => 3)
116 117
117 118 # Issue statuses
118 119 new = IssueStatus.create!(:name => l(:default_issue_status_new), :is_closed => false, :is_default => true, :position => 1)
119 120 assigned = IssueStatus.create!(:name => l(:default_issue_status_assigned), :is_closed => false, :is_default => false, :position => 2)
120 121 resolved = IssueStatus.create!(:name => l(:default_issue_status_resolved), :is_closed => false, :is_default => false, :position => 3)
121 122 feedback = IssueStatus.create!(:name => l(:default_issue_status_feedback), :is_closed => false, :is_default => false, :position => 4)
122 123 closed = IssueStatus.create!(:name => l(:default_issue_status_closed), :is_closed => true, :is_default => false, :position => 5)
123 124 rejected = IssueStatus.create!(:name => l(:default_issue_status_rejected), :is_closed => true, :is_default => false, :position => 6)
124 125
125 126 # Workflow
126 127 Tracker.find(:all).each { |t|
127 128 IssueStatus.find(:all).each { |os|
128 129 IssueStatus.find(:all).each { |ns|
129 130 Workflow.create!(:tracker_id => t.id, :role_id => manager.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns
130 131 }
131 132 }
132 133 }
133 134
134 135 Tracker.find(:all).each { |t|
135 136 [new, assigned, resolved, feedback].each { |os|
136 137 [assigned, resolved, feedback, closed].each { |ns|
137 138 Workflow.create!(:tracker_id => t.id, :role_id => developper.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns
138 139 }
139 140 }
140 141 }
141 142
142 143 Tracker.find(:all).each { |t|
143 144 [new, assigned, resolved, feedback].each { |os|
144 145 [closed].each { |ns|
145 146 Workflow.create!(:tracker_id => t.id, :role_id => reporter.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns
146 147 }
147 148 }
148 149 Workflow.create!(:tracker_id => t.id, :role_id => reporter.id, :old_status_id => resolved.id, :new_status_id => feedback.id)
149 150 }
150 151
151 152 # Enumerations
152 153 Enumeration.create!(:opt => "DCAT", :name => l(:default_doc_category_user), :position => 1)
153 154 Enumeration.create!(:opt => "DCAT", :name => l(:default_doc_category_tech), :position => 2)
154 155
155 156 Enumeration.create!(:opt => "IPRI", :name => l(:default_priority_low), :position => 1)
156 157 Enumeration.create!(:opt => "IPRI", :name => l(:default_priority_normal), :position => 2, :is_default => true)
157 158 Enumeration.create!(:opt => "IPRI", :name => l(:default_priority_high), :position => 3)
158 159 Enumeration.create!(:opt => "IPRI", :name => l(:default_priority_urgent), :position => 4)
159 160 Enumeration.create!(:opt => "IPRI", :name => l(:default_priority_immediate), :position => 5)
160 161
161 162 Enumeration.create!(:opt => "ACTI", :name => l(:default_activity_design), :position => 1)
162 163 Enumeration.create!(:opt => "ACTI", :name => l(:default_activity_development), :position => 2)
163 164 end
164 165 true
165 166 end
166 167 end
167 168 end
168 169 end
169 170 end
@@ -1,31 +1,50
1 1 require File.dirname(__FILE__) + '/../test_helper'
2 2 require 'sys_controller'
3 3
4 4 # Re-raise errors caught by the controller.
5 5 class SysController; def rescue_action(e) raise e end; end
6 6
7 7 class SysControllerTest < Test::Unit::TestCase
8 fixtures :projects, :repositories
8 fixtures :projects, :enabled_modules, :repositories
9 9
10 10 def setup
11 11 @controller = SysController.new
12 12 @request = ActionController::TestRequest.new
13 13 @response = ActionController::TestResponse.new
14 14 # Enable WS
15 15 Setting.sys_api_enabled = 1
16 16 end
17 17
18 def test_projects
19 result = invoke :projects
20 assert_equal Project.count, result.size
21 assert result.first.is_a?(Project)
18 def test_projects_with_repository_enabled
19 result = invoke :projects_with_repository_enabled
20 assert_equal EnabledModule.count(:all, :conditions => {:name => 'repository'}), result.size
21
22 project = result.first
23 assert project.is_a?(AWSProjectWithRepository)
24
25 assert project.respond_to?(:id)
26 assert_equal 1, project.id
27
28 assert project.respond_to?(:identifier)
29 assert_equal 'ecookbook', project.identifier
30
31 assert project.respond_to?(:name)
32 assert_equal 'eCookbook', project.name
33
34 assert project.respond_to?(:is_public)
35 assert project.is_public
36
37 assert project.respond_to?(:repository)
38 assert project.repository.is_a?(Repository)
22 39 end
23 40
24 41 def test_repository_created
25 42 project = Project.find(3)
26 43 assert_nil project.repository
27 assert invoke(:repository_created, project.identifier, 'http://localhost/svn')
44 assert invoke(:repository_created, project.identifier, 'Subversion', 'http://localhost/svn')
28 45 project.reload
29 46 assert_not_nil project.repository
47 assert project.repository.is_a?(Repository::Subversion)
48 assert_equal 'http://localhost/svn', project.repository.url
30 49 end
31 50 end
@@ -1,33 +1,53
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
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 18 require File.dirname(__FILE__) + '/../test_helper'
19 19
20 20 class RoleTest < Test::Unit::TestCase
21 21 fixtures :roles, :workflows
22 22
23 23 def test_copy_workflows
24 24 source = Role.find(1)
25 25 assert_equal 90, source.workflows.size
26 26
27 27 target = Role.new(:name => 'Target')
28 28 assert target.save
29 29 target.workflows.copy(source)
30 30 target.reload
31 31 assert_equal 90, target.workflows.size
32 32 end
33
34 def test_add_permission
35 role = Role.find(1)
36 size = role.permissions.size
37 role.add_permission!("apermission", "anotherpermission")
38 role.reload
39 assert role.permissions.include?(:anotherpermission)
40 assert_equal size + 2, role.permissions.size
41 end
42
43 def test_remove_permission
44 role = Role.find(1)
45 size = role.permissions.size
46 perm = role.permissions[0..1]
47 role.remove_permission!(*perm)
48 role.reload
49 assert ! role.permissions.include?(perm[0])
50 assert_equal size - 2, role.permissions.size
51 end
52
33 53 end
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now