##// END OF EJS Templates
Respond with 404 on ActionView::MissingTemplate (#11503)....
Jean-Philippe Lang -
r10021:327660eb7f7e
parent child
Show More
@@ -1,549 +1,554
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 Unauthorized < Exception; end
21 class Unauthorized < Exception; end
22
22
23 class ApplicationController < ActionController::Base
23 class ApplicationController < ActionController::Base
24 include Redmine::I18n
24 include Redmine::I18n
25
25
26 class_attribute :accept_api_auth_actions
26 class_attribute :accept_api_auth_actions
27 class_attribute :accept_rss_auth_actions
27 class_attribute :accept_rss_auth_actions
28 class_attribute :model_object
28 class_attribute :model_object
29
29
30 layout 'base'
30 layout 'base'
31
31
32 protect_from_forgery
32 protect_from_forgery
33 def handle_unverified_request
33 def handle_unverified_request
34 super
34 super
35 cookies.delete(:autologin)
35 cookies.delete(:autologin)
36 end
36 end
37
37
38 before_filter :session_expiration, :user_setup, :check_if_login_required, :set_localization
38 before_filter :session_expiration, :user_setup, :check_if_login_required, :set_localization
39
39
40 rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
40 rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
41 rescue_from ::Unauthorized, :with => :deny_access
41 rescue_from ::Unauthorized, :with => :deny_access
42 rescue_from ::ActionView::MissingTemplate, :with => :missing_template
42
43
43 include Redmine::Search::Controller
44 include Redmine::Search::Controller
44 include Redmine::MenuManager::MenuController
45 include Redmine::MenuManager::MenuController
45 helper Redmine::MenuManager::MenuHelper
46 helper Redmine::MenuManager::MenuHelper
46
47
47 def session_expiration
48 def session_expiration
48 if session[:user_id]
49 if session[:user_id]
49 if session_expired? && !try_to_autologin
50 if session_expired? && !try_to_autologin
50 reset_session
51 reset_session
51 flash[:error] = l(:error_session_expired)
52 flash[:error] = l(:error_session_expired)
52 redirect_to signin_url
53 redirect_to signin_url
53 else
54 else
54 session[:atime] = Time.now.utc.to_i
55 session[:atime] = Time.now.utc.to_i
55 end
56 end
56 end
57 end
57 end
58 end
58
59
59 def session_expired?
60 def session_expired?
60 if Setting.session_lifetime?
61 if Setting.session_lifetime?
61 unless session[:ctime] && (Time.now.utc.to_i - session[:ctime].to_i <= Setting.session_lifetime.to_i * 60)
62 unless session[:ctime] && (Time.now.utc.to_i - session[:ctime].to_i <= Setting.session_lifetime.to_i * 60)
62 return true
63 return true
63 end
64 end
64 end
65 end
65 if Setting.session_timeout?
66 if Setting.session_timeout?
66 unless session[:atime] && (Time.now.utc.to_i - session[:atime].to_i <= Setting.session_timeout.to_i * 60)
67 unless session[:atime] && (Time.now.utc.to_i - session[:atime].to_i <= Setting.session_timeout.to_i * 60)
67 return true
68 return true
68 end
69 end
69 end
70 end
70 false
71 false
71 end
72 end
72
73
73 def start_user_session(user)
74 def start_user_session(user)
74 session[:user_id] = user.id
75 session[:user_id] = user.id
75 session[:ctime] = Time.now.utc.to_i
76 session[:ctime] = Time.now.utc.to_i
76 session[:atime] = Time.now.utc.to_i
77 session[:atime] = Time.now.utc.to_i
77 end
78 end
78
79
79 def user_setup
80 def user_setup
80 # Check the settings cache for each request
81 # Check the settings cache for each request
81 Setting.check_cache
82 Setting.check_cache
82 # Find the current user
83 # Find the current user
83 User.current = find_current_user
84 User.current = find_current_user
84 end
85 end
85
86
86 # Returns the current user or nil if no user is logged in
87 # Returns the current user or nil if no user is logged in
87 # and starts a session if needed
88 # and starts a session if needed
88 def find_current_user
89 def find_current_user
89 user = nil
90 user = nil
90 unless api_request?
91 unless api_request?
91 if session[:user_id]
92 if session[:user_id]
92 # existing session
93 # existing session
93 user = (User.active.find(session[:user_id]) rescue nil)
94 user = (User.active.find(session[:user_id]) rescue nil)
94 elsif autologin_user = try_to_autologin
95 elsif autologin_user = try_to_autologin
95 user = autologin_user
96 user = autologin_user
96 elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth?
97 elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth?
97 # RSS key authentication does not start a session
98 # RSS key authentication does not start a session
98 user = User.find_by_rss_key(params[:key])
99 user = User.find_by_rss_key(params[:key])
99 end
100 end
100 end
101 end
101 if user.nil? && Setting.rest_api_enabled? && accept_api_auth?
102 if user.nil? && Setting.rest_api_enabled? && accept_api_auth?
102 if (key = api_key_from_request)
103 if (key = api_key_from_request)
103 # Use API key
104 # Use API key
104 user = User.find_by_api_key(key)
105 user = User.find_by_api_key(key)
105 else
106 else
106 # HTTP Basic, either username/password or API key/random
107 # HTTP Basic, either username/password or API key/random
107 authenticate_with_http_basic do |username, password|
108 authenticate_with_http_basic do |username, password|
108 user = User.try_to_login(username, password) || User.find_by_api_key(username)
109 user = User.try_to_login(username, password) || User.find_by_api_key(username)
109 end
110 end
110 end
111 end
111 end
112 end
112 user
113 user
113 end
114 end
114
115
115 def try_to_autologin
116 def try_to_autologin
116 if cookies[:autologin] && Setting.autologin?
117 if cookies[:autologin] && Setting.autologin?
117 # auto-login feature starts a new session
118 # auto-login feature starts a new session
118 user = User.try_to_autologin(cookies[:autologin])
119 user = User.try_to_autologin(cookies[:autologin])
119 if user
120 if user
120 reset_session
121 reset_session
121 start_user_session(user)
122 start_user_session(user)
122 end
123 end
123 user
124 user
124 end
125 end
125 end
126 end
126
127
127 # Sets the logged in user
128 # Sets the logged in user
128 def logged_user=(user)
129 def logged_user=(user)
129 reset_session
130 reset_session
130 if user && user.is_a?(User)
131 if user && user.is_a?(User)
131 User.current = user
132 User.current = user
132 start_user_session(user)
133 start_user_session(user)
133 else
134 else
134 User.current = User.anonymous
135 User.current = User.anonymous
135 end
136 end
136 end
137 end
137
138
138 # Logs out current user
139 # Logs out current user
139 def logout_user
140 def logout_user
140 if User.current.logged?
141 if User.current.logged?
141 cookies.delete :autologin
142 cookies.delete :autologin
142 Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
143 Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
143 self.logged_user = nil
144 self.logged_user = nil
144 end
145 end
145 end
146 end
146
147
147 # check if login is globally required to access the application
148 # check if login is globally required to access the application
148 def check_if_login_required
149 def check_if_login_required
149 # no check needed if user is already logged in
150 # no check needed if user is already logged in
150 return true if User.current.logged?
151 return true if User.current.logged?
151 require_login if Setting.login_required?
152 require_login if Setting.login_required?
152 end
153 end
153
154
154 def set_localization
155 def set_localization
155 lang = nil
156 lang = nil
156 if User.current.logged?
157 if User.current.logged?
157 lang = find_language(User.current.language)
158 lang = find_language(User.current.language)
158 end
159 end
159 if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
160 if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
160 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first
161 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first
161 if !accept_lang.blank?
162 if !accept_lang.blank?
162 accept_lang = accept_lang.downcase
163 accept_lang = accept_lang.downcase
163 lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
164 lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
164 end
165 end
165 end
166 end
166 lang ||= Setting.default_language
167 lang ||= Setting.default_language
167 set_language_if_valid(lang)
168 set_language_if_valid(lang)
168 end
169 end
169
170
170 def require_login
171 def require_login
171 if !User.current.logged?
172 if !User.current.logged?
172 # Extract only the basic url parameters on non-GET requests
173 # Extract only the basic url parameters on non-GET requests
173 if request.get?
174 if request.get?
174 url = url_for(params)
175 url = url_for(params)
175 else
176 else
176 url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
177 url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
177 end
178 end
178 respond_to do |format|
179 respond_to do |format|
179 format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
180 format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
180 format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
181 format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
181 format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
182 format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
182 format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
183 format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
183 format.json { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
184 format.json { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
184 end
185 end
185 return false
186 return false
186 end
187 end
187 true
188 true
188 end
189 end
189
190
190 def require_admin
191 def require_admin
191 return unless require_login
192 return unless require_login
192 if !User.current.admin?
193 if !User.current.admin?
193 render_403
194 render_403
194 return false
195 return false
195 end
196 end
196 true
197 true
197 end
198 end
198
199
199 def deny_access
200 def deny_access
200 User.current.logged? ? render_403 : require_login
201 User.current.logged? ? render_403 : require_login
201 end
202 end
202
203
203 # Authorize the user for the requested action
204 # Authorize the user for the requested action
204 def authorize(ctrl = params[:controller], action = params[:action], global = false)
205 def authorize(ctrl = params[:controller], action = params[:action], global = false)
205 allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project || @projects, :global => global)
206 allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project || @projects, :global => global)
206 if allowed
207 if allowed
207 true
208 true
208 else
209 else
209 if @project && @project.archived?
210 if @project && @project.archived?
210 render_403 :message => :notice_not_authorized_archived_project
211 render_403 :message => :notice_not_authorized_archived_project
211 else
212 else
212 deny_access
213 deny_access
213 end
214 end
214 end
215 end
215 end
216 end
216
217
217 # Authorize the user for the requested action outside a project
218 # Authorize the user for the requested action outside a project
218 def authorize_global(ctrl = params[:controller], action = params[:action], global = true)
219 def authorize_global(ctrl = params[:controller], action = params[:action], global = true)
219 authorize(ctrl, action, global)
220 authorize(ctrl, action, global)
220 end
221 end
221
222
222 # Find project of id params[:id]
223 # Find project of id params[:id]
223 def find_project
224 def find_project
224 @project = Project.find(params[:id])
225 @project = Project.find(params[:id])
225 rescue ActiveRecord::RecordNotFound
226 rescue ActiveRecord::RecordNotFound
226 render_404
227 render_404
227 end
228 end
228
229
229 # Find project of id params[:project_id]
230 # Find project of id params[:project_id]
230 def find_project_by_project_id
231 def find_project_by_project_id
231 @project = Project.find(params[:project_id])
232 @project = Project.find(params[:project_id])
232 rescue ActiveRecord::RecordNotFound
233 rescue ActiveRecord::RecordNotFound
233 render_404
234 render_404
234 end
235 end
235
236
236 # Find a project based on params[:project_id]
237 # Find a project based on params[:project_id]
237 # TODO: some subclasses override this, see about merging their logic
238 # TODO: some subclasses override this, see about merging their logic
238 def find_optional_project
239 def find_optional_project
239 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
240 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
240 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
241 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
241 allowed ? true : deny_access
242 allowed ? true : deny_access
242 rescue ActiveRecord::RecordNotFound
243 rescue ActiveRecord::RecordNotFound
243 render_404
244 render_404
244 end
245 end
245
246
246 # Finds and sets @project based on @object.project
247 # Finds and sets @project based on @object.project
247 def find_project_from_association
248 def find_project_from_association
248 render_404 unless @object.present?
249 render_404 unless @object.present?
249
250
250 @project = @object.project
251 @project = @object.project
251 end
252 end
252
253
253 def find_model_object
254 def find_model_object
254 model = self.class.model_object
255 model = self.class.model_object
255 if model
256 if model
256 @object = model.find(params[:id])
257 @object = model.find(params[:id])
257 self.instance_variable_set('@' + controller_name.singularize, @object) if @object
258 self.instance_variable_set('@' + controller_name.singularize, @object) if @object
258 end
259 end
259 rescue ActiveRecord::RecordNotFound
260 rescue ActiveRecord::RecordNotFound
260 render_404
261 render_404
261 end
262 end
262
263
263 def self.model_object(model)
264 def self.model_object(model)
264 self.model_object = model
265 self.model_object = model
265 end
266 end
266
267
267 # Filter for bulk issue operations
268 # Filter for bulk issue operations
268 def find_issues
269 def find_issues
269 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
270 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
270 raise ActiveRecord::RecordNotFound if @issues.empty?
271 raise ActiveRecord::RecordNotFound if @issues.empty?
271 if @issues.detect {|issue| !issue.visible?}
272 if @issues.detect {|issue| !issue.visible?}
272 deny_access
273 deny_access
273 return
274 return
274 end
275 end
275 @projects = @issues.collect(&:project).compact.uniq
276 @projects = @issues.collect(&:project).compact.uniq
276 @project = @projects.first if @projects.size == 1
277 @project = @projects.first if @projects.size == 1
277 rescue ActiveRecord::RecordNotFound
278 rescue ActiveRecord::RecordNotFound
278 render_404
279 render_404
279 end
280 end
280
281
281 # make sure that the user is a member of the project (or admin) if project is private
282 # make sure that the user is a member of the project (or admin) if project is private
282 # used as a before_filter for actions that do not require any particular permission on the project
283 # used as a before_filter for actions that do not require any particular permission on the project
283 def check_project_privacy
284 def check_project_privacy
284 if @project && !@project.archived?
285 if @project && !@project.archived?
285 if @project.visible?
286 if @project.visible?
286 true
287 true
287 else
288 else
288 deny_access
289 deny_access
289 end
290 end
290 else
291 else
291 @project = nil
292 @project = nil
292 render_404
293 render_404
293 false
294 false
294 end
295 end
295 end
296 end
296
297
297 def back_url
298 def back_url
298 params[:back_url] || request.env['HTTP_REFERER']
299 params[:back_url] || request.env['HTTP_REFERER']
299 end
300 end
300
301
301 def redirect_back_or_default(default)
302 def redirect_back_or_default(default)
302 back_url = CGI.unescape(params[:back_url].to_s)
303 back_url = CGI.unescape(params[:back_url].to_s)
303 if !back_url.blank?
304 if !back_url.blank?
304 begin
305 begin
305 uri = URI.parse(back_url)
306 uri = URI.parse(back_url)
306 # do not redirect user to another host or to the login or register page
307 # do not redirect user to another host or to the login or register page
307 if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
308 if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
308 redirect_to(back_url)
309 redirect_to(back_url)
309 return
310 return
310 end
311 end
311 rescue URI::InvalidURIError
312 rescue URI::InvalidURIError
312 # redirect to default
313 # redirect to default
313 end
314 end
314 end
315 end
315 redirect_to default
316 redirect_to default
316 false
317 false
317 end
318 end
318
319
319 # Redirects to the request referer if present, redirects to args or call block otherwise.
320 # Redirects to the request referer if present, redirects to args or call block otherwise.
320 def redirect_to_referer_or(*args, &block)
321 def redirect_to_referer_or(*args, &block)
321 redirect_to :back
322 redirect_to :back
322 rescue ::ActionController::RedirectBackError
323 rescue ::ActionController::RedirectBackError
323 if args.any?
324 if args.any?
324 redirect_to *args
325 redirect_to *args
325 elsif block_given?
326 elsif block_given?
326 block.call
327 block.call
327 else
328 else
328 raise "#redirect_to_referer_or takes arguments or a block"
329 raise "#redirect_to_referer_or takes arguments or a block"
329 end
330 end
330 end
331 end
331
332
332 def render_403(options={})
333 def render_403(options={})
333 @project = nil
334 @project = nil
334 render_error({:message => :notice_not_authorized, :status => 403}.merge(options))
335 render_error({:message => :notice_not_authorized, :status => 403}.merge(options))
335 return false
336 return false
336 end
337 end
337
338
338 def render_404(options={})
339 def render_404(options={})
339 render_error({:message => :notice_file_not_found, :status => 404}.merge(options))
340 render_error({:message => :notice_file_not_found, :status => 404}.merge(options))
340 return false
341 return false
341 end
342 end
342
343
343 # Renders an error response
344 # Renders an error response
344 def render_error(arg)
345 def render_error(arg)
345 arg = {:message => arg} unless arg.is_a?(Hash)
346 arg = {:message => arg} unless arg.is_a?(Hash)
346
347
347 @message = arg[:message]
348 @message = arg[:message]
348 @message = l(@message) if @message.is_a?(Symbol)
349 @message = l(@message) if @message.is_a?(Symbol)
349 @status = arg[:status] || 500
350 @status = arg[:status] || 500
350
351
351 respond_to do |format|
352 respond_to do |format|
352 format.html {
353 format.html {
353 render :template => 'common/error', :layout => use_layout, :status => @status
354 render :template => 'common/error', :layout => use_layout, :status => @status
354 }
355 }
355 format.atom { head @status }
356 format.any { head @status }
356 format.xml { head @status }
357 format.js { head @status }
358 format.json { head @status }
359 end
357 end
360 end
358 end
361
359
360 # Handler for ActionView::MissingTemplate exception
361 def missing_template
362 logger.warn "Missing template, responding with 404"
363 @project = nil
364 render_404
365 end
366
362 # Filter for actions that provide an API response
367 # Filter for actions that provide an API response
363 # but have no HTML representation for non admin users
368 # but have no HTML representation for non admin users
364 def require_admin_or_api_request
369 def require_admin_or_api_request
365 return true if api_request?
370 return true if api_request?
366 if User.current.admin?
371 if User.current.admin?
367 true
372 true
368 elsif User.current.logged?
373 elsif User.current.logged?
369 render_error(:status => 406)
374 render_error(:status => 406)
370 else
375 else
371 deny_access
376 deny_access
372 end
377 end
373 end
378 end
374
379
375 # Picks which layout to use based on the request
380 # Picks which layout to use based on the request
376 #
381 #
377 # @return [boolean, string] name of the layout to use or false for no layout
382 # @return [boolean, string] name of the layout to use or false for no layout
378 def use_layout
383 def use_layout
379 request.xhr? ? false : 'base'
384 request.xhr? ? false : 'base'
380 end
385 end
381
386
382 def invalid_authenticity_token
387 def invalid_authenticity_token
383 if api_request?
388 if api_request?
384 logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)."
389 logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)."
385 end
390 end
386 render_error "Invalid form authenticity token."
391 render_error "Invalid form authenticity token."
387 end
392 end
388
393
389 def render_feed(items, options={})
394 def render_feed(items, options={})
390 @items = items || []
395 @items = items || []
391 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
396 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
392 @items = @items.slice(0, Setting.feeds_limit.to_i)
397 @items = @items.slice(0, Setting.feeds_limit.to_i)
393 @title = options[:title] || Setting.app_title
398 @title = options[:title] || Setting.app_title
394 render :template => "common/feed.atom", :layout => false,
399 render :template => "common/feed.atom", :layout => false,
395 :content_type => 'application/atom+xml'
400 :content_type => 'application/atom+xml'
396 end
401 end
397
402
398 def self.accept_rss_auth(*actions)
403 def self.accept_rss_auth(*actions)
399 if actions.any?
404 if actions.any?
400 self.accept_rss_auth_actions = actions
405 self.accept_rss_auth_actions = actions
401 else
406 else
402 self.accept_rss_auth_actions || []
407 self.accept_rss_auth_actions || []
403 end
408 end
404 end
409 end
405
410
406 def accept_rss_auth?(action=action_name)
411 def accept_rss_auth?(action=action_name)
407 self.class.accept_rss_auth.include?(action.to_sym)
412 self.class.accept_rss_auth.include?(action.to_sym)
408 end
413 end
409
414
410 def self.accept_api_auth(*actions)
415 def self.accept_api_auth(*actions)
411 if actions.any?
416 if actions.any?
412 self.accept_api_auth_actions = actions
417 self.accept_api_auth_actions = actions
413 else
418 else
414 self.accept_api_auth_actions || []
419 self.accept_api_auth_actions || []
415 end
420 end
416 end
421 end
417
422
418 def accept_api_auth?(action=action_name)
423 def accept_api_auth?(action=action_name)
419 self.class.accept_api_auth.include?(action.to_sym)
424 self.class.accept_api_auth.include?(action.to_sym)
420 end
425 end
421
426
422 # Returns the number of objects that should be displayed
427 # Returns the number of objects that should be displayed
423 # on the paginated list
428 # on the paginated list
424 def per_page_option
429 def per_page_option
425 per_page = nil
430 per_page = nil
426 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
431 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
427 per_page = params[:per_page].to_s.to_i
432 per_page = params[:per_page].to_s.to_i
428 session[:per_page] = per_page
433 session[:per_page] = per_page
429 elsif session[:per_page]
434 elsif session[:per_page]
430 per_page = session[:per_page]
435 per_page = session[:per_page]
431 else
436 else
432 per_page = Setting.per_page_options_array.first || 25
437 per_page = Setting.per_page_options_array.first || 25
433 end
438 end
434 per_page
439 per_page
435 end
440 end
436
441
437 # Returns offset and limit used to retrieve objects
442 # Returns offset and limit used to retrieve objects
438 # for an API response based on offset, limit and page parameters
443 # for an API response based on offset, limit and page parameters
439 def api_offset_and_limit(options=params)
444 def api_offset_and_limit(options=params)
440 if options[:offset].present?
445 if options[:offset].present?
441 offset = options[:offset].to_i
446 offset = options[:offset].to_i
442 if offset < 0
447 if offset < 0
443 offset = 0
448 offset = 0
444 end
449 end
445 end
450 end
446 limit = options[:limit].to_i
451 limit = options[:limit].to_i
447 if limit < 1
452 if limit < 1
448 limit = 25
453 limit = 25
449 elsif limit > 100
454 elsif limit > 100
450 limit = 100
455 limit = 100
451 end
456 end
452 if offset.nil? && options[:page].present?
457 if offset.nil? && options[:page].present?
453 offset = (options[:page].to_i - 1) * limit
458 offset = (options[:page].to_i - 1) * limit
454 offset = 0 if offset < 0
459 offset = 0 if offset < 0
455 end
460 end
456 offset ||= 0
461 offset ||= 0
457
462
458 [offset, limit]
463 [offset, limit]
459 end
464 end
460
465
461 # qvalues http header parser
466 # qvalues http header parser
462 # code taken from webrick
467 # code taken from webrick
463 def parse_qvalues(value)
468 def parse_qvalues(value)
464 tmp = []
469 tmp = []
465 if value
470 if value
466 parts = value.split(/,\s*/)
471 parts = value.split(/,\s*/)
467 parts.each {|part|
472 parts.each {|part|
468 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
473 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
469 val = m[1]
474 val = m[1]
470 q = (m[2] or 1).to_f
475 q = (m[2] or 1).to_f
471 tmp.push([val, q])
476 tmp.push([val, q])
472 end
477 end
473 }
478 }
474 tmp = tmp.sort_by{|val, q| -q}
479 tmp = tmp.sort_by{|val, q| -q}
475 tmp.collect!{|val, q| val}
480 tmp.collect!{|val, q| val}
476 end
481 end
477 return tmp
482 return tmp
478 rescue
483 rescue
479 nil
484 nil
480 end
485 end
481
486
482 # Returns a string that can be used as filename value in Content-Disposition header
487 # Returns a string that can be used as filename value in Content-Disposition header
483 def filename_for_content_disposition(name)
488 def filename_for_content_disposition(name)
484 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
489 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
485 end
490 end
486
491
487 def api_request?
492 def api_request?
488 %w(xml json).include? params[:format]
493 %w(xml json).include? params[:format]
489 end
494 end
490
495
491 # Returns the API key present in the request
496 # Returns the API key present in the request
492 def api_key_from_request
497 def api_key_from_request
493 if params[:key].present?
498 if params[:key].present?
494 params[:key].to_s
499 params[:key].to_s
495 elsif request.headers["X-Redmine-API-Key"].present?
500 elsif request.headers["X-Redmine-API-Key"].present?
496 request.headers["X-Redmine-API-Key"].to_s
501 request.headers["X-Redmine-API-Key"].to_s
497 end
502 end
498 end
503 end
499
504
500 # Renders a warning flash if obj has unsaved attachments
505 # Renders a warning flash if obj has unsaved attachments
501 def render_attachment_warning_if_needed(obj)
506 def render_attachment_warning_if_needed(obj)
502 flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
507 flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
503 end
508 end
504
509
505 # Sets the `flash` notice or error based the number of issues that did not save
510 # Sets the `flash` notice or error based the number of issues that did not save
506 #
511 #
507 # @param [Array, Issue] issues all of the saved and unsaved Issues
512 # @param [Array, Issue] issues all of the saved and unsaved Issues
508 # @param [Array, Integer] unsaved_issue_ids the issue ids that were not saved
513 # @param [Array, Integer] unsaved_issue_ids the issue ids that were not saved
509 def set_flash_from_bulk_issue_save(issues, unsaved_issue_ids)
514 def set_flash_from_bulk_issue_save(issues, unsaved_issue_ids)
510 if unsaved_issue_ids.empty?
515 if unsaved_issue_ids.empty?
511 flash[:notice] = l(:notice_successful_update) unless issues.empty?
516 flash[:notice] = l(:notice_successful_update) unless issues.empty?
512 else
517 else
513 flash[:error] = l(:notice_failed_to_save_issues,
518 flash[:error] = l(:notice_failed_to_save_issues,
514 :count => unsaved_issue_ids.size,
519 :count => unsaved_issue_ids.size,
515 :total => issues.size,
520 :total => issues.size,
516 :ids => '#' + unsaved_issue_ids.join(', #'))
521 :ids => '#' + unsaved_issue_ids.join(', #'))
517 end
522 end
518 end
523 end
519
524
520 # Rescues an invalid query statement. Just in case...
525 # Rescues an invalid query statement. Just in case...
521 def query_statement_invalid(exception)
526 def query_statement_invalid(exception)
522 logger.error "Query::StatementInvalid: #{exception.message}" if logger
527 logger.error "Query::StatementInvalid: #{exception.message}" if logger
523 session.delete(:query)
528 session.delete(:query)
524 sort_clear if respond_to?(:sort_clear)
529 sort_clear if respond_to?(:sort_clear)
525 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
530 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
526 end
531 end
527
532
528 # Renders a 200 response for successfull updates or deletions via the API
533 # Renders a 200 response for successfull updates or deletions via the API
529 def render_api_ok
534 def render_api_ok
530 # head :ok would return a response body with one space
535 # head :ok would return a response body with one space
531 render :text => '', :status => :ok, :layout => nil
536 render :text => '', :status => :ok, :layout => nil
532 end
537 end
533
538
534 # Renders API response on validation failure
539 # Renders API response on validation failure
535 def render_validation_errors(objects)
540 def render_validation_errors(objects)
536 if objects.is_a?(Array)
541 if objects.is_a?(Array)
537 @error_messages = objects.map {|object| object.errors.full_messages}.flatten
542 @error_messages = objects.map {|object| object.errors.full_messages}.flatten
538 else
543 else
539 @error_messages = objects.errors.full_messages
544 @error_messages = objects.errors.full_messages
540 end
545 end
541 render :template => 'common/error_messages.api', :status => :unprocessable_entity, :layout => nil
546 render :template => 'common/error_messages.api', :status => :unprocessable_entity, :layout => nil
542 end
547 end
543
548
544 # Overrides #_include_layout? so that #render with no arguments
549 # Overrides #_include_layout? so that #render with no arguments
545 # doesn't use the layout for api requests
550 # doesn't use the layout for api requests
546 def _include_layout?(*args)
551 def _include_layout?(*args)
547 api_request? ? false : super
552 api_request? ? false : super
548 end
553 end
549 end
554 end
@@ -1,63 +1,68
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class ApplicationTest < ActionController::IntegrationTest
20 class ApplicationTest < ActionController::IntegrationTest
21 include Redmine::I18n
21 include Redmine::I18n
22
22
23 fixtures :projects, :trackers, :issue_statuses, :issues,
23 fixtures :projects, :trackers, :issue_statuses, :issues,
24 :enumerations, :users, :issue_categories,
24 :enumerations, :users, :issue_categories,
25 :projects_trackers,
25 :projects_trackers,
26 :roles,
26 :roles,
27 :member_roles,
27 :member_roles,
28 :members,
28 :members,
29 :enabled_modules,
29 :enabled_modules,
30 :workflows
30 :workflows
31
31
32 def test_set_localization
32 def test_set_localization
33 Setting.default_language = 'en'
33 Setting.default_language = 'en'
34
34
35 # a french user
35 # a french user
36 get 'projects', { }, 'HTTP_ACCEPT_LANGUAGE' => 'fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3'
36 get 'projects', { }, 'HTTP_ACCEPT_LANGUAGE' => 'fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3'
37 assert_response :success
37 assert_response :success
38 assert_tag :tag => 'h2', :content => 'Projets'
38 assert_tag :tag => 'h2', :content => 'Projets'
39 assert_equal :fr, current_language
39 assert_equal :fr, current_language
40
40
41 # then an italien user
41 # then an italien user
42 get 'projects', { }, 'HTTP_ACCEPT_LANGUAGE' => 'it;q=0.8,en-us;q=0.5,en;q=0.3'
42 get 'projects', { }, 'HTTP_ACCEPT_LANGUAGE' => 'it;q=0.8,en-us;q=0.5,en;q=0.3'
43 assert_response :success
43 assert_response :success
44 assert_tag :tag => 'h2', :content => 'Progetti'
44 assert_tag :tag => 'h2', :content => 'Progetti'
45 assert_equal :it, current_language
45 assert_equal :it, current_language
46
46
47 # not a supported language: default language should be used
47 # not a supported language: default language should be used
48 get 'projects', { }, 'HTTP_ACCEPT_LANGUAGE' => 'zz'
48 get 'projects', { }, 'HTTP_ACCEPT_LANGUAGE' => 'zz'
49 assert_response :success
49 assert_response :success
50 assert_tag :tag => 'h2', :content => 'Projects'
50 assert_tag :tag => 'h2', :content => 'Projects'
51 end
51 end
52
52
53 def test_token_based_access_should_not_start_session
53 def test_token_based_access_should_not_start_session
54 # issue of a private project
54 # issue of a private project
55 get 'issues/4.atom'
55 get 'issues/4.atom'
56 assert_response 302
56 assert_response 302
57
57
58 rss_key = User.find(2).rss_key
58 rss_key = User.find(2).rss_key
59 get "issues/4.atom?key=#{rss_key}"
59 get "issues/4.atom?key=#{rss_key}"
60 assert_response 200
60 assert_response 200
61 assert_nil session[:user_id]
61 assert_nil session[:user_id]
62 end
62 end
63
64 def test_missing_template_should_respond_with_404
65 get '/login.png'
66 assert_response 404
67 end
63 end
68 end
General Comments 0
You need to be logged in to leave comments. Login now