The skip_session_expiry filter needs to be prepended to the filter chain
[gitorious:taladars-gitorious-saltation.git] / app / controllers / application_controller.rb
1 # encoding: utf-8
2 #--
3 #   Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)
4 #   Copyright (C) 2007, 2008 Johan Sørensen <johan@johansorensen.com>
5 #
6 #   This program is free software: you can redistribute it and/or modify
7 #   it under the terms of the GNU Affero General Public License as published by
8 #   the Free Software Foundation, either version 3 of the License, or
9 #   (at your option) any later version.
10 #
11 #   This program is distributed in the hope that it will be useful,
12 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
13 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 #   GNU Affero General Public License for more details.
15 #
16 #   You should have received a copy of the GNU Affero General Public License
17 #   along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #++
19
20 # Filters added to this controller apply to all controllers in the application.
21 # Likewise, all the methods added will be available for all controllers.
22
23 class ApplicationController < ActionController::Base
24   include AuthenticatedSystem
25   include ExceptionNotifiable
26   include RoutingHelper
27   protect_from_forgery
28
29   before_filter :public_and_logged_in
30   before_filter :require_current_eula
31
32   include SslRequirement # Need to be included after the above
33
34   after_filter :mark_flash_status
35
36   filter_parameter_logging :password, :password_confirmation
37
38   layout :pick_layout_based_on_site
39
40   rescue_from ActiveRecord::RecordNotFound, :with => :render_not_found
41   rescue_from ActionController::UnknownController, :with => :render_not_found
42   rescue_from ActionController::UnknownAction, :with => :render_not_found
43   rescue_from Grit::GitRuby::Repository::NoSuchPath, :with => :render_not_found
44   rescue_from Grit::Git::GitTimeout, :with => :render_git_timeout
45   rescue_from RecordThrottling::LimitReachedError, :with => :render_throttled_record
46
47   def rescue_action(exception)
48     return super if RAILS_ENV != "production"
49
50     case exception
51       # Can't catch RoutingError with rescue_from it seems,
52       # so do it the old-fashioned way
53     when ActionController::RoutingError
54       render_not_found
55     else
56       super
57     end
58   end
59
60   def current_site
61     @current_site || Site.default
62   end
63
64   protected
65     # Sets the before_filters needed to be able to render in a Site specific
66     # context. +options+ is the options for the before_filters
67     def self.renders_in_site_specific_context(options = {})
68       before_filter :find_current_site, options
69       before_filter :redirect_to_current_site_subdomain, options
70     end
71
72     # Sets the before_filters needed to make sure the requests are rendered
73     # in the "global" (eg without any Site specific layouts + subdomains).
74     # +options+ is the options for the before_filter
75     def self.renders_in_global_context(options = {})
76       before_filter :require_global_site_context, options
77     end
78
79     def require_user_has_ssh_keys
80       unless current_user.ssh_keys.count > 0
81         flash[:error] = I18n.t "application.require_ssh_keys_error"
82         redirect_to new_user_key_path(current_user)
83         return
84       end
85     end
86
87     def require_current_user
88       unless @user == current_user
89         flash[:error] = I18n.t "application.require_current_user", :title => current_user.title
90         redirect_to user_path(current_user)
91         return
92       end
93     end
94
95     def require_not_logged_in
96       redirect_to root_path if logged_in?
97     end
98
99     def require_current_eula
100       if logged_in?
101         unless current_user.terms_accepted?
102           store_location
103           flash[:error] = I18n.t "views.license.terms_not_accepted"
104           redirect_to user_license_path(current_user)
105           return
106         end
107       end
108       return true
109     end
110
111     def find_repository_owner
112       if params[:user_id]
113         @owner = User.find_by_login!(params[:user_id])
114         @containing_project = Project.find_by_slug!(params[:project_id]) if params[:project_id]
115       elsif params[:group_id]
116         @owner = Group.find_by_name!(params[:group_id])
117         @containing_project = Project.find_by_slug!(params[:project_id]) if params[:project_id]
118       elsif params[:project_id]
119         @owner = Project.find_by_slug!(params[:project_id])
120         @project = @owner
121       else
122         raise ActiveRecord::RecordNotFound
123       end
124     end
125
126     def find_repository_owner_and_repository
127       find_repository_owner
128       @owner.repositories.find_by_name!(params[:id])
129     end
130
131     def find_project
132       @project = Project.find_by_slug!(params[:project_id])
133     end
134
135     def find_project_and_repository
136       @project = Project.find_by_slug!(params[:project_id])
137       # We want to look in all repositories that's somehow within this project
138       # realm, not just @project.repositories
139       @repository = Repository.find_by_name_and_project_id!(params[:repository_id], @project.id)
140     end
141
142     def check_repository_for_commits
143       unless @repository.has_commits?
144         flash[:notice] = I18n.t "application.no_commits_notice"
145         redirect_to project_repository_path(@project, @repository) and return
146       end
147     end
148
149     def render_not_found
150       render :template => "#{RAILS_ROOT}/public/404.html", :status => 404, :layout => "application"
151     end
152
153     def render_git_timeout
154       render :partial => "/shared/git_timeout", :layout => "application" and return
155     end
156
157     def render_throttled_record
158       render :partial => "/shared/throttled_record",
159         :layout => "application", :status => 412 # precondition failed
160       return false
161     end
162
163     def public_and_logged_in
164       login_required unless GitoriousConfig['public_mode']
165     end
166
167     def mark_flash_status
168       unless flash.empty?
169         headers['X-Has-Flash'] = "true"
170       end
171     end
172
173     # Returns an array like [branch_ref, *tree_path]
174     def branch_with_tree(branch_ref, tree_path)
175       tree_path = tree_path.is_a?(Array) ? tree_path : ensplat_path(tree_path)
176       ensplat_path(branch_ref) + tree_path
177     end
178     helper_method :branch_with_tree
179
180     def branch_and_path(branch_and_path, git)
181       branch_and_path = desplat_path(branch_and_path)
182       branch_ref = path = nil
183       heads = Array(git.heads).map{|h| h.name }.sort{|a,b| b.length <=> a.length }
184       heads.each do |head|
185         if "#{branch_and_path}/".starts_with?("#{head}/")
186           branch_ref = head
187           path = ensplat_path(branch_and_path.sub(head, "")) || []
188           break
189         end
190       end
191       unless path
192         tags = Array(git.tags).map{|t| t.name }.sort{|a,b| b.length <=> a.length }
193         tags.each do |tag|
194           if "#{branch_and_path}/".starts_with?("#{tag}/")
195             branch_ref = tag
196             path = ensplat_path(branch_and_path.sub(tag, "")) || []
197             break
198           end
199         end
200       end
201       unless path # fallback
202         path = ensplat_path(branch_and_path)[1..-1]
203         branch_ref = ensplat_path(branch_and_path)[0]
204       end
205       [branch_ref, path]
206     end
207
208     def find_current_site
209       @current_site ||= begin
210         if @project
211           @project.site
212         else
213           if !subdomain_without_common.blank?
214             Site.find_by_subdomain(subdomain_without_common)
215           end
216         end
217       end
218     end
219
220     def pick_layout_based_on_site
221       if current_site && current_site.subdomain
222         current_site.subdomain
223       else
224         "application"
225       end
226     end
227
228     def subdomain_without_common
229       tld_length = GitoriousConfig["gitorious_host"].split(".").length - 1
230       request.subdomains(tld_length).select{|s| s !~ /^(ww.|secure)$/}.first
231     end
232
233     def redirect_to_current_site_subdomain
234       return unless request.get?
235       if !current_site.subdomain.blank?
236         if subdomain_without_common != current_site.subdomain
237           url_parameters = {:only_path => false, :host => "#{current_site.subdomain}.#{GitoriousConfig["gitorious_host"]}#{request.port_string}"}.merge(params)
238           redirect_to url_parameters
239         end
240       elsif !subdomain_without_common.blank?
241         redirect_to_top_domain
242       end
243     end
244
245     def require_global_site_context
246       unless subdomain_without_common.blank?
247         redirect_to_top_domain
248       end
249     end
250
251     def redirect_to_top_domain
252       host_without_subdomain = {
253         :only_path => false,
254         :host => GitoriousConfig["gitorious_host"]
255       }
256       if ![80, 443].include?(request.port)
257         host_without_subdomain[:host] << ":#{request.port}"
258       end
259       redirect_to host_without_subdomain
260     end
261
262     def self.skip_session(options = {})
263       skip_before_filter :public_and_logged_in, options
264       skip_before_filter :require_current_eula, options
265       skip_after_filter :mark_flash_status, options
266       prepend_before_filter :skip_session_expiry, options
267     end
268
269     def skip_session_expiry
270       request.session_options[:expire_after] = nil
271     end
272     
273     def cache_forever
274       headers["Cache-Control"] = "public, max-age=315360000"
275     end
276
277     # A wrapper around ActionPack's #stale?, that always returns true
278     # if there's data in the flash hash
279     def stale_conditional?(etag, last_modified)
280       return true unless flash.empty?
281       stale?(:etag => [etag, current_user], :last_modified => last_modified)
282     end
283
284   private
285     def ssl_required?
286       return false if !GitoriousConfig["use_ssl"]
287       return true if request.ssl?
288       !request.session_options[:expire_after].nil? && logged_in?
289     end
290
291     def unshifted_polymorphic_path(repo, path_spec)
292       if path_spec[0].is_a?(Symbol)
293         path_spec.insert(1, repo.owner)
294       else
295         path_spec.unshift(repo.owner)
296       end
297     end
298
299     helper_method :unshifted_polymorphic_path
300 end