Merge branch 'master' of gitorious.org:+fiit/gitorious/gitorious-fiit-mainline
[gitorious:gitorious-fiit-mainline.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   unless GitoriousConfig['authenticators']
48     GitoriousConfig['authenticators'] = ['password', 'openid']
49     logger.warn("Authenticators are not set in gitorious.yml. Using password and openid.")
50   end
51
52   def rescue_action(exception)
53     return super if RAILS_ENV != "production"
54
55     case exception
56       # Can't catch RoutingError with rescue_from it seems,
57       # so do it the old-fashioned way
58     when ActionController::RoutingError
59       render_not_found
60     else
61       super
62     end
63   end
64
65   def current_site
66     @current_site || Site.default
67   end
68
69   protected
70     # Sets the before_filters needed to be able to render in a Site specific
71     # context. +options+ is the options for the before_filters
72     def self.renders_in_site_specific_context(options = {})
73       before_filter :find_current_site, options
74       before_filter :redirect_to_current_site_subdomain, options
75     end
76
77     # Sets the before_filters needed to make sure the requests are rendered
78     # in the "global" (eg without any Site specific layouts + subdomains).
79     # +options+ is the options for the before_filter
80     def self.renders_in_global_context(options = {})
81       before_filter :require_global_site_context, options
82     end
83
84     def require_user_has_ssh_keys
85       unless current_user.ssh_keys.count > 0
86         flash[:error] = I18n.t "application.require_ssh_keys_error"
87         redirect_to new_user_key_path(current_user)
88         return
89       end
90     end
91
92     def require_current_user
93       unless @user == current_user
94         flash[:error] = I18n.t "application.require_current_user", :title => current_user.title
95         redirect_to user_path(current_user)
96         return
97       end
98     end
99
100     def require_not_logged_in
101       redirect_to root_path if logged_in?
102     end
103
104     def require_current_eula
105       if logged_in?
106         unless current_user.terms_accepted?
107           store_location
108           flash[:error] = I18n.t "views.license.terms_not_accepted"
109           redirect_to user_license_path(current_user)
110           return
111         end
112       end
113       return true
114     end
115
116     def find_repository_owner
117       if params[:user_id]
118         @owner = User.find_by_login!(params[:user_id])
119         @containing_project = Project.find_by_slug!(params[:project_id]) if params[:project_id]
120       elsif params[:group_id]
121         @owner = Group.find_by_name!(params[:group_id])
122         @containing_project = Project.find_by_slug!(params[:project_id]) if params[:project_id]
123       elsif params[:project_id]
124         @owner = Project.find_by_slug!(params[:project_id])
125         @project = @owner
126       else
127         raise ActiveRecord::RecordNotFound
128       end
129     end
130
131     def find_repository_owner_and_repository
132       find_repository_owner
133       @owner.repositories.find_by_name!(params[:id])
134     end
135
136     def find_project
137       @project = Project.find_by_slug!(params[:project_id])
138     end
139
140     def find_project_and_repository
141       @project = Project.find_by_slug!(params[:project_id])
142       # We want to look in all repositories that's somehow within this project
143       # realm, not just @project.repositories
144       @repository = Repository.find_by_name_and_project_id!(params[:repository_id], @project.id)
145     end
146
147     def check_repository_for_commits
148       unless @repository.has_commits?
149         flash[:notice] = I18n.t "application.no_commits_notice"
150         redirect_to project_repository_path(@project, @repository) and return
151       end
152     end
153
154     def render_not_found
155       render :template => "#{RAILS_ROOT}/public/404.html", :status => 404, :layout => "application"
156     end
157
158     def render_git_timeout
159       render :partial => "/shared/git_timeout", :layout => "application" and return
160     end
161
162     def render_throttled_record
163       render :partial => "/shared/throttled_record",
164         :layout => "application", :status => 412 # precondition failed
165       return false
166     end
167
168     def public_and_logged_in
169       login_required unless GitoriousConfig['public_mode']
170     end
171
172     def mark_flash_status
173       unless flash.empty?
174         headers['X-Has-Flash'] = "true"
175       end
176     end
177
178     # Returns an array like [branch_ref, *tree_path]
179     def branch_with_tree(branch_ref, tree_path)
180       tree_path = tree_path.is_a?(Array) ? tree_path : ensplat_path(tree_path)
181       ensplat_path(branch_ref) + tree_path
182     end
183     helper_method :branch_with_tree
184
185     def branch_and_path(branch_and_path, git)
186       branch_and_path = desplat_path(branch_and_path)
187       branch_ref = path = nil
188       heads = Array(git.heads).map{|h| h.name }.sort{|a,b| b.length <=> a.length }
189       heads.each do |head|
190         if "#{branch_and_path}/".starts_with?("#{head}/")
191           branch_ref = head
192           path = ensplat_path(branch_and_path.sub(head, "")) || []
193           break
194         end
195       end
196       unless path
197         tags = Array(git.tags).map{|t| t.name }.sort{|a,b| b.length <=> a.length }
198         tags.each do |tag|
199           if "#{branch_and_path}/".starts_with?("#{tag}/")
200             branch_ref = tag
201             path = ensplat_path(branch_and_path.sub(tag, "")) || []
202             break
203           end
204         end
205       end
206       unless path # fallback
207         path = ensplat_path(branch_and_path)[1..-1]
208         branch_ref = ensplat_path(branch_and_path)[0]
209       end
210       [branch_ref, path]
211     end
212
213     def find_current_site
214       @current_site ||= begin
215         if @project
216           @project.site
217         else
218           if !subdomain_without_common.blank?
219             Site.find_by_subdomain(subdomain_without_common)
220           end
221         end
222       end
223     end
224
225     def pick_layout_based_on_site
226       if current_site && current_site.subdomain
227         current_site.subdomain
228       else
229         "application"
230       end
231     end
232
233     def subdomain_without_common
234       tld_length = GitoriousConfig["gitorious_host"].split(".").length - 1
235       request.subdomains(tld_length).select{|s| s !~ /^(ww.|secure)$/}.first
236     end
237
238     def redirect_to_current_site_subdomain
239       return unless request.get?
240       if !current_site.subdomain.blank?
241         if subdomain_without_common != current_site.subdomain
242           url_parameters = {:only_path => false, :host => "#{current_site.subdomain}.#{GitoriousConfig["gitorious_host"]}#{request.port_string}"}.merge(params)
243           redirect_to url_parameters
244         end
245       elsif !subdomain_without_common.blank?
246         redirect_to_top_domain
247       end
248     end
249
250     def require_global_site_context
251       unless subdomain_without_common.blank?
252         redirect_to_top_domain
253       end
254     end
255
256     def redirect_to_top_domain
257       host_without_subdomain = {
258         :only_path => false,
259         :host => GitoriousConfig["gitorious_host"]
260       }
261       if ![80, 443].include?(request.port)
262         host_without_subdomain[:host] << ":#{request.port}"
263       end
264       redirect_to host_without_subdomain
265     end
266
267     def self.skip_session(options = {})
268       skip_before_filter :public_and_logged_in, options
269       skip_before_filter :require_current_eula, options
270       skip_after_filter :mark_flash_status, options
271       prepend_before_filter :skip_session_expiry, options
272     end
273
274     def skip_session_expiry
275       request.session_options[:expire_after] = nil
276     end
277     
278     def cache_forever
279       headers["Cache-Control"] = "public, max-age=315360000"
280     end
281
282     # A wrapper around ActionPack's #stale?, that always returns true
283     # if there's data in the flash hash
284     def stale_conditional?(etag, last_modified)
285       return true unless flash.empty?
286       stale?(:etag => [etag, current_user], :last_modified => last_modified)
287     end
288
289   private
290     def ssl_required?
291       return false if !GitoriousConfig["use_ssl"]
292       return true if request.ssl?
293       !request.session_options[:expire_after].nil? && logged_in?
294     end
295
296     def unshifted_polymorphic_path(repo, path_spec)
297       if path_spec[0].is_a?(Symbol)
298         path_spec.insert(1, repo.owner)
299       else
300         path_spec.unshift(repo.owner)
301       end
302     end
303
304     helper_method :unshifted_polymorphic_path
305 end