|
@@
-1,241
+1,247
|
|
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
|
require 'uri'
|
|
18
|
require 'uri'
|
|
19
|
require 'cgi'
|
|
19
|
require 'cgi'
|
|
20
|
|
|
20
|
|
|
21
|
class ApplicationController < ActionController::Base
|
|
21
|
class ApplicationController < ActionController::Base
|
|
|
|
|
22
|
class MissingSessionSecret < Exception ; end
|
|
22
|
layout 'base'
|
|
23
|
layout 'base'
|
|
23
|
|
|
24
|
|
|
24
|
before_filter :user_setup, :check_if_login_required, :set_localization
|
|
25
|
before_filter :user_setup, :check_if_login_required, :set_localization
|
|
25
|
filter_parameter_logging :password
|
|
26
|
filter_parameter_logging :password
|
|
26
|
protect_from_forgery :secret => session.first[:secret]
|
|
27
|
|
|
|
|
|
28
|
if session.first[:secret].blank?
|
|
|
|
|
29
|
raise MissingSessionSecret, "Missing session secret. Please run 'rake config/initializers/session_store.rb' to generate one"
|
|
|
|
|
30
|
else
|
|
|
|
|
31
|
protect_from_forgery :secret => session.first[:secret]
|
|
|
|
|
32
|
end
|
|
27
|
|
|
33
|
|
|
28
|
include Redmine::MenuManager::MenuController
|
|
34
|
include Redmine::MenuManager::MenuController
|
|
29
|
helper Redmine::MenuManager::MenuHelper
|
|
35
|
helper Redmine::MenuManager::MenuHelper
|
|
30
|
|
|
36
|
|
|
31
|
REDMINE_SUPPORTED_SCM.each do |scm|
|
|
37
|
REDMINE_SUPPORTED_SCM.each do |scm|
|
|
32
|
require_dependency "repository/#{scm.underscore}"
|
|
38
|
require_dependency "repository/#{scm.underscore}"
|
|
33
|
end
|
|
39
|
end
|
|
34
|
|
|
40
|
|
|
35
|
def current_role
|
|
41
|
def current_role
|
|
36
|
@current_role ||= User.current.role_for_project(@project)
|
|
42
|
@current_role ||= User.current.role_for_project(@project)
|
|
37
|
end
|
|
43
|
end
|
|
38
|
|
|
44
|
|
|
39
|
def user_setup
|
|
45
|
def user_setup
|
|
40
|
# Check the settings cache for each request
|
|
46
|
# Check the settings cache for each request
|
|
41
|
Setting.check_cache
|
|
47
|
Setting.check_cache
|
|
42
|
# Find the current user
|
|
48
|
# Find the current user
|
|
43
|
User.current = find_current_user
|
|
49
|
User.current = find_current_user
|
|
44
|
end
|
|
50
|
end
|
|
45
|
|
|
51
|
|
|
46
|
# Returns the current user or nil if no user is logged in
|
|
52
|
# Returns the current user or nil if no user is logged in
|
|
47
|
def find_current_user
|
|
53
|
def find_current_user
|
|
48
|
if session[:user_id]
|
|
54
|
if session[:user_id]
|
|
49
|
# existing session
|
|
55
|
# existing session
|
|
50
|
(User.active.find(session[:user_id]) rescue nil)
|
|
56
|
(User.active.find(session[:user_id]) rescue nil)
|
|
51
|
elsif cookies[:autologin] && Setting.autologin?
|
|
57
|
elsif cookies[:autologin] && Setting.autologin?
|
|
52
|
# auto-login feature
|
|
58
|
# auto-login feature
|
|
53
|
User.find_by_autologin_key(cookies[:autologin])
|
|
59
|
User.find_by_autologin_key(cookies[:autologin])
|
|
54
|
elsif params[:key] && accept_key_auth_actions.include?(params[:action])
|
|
60
|
elsif params[:key] && accept_key_auth_actions.include?(params[:action])
|
|
55
|
# RSS key authentication
|
|
61
|
# RSS key authentication
|
|
56
|
User.find_by_rss_key(params[:key])
|
|
62
|
User.find_by_rss_key(params[:key])
|
|
57
|
end
|
|
63
|
end
|
|
58
|
end
|
|
64
|
end
|
|
59
|
|
|
65
|
|
|
60
|
# check if login is globally required to access the application
|
|
66
|
# check if login is globally required to access the application
|
|
61
|
def check_if_login_required
|
|
67
|
def check_if_login_required
|
|
62
|
# no check needed if user is already logged in
|
|
68
|
# no check needed if user is already logged in
|
|
63
|
return true if User.current.logged?
|
|
69
|
return true if User.current.logged?
|
|
64
|
require_login if Setting.login_required?
|
|
70
|
require_login if Setting.login_required?
|
|
65
|
end
|
|
71
|
end
|
|
66
|
|
|
72
|
|
|
67
|
def set_localization
|
|
73
|
def set_localization
|
|
68
|
User.current.language = nil unless User.current.logged?
|
|
74
|
User.current.language = nil unless User.current.logged?
|
|
69
|
lang = begin
|
|
75
|
lang = begin
|
|
70
|
if !User.current.language.blank? && GLoc.valid_language?(User.current.language)
|
|
76
|
if !User.current.language.blank? && GLoc.valid_language?(User.current.language)
|
|
71
|
User.current.language
|
|
77
|
User.current.language
|
|
72
|
elsif request.env['HTTP_ACCEPT_LANGUAGE']
|
|
78
|
elsif request.env['HTTP_ACCEPT_LANGUAGE']
|
|
73
|
accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.downcase
|
|
79
|
accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.downcase
|
|
74
|
if !accept_lang.blank? && (GLoc.valid_language?(accept_lang) || GLoc.valid_language?(accept_lang = accept_lang.split('-').first))
|
|
80
|
if !accept_lang.blank? && (GLoc.valid_language?(accept_lang) || GLoc.valid_language?(accept_lang = accept_lang.split('-').first))
|
|
75
|
User.current.language = accept_lang
|
|
81
|
User.current.language = accept_lang
|
|
76
|
end
|
|
82
|
end
|
|
77
|
end
|
|
83
|
end
|
|
78
|
rescue
|
|
84
|
rescue
|
|
79
|
nil
|
|
85
|
nil
|
|
80
|
end || Setting.default_language
|
|
86
|
end || Setting.default_language
|
|
81
|
set_language_if_valid(lang)
|
|
87
|
set_language_if_valid(lang)
|
|
82
|
end
|
|
88
|
end
|
|
83
|
|
|
89
|
|
|
84
|
def require_login
|
|
90
|
def require_login
|
|
85
|
if !User.current.logged?
|
|
91
|
if !User.current.logged?
|
|
86
|
# Extract only the basic url parameters on non-GET requests
|
|
92
|
# Extract only the basic url parameters on non-GET requests
|
|
87
|
if request.get?
|
|
93
|
if request.get?
|
|
88
|
url = url_for(params)
|
|
94
|
url = url_for(params)
|
|
89
|
else
|
|
95
|
else
|
|
90
|
url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
|
|
96
|
url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
|
|
91
|
end
|
|
97
|
end
|
|
92
|
redirect_to :controller => "account", :action => "login", :back_url => url
|
|
98
|
redirect_to :controller => "account", :action => "login", :back_url => url
|
|
93
|
return false
|
|
99
|
return false
|
|
94
|
end
|
|
100
|
end
|
|
95
|
true
|
|
101
|
true
|
|
96
|
end
|
|
102
|
end
|
|
97
|
|
|
103
|
|
|
98
|
def require_admin
|
|
104
|
def require_admin
|
|
99
|
return unless require_login
|
|
105
|
return unless require_login
|
|
100
|
if !User.current.admin?
|
|
106
|
if !User.current.admin?
|
|
101
|
render_403
|
|
107
|
render_403
|
|
102
|
return false
|
|
108
|
return false
|
|
103
|
end
|
|
109
|
end
|
|
104
|
true
|
|
110
|
true
|
|
105
|
end
|
|
111
|
end
|
|
106
|
|
|
112
|
|
|
107
|
def deny_access
|
|
113
|
def deny_access
|
|
108
|
User.current.logged? ? render_403 : require_login
|
|
114
|
User.current.logged? ? render_403 : require_login
|
|
109
|
end
|
|
115
|
end
|
|
110
|
|
|
116
|
|
|
111
|
# Authorize the user for the requested action
|
|
117
|
# Authorize the user for the requested action
|
|
112
|
def authorize(ctrl = params[:controller], action = params[:action])
|
|
118
|
def authorize(ctrl = params[:controller], action = params[:action])
|
|
113
|
allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project)
|
|
119
|
allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project)
|
|
114
|
allowed ? true : deny_access
|
|
120
|
allowed ? true : deny_access
|
|
115
|
end
|
|
121
|
end
|
|
116
|
|
|
122
|
|
|
117
|
# make sure that the user is a member of the project (or admin) if project is private
|
|
123
|
# make sure that the user is a member of the project (or admin) if project is private
|
|
118
|
# used as a before_filter for actions that do not require any particular permission on the project
|
|
124
|
# used as a before_filter for actions that do not require any particular permission on the project
|
|
119
|
def check_project_privacy
|
|
125
|
def check_project_privacy
|
|
120
|
if @project && @project.active?
|
|
126
|
if @project && @project.active?
|
|
121
|
if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
|
|
127
|
if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
|
|
122
|
true
|
|
128
|
true
|
|
123
|
else
|
|
129
|
else
|
|
124
|
User.current.logged? ? render_403 : require_login
|
|
130
|
User.current.logged? ? render_403 : require_login
|
|
125
|
end
|
|
131
|
end
|
|
126
|
else
|
|
132
|
else
|
|
127
|
@project = nil
|
|
133
|
@project = nil
|
|
128
|
render_404
|
|
134
|
render_404
|
|
129
|
false
|
|
135
|
false
|
|
130
|
end
|
|
136
|
end
|
|
131
|
end
|
|
137
|
end
|
|
132
|
|
|
138
|
|
|
133
|
def redirect_back_or_default(default)
|
|
139
|
def redirect_back_or_default(default)
|
|
134
|
back_url = CGI.unescape(params[:back_url].to_s)
|
|
140
|
back_url = CGI.unescape(params[:back_url].to_s)
|
|
135
|
if !back_url.blank?
|
|
141
|
if !back_url.blank?
|
|
136
|
begin
|
|
142
|
begin
|
|
137
|
uri = URI.parse(back_url)
|
|
143
|
uri = URI.parse(back_url)
|
|
138
|
# do not redirect user to another host or to the login or register page
|
|
144
|
# do not redirect user to another host or to the login or register page
|
|
139
|
if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
|
|
145
|
if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
|
|
140
|
redirect_to(back_url) and return
|
|
146
|
redirect_to(back_url) and return
|
|
141
|
end
|
|
147
|
end
|
|
142
|
rescue URI::InvalidURIError
|
|
148
|
rescue URI::InvalidURIError
|
|
143
|
# redirect to default
|
|
149
|
# redirect to default
|
|
144
|
end
|
|
150
|
end
|
|
145
|
end
|
|
151
|
end
|
|
146
|
redirect_to default
|
|
152
|
redirect_to default
|
|
147
|
end
|
|
153
|
end
|
|
148
|
|
|
154
|
|
|
149
|
def render_403
|
|
155
|
def render_403
|
|
150
|
@project = nil
|
|
156
|
@project = nil
|
|
151
|
render :template => "common/403", :layout => !request.xhr?, :status => 403
|
|
157
|
render :template => "common/403", :layout => !request.xhr?, :status => 403
|
|
152
|
return false
|
|
158
|
return false
|
|
153
|
end
|
|
159
|
end
|
|
154
|
|
|
160
|
|
|
155
|
def render_404
|
|
161
|
def render_404
|
|
156
|
render :template => "common/404", :layout => !request.xhr?, :status => 404
|
|
162
|
render :template => "common/404", :layout => !request.xhr?, :status => 404
|
|
157
|
return false
|
|
163
|
return false
|
|
158
|
end
|
|
164
|
end
|
|
159
|
|
|
165
|
|
|
160
|
def render_error(msg)
|
|
166
|
def render_error(msg)
|
|
161
|
flash.now[:error] = msg
|
|
167
|
flash.now[:error] = msg
|
|
162
|
render :nothing => true, :layout => !request.xhr?, :status => 500
|
|
168
|
render :nothing => true, :layout => !request.xhr?, :status => 500
|
|
163
|
end
|
|
169
|
end
|
|
164
|
|
|
170
|
|
|
165
|
def render_feed(items, options={})
|
|
171
|
def render_feed(items, options={})
|
|
166
|
@items = items || []
|
|
172
|
@items = items || []
|
|
167
|
@items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
|
|
173
|
@items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
|
|
168
|
@items = @items.slice(0, Setting.feeds_limit.to_i)
|
|
174
|
@items = @items.slice(0, Setting.feeds_limit.to_i)
|
|
169
|
@title = options[:title] || Setting.app_title
|
|
175
|
@title = options[:title] || Setting.app_title
|
|
170
|
render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml'
|
|
176
|
render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml'
|
|
171
|
end
|
|
177
|
end
|
|
172
|
|
|
178
|
|
|
173
|
def self.accept_key_auth(*actions)
|
|
179
|
def self.accept_key_auth(*actions)
|
|
174
|
actions = actions.flatten.map(&:to_s)
|
|
180
|
actions = actions.flatten.map(&:to_s)
|
|
175
|
write_inheritable_attribute('accept_key_auth_actions', actions)
|
|
181
|
write_inheritable_attribute('accept_key_auth_actions', actions)
|
|
176
|
end
|
|
182
|
end
|
|
177
|
|
|
183
|
|
|
178
|
def accept_key_auth_actions
|
|
184
|
def accept_key_auth_actions
|
|
179
|
self.class.read_inheritable_attribute('accept_key_auth_actions') || []
|
|
185
|
self.class.read_inheritable_attribute('accept_key_auth_actions') || []
|
|
180
|
end
|
|
186
|
end
|
|
181
|
|
|
187
|
|
|
182
|
# TODO: move to model
|
|
188
|
# TODO: move to model
|
|
183
|
def attach_files(obj, attachments)
|
|
189
|
def attach_files(obj, attachments)
|
|
184
|
attached = []
|
|
190
|
attached = []
|
|
185
|
unsaved = []
|
|
191
|
unsaved = []
|
|
186
|
if attachments && attachments.is_a?(Hash)
|
|
192
|
if attachments && attachments.is_a?(Hash)
|
|
187
|
attachments.each_value do |attachment|
|
|
193
|
attachments.each_value do |attachment|
|
|
188
|
file = attachment['file']
|
|
194
|
file = attachment['file']
|
|
189
|
next unless file && file.size > 0
|
|
195
|
next unless file && file.size > 0
|
|
190
|
a = Attachment.create(:container => obj,
|
|
196
|
a = Attachment.create(:container => obj,
|
|
191
|
:file => file,
|
|
197
|
:file => file,
|
|
192
|
:description => attachment['description'].to_s.strip,
|
|
198
|
:description => attachment['description'].to_s.strip,
|
|
193
|
:author => User.current)
|
|
199
|
:author => User.current)
|
|
194
|
a.new_record? ? (unsaved << a) : (attached << a)
|
|
200
|
a.new_record? ? (unsaved << a) : (attached << a)
|
|
195
|
end
|
|
201
|
end
|
|
196
|
if unsaved.any?
|
|
202
|
if unsaved.any?
|
|
197
|
flash[:warning] = l(:warning_attachments_not_saved, unsaved.size)
|
|
203
|
flash[:warning] = l(:warning_attachments_not_saved, unsaved.size)
|
|
198
|
end
|
|
204
|
end
|
|
199
|
end
|
|
205
|
end
|
|
200
|
attached
|
|
206
|
attached
|
|
201
|
end
|
|
207
|
end
|
|
202
|
|
|
208
|
|
|
203
|
# Returns the number of objects that should be displayed
|
|
209
|
# Returns the number of objects that should be displayed
|
|
204
|
# on the paginated list
|
|
210
|
# on the paginated list
|
|
205
|
def per_page_option
|
|
211
|
def per_page_option
|
|
206
|
per_page = nil
|
|
212
|
per_page = nil
|
|
207
|
if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
|
|
213
|
if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
|
|
208
|
per_page = params[:per_page].to_s.to_i
|
|
214
|
per_page = params[:per_page].to_s.to_i
|
|
209
|
session[:per_page] = per_page
|
|
215
|
session[:per_page] = per_page
|
|
210
|
elsif session[:per_page]
|
|
216
|
elsif session[:per_page]
|
|
211
|
per_page = session[:per_page]
|
|
217
|
per_page = session[:per_page]
|
|
212
|
else
|
|
218
|
else
|
|
213
|
per_page = Setting.per_page_options_array.first || 25
|
|
219
|
per_page = Setting.per_page_options_array.first || 25
|
|
214
|
end
|
|
220
|
end
|
|
215
|
per_page
|
|
221
|
per_page
|
|
216
|
end
|
|
222
|
end
|
|
217
|
|
|
223
|
|
|
218
|
# qvalues http header parser
|
|
224
|
# qvalues http header parser
|
|
219
|
# code taken from webrick
|
|
225
|
# code taken from webrick
|
|
220
|
def parse_qvalues(value)
|
|
226
|
def parse_qvalues(value)
|
|
221
|
tmp = []
|
|
227
|
tmp = []
|
|
222
|
if value
|
|
228
|
if value
|
|
223
|
parts = value.split(/,\s*/)
|
|
229
|
parts = value.split(/,\s*/)
|
|
224
|
parts.each {|part|
|
|
230
|
parts.each {|part|
|
|
225
|
if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
|
|
231
|
if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
|
|
226
|
val = m[1]
|
|
232
|
val = m[1]
|
|
227
|
q = (m[2] or 1).to_f
|
|
233
|
q = (m[2] or 1).to_f
|
|
228
|
tmp.push([val, q])
|
|
234
|
tmp.push([val, q])
|
|
229
|
end
|
|
235
|
end
|
|
230
|
}
|
|
236
|
}
|
|
231
|
tmp = tmp.sort_by{|val, q| -q}
|
|
237
|
tmp = tmp.sort_by{|val, q| -q}
|
|
232
|
tmp.collect!{|val, q| val}
|
|
238
|
tmp.collect!{|val, q| val}
|
|
233
|
end
|
|
239
|
end
|
|
234
|
return tmp
|
|
240
|
return tmp
|
|
235
|
end
|
|
241
|
end
|
|
236
|
|
|
242
|
|
|
237
|
# Returns a string that can be used as filename value in Content-Disposition header
|
|
243
|
# Returns a string that can be used as filename value in Content-Disposition header
|
|
238
|
def filename_for_content_disposition(name)
|
|
244
|
def filename_for_content_disposition(name)
|
|
239
|
request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
|
|
245
|
request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
|
|
240
|
end
|
|
246
|
end
|
|
241
|
end
|
|
247
|
end
|