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