Add methods for requiring view right in app controller
[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   
27   before_filter :public_and_logged_in
28   before_filter :require_current_eula
29   
30   include SslRequirement # Need to be included after the above
31
32   after_filter :mark_flash_status
33
34   filter_parameter_logging :password, :password_confirmation
35
36   layout :pick_layout_based_on_site
37   
38   rescue_from ActiveRecord::RecordNotFound, :with => :render_not_found
39   rescue_from ActionController::UnknownController, :with => :render_not_found
40   rescue_from ActionController::UnknownAction, :with => :render_not_found
41   rescue_from Grit::GitRuby::Repository::NoSuchPath, :with => :render_not_found
42   rescue_from Grit::Git::GitTimeout, :with => :render_git_timeout
43   rescue_from RecordThrottling::LimitReachedError, :with => :render_throttled_record
44   
45   def rescue_action(exception)
46     return super if RAILS_ENV != "production"
47     
48     case exception
49       # Can't catch RoutingError with rescue_from it seems, 
50       # so do it the old-fashioned way
51     when ActionController::RoutingError
52       render_not_found
53     else
54       super
55     end
56   end
57   
58   def current_site
59     @current_site || Site.default
60   end
61   
62   protected
63     # Sets the before_filters needed to be able to render in a Site specific
64     # context. +options+ is the options for the before_filters
65     def self.renders_in_site_specific_context(options = {})
66       before_filter :find_current_site, options
67       before_filter :redirect_to_current_site_subdomain, options
68     end
69     
70     # Sets the before_filters needed to make sure the requests are rendered
71     # in the "global" (eg without any Site specific layouts + subdomains).
72     # +options+ is the options for the before_filter
73     def self.renders_in_global_context(options = {})
74       before_filter :require_global_site_context, options
75     end
76   
77     # return the url with the +repo+.owner prefixed if it's a mainline repo,
78     # otherwise return the +path_spec+
79     # if +path_spec+ is an array (and no +args+ given) it'll use that as the 
80     # polymorphic-url-style (eg [@project, @repo, @foo])
81     def repo_owner_path(repo, path_spec, *args)
82       if repo.team_repo?
83         if path_spec.is_a?(Symbol)
84           return send("group_#{path_spec}", *args.unshift(repo.owner))
85         else
86           return *unshifted_polymorphic_path(repo, path_spec)
87         end
88       elsif repo.user_repo?
89         if path_spec.is_a?(Symbol)
90           return send("user_#{path_spec}", *args.unshift(repo.owner))
91         else
92           return *unshifted_polymorphic_path(repo, path_spec)
93         end
94       else
95         if path_spec.is_a?(Symbol)
96           return send(path_spec, *args)
97         else
98           return *path_spec
99         end
100       end
101     end
102     helper_method :repo_owner_path
103   
104     def require_user_has_ssh_keys
105       unless current_user.ssh_keys.count > 0
106         flash[:error] = I18n.t "application.require_ssh_keys_error"
107         redirect_to new_user_key_path(current_user)
108         return 
109       end
110     end
111
112     def require_current_user
113       unless @user == current_user
114         flash[:error] = I18n.t "application.require_current_user", :title => current_user.title
115         redirect_to user_path(current_user)
116         return
117       end
118     end
119     
120     def require_not_logged_in
121       redirect_to root_path if logged_in?
122     end
123     
124     def require_current_eula
125       if logged_in?
126         unless current_user.terms_accepted?
127           store_location
128           flash[:error] = I18n.t "views.license.terms_not_accepted"
129           redirect_to user_license_path(current_user)
130           return
131         end
132       end
133       return true
134     end
135     
136     def find_repository_owner
137       if params[:user_id]
138         @owner = User.find_by_login!(params[:user_id])
139         @containing_project = Project.find_by_slug!(params[:project_id]) if params[:project_id]
140       elsif params[:group_id]
141         @owner = Group.find_by_name!(params[:group_id])
142         @containing_project = Project.find_by_slug!(params[:project_id]) if params[:project_id]
143       elsif params[:project_id]
144         @owner = Project.find_by_slug!(params[:project_id])
145         @project = @owner
146       else
147         raise ActiveRecord::RecordNotFound
148       end
149     end
150     
151     def find_repository_owner_and_repository
152       find_repository_owner
153       @owner.repositories.find_by_name!(params[:id])
154     end
155     
156     def find_project
157       @project = Project.find_by_slug!(params[:project_id])
158     end
159     
160     def find_project_and_repository
161       @project = Project.find_by_slug!(params[:project_id])
162       # We want to look in all repositories that's somehow within this project
163       # realm, not just @project.repositories
164       @repository = Repository.find_by_name_and_project_id!(params[:repository_id], @project.id)
165     end
166     
167     def require_view_right_to_repository
168       unless @repository && @repository.can_be_viewed_by?(current_user)
169         flash[:error] = I18n.t "application.require_current_user"
170         redirect_to root_path and return
171       end
172     end
173     
174     def require_view_right_to_project
175       unless @project && @project.can_be_viewed_by?(current_user)
176         flash[:error] = I18n.t "application.require_current_user"
177         redirect_to root_path and return
178       end
179     end
180
181     def check_repository_for_commits
182       unless @repository.has_commits?
183         flash[:notice] = I18n.t "application.no_commits_notice"
184         redirect_to project_repository_path(@project, @repository) and return
185       end
186     end
187     
188     def render_not_found
189       render :template => "#{RAILS_ROOT}/public/404.html", :status => 404, :layout => "application"
190     end
191     
192     def render_git_timeout
193       render :partial => "/shared/git_timeout", :layout => "application" and return
194     end
195     
196     def render_throttled_record
197       render :partial => "/shared/throttled_record",
198         :layout => "application", :status => 412 # precondition failed
199       return false
200     end
201     
202     def public_and_logged_in
203       login_required unless GitoriousConfig['public_mode']
204     end
205     
206     def mark_flash_status
207       unless flash.empty?
208         headers['X-Has-Flash'] = "true"
209       end
210     end
211     
212     # turns ["foo", "bar"] route globbing parameters into "foo/bar"
213     # Note that while the path components will be uri unescaped, any
214     # '+' will be preserved
215     def desplat_path(*paths)
216       # we temporarily swap the + out with a magic byte, so
217       # filenames/branches with +'s won't get unescaped to a space
218       paths.flatten.compact.map do |p|
219         CGI.unescape(p.gsub("+", "\001")).gsub("\001", '+')
220       end.join("/")
221     end
222     helper_method :desplat_path
223     
224     # turns "foo/bar" into ["foo", "bar"] for route globbing
225     def ensplat_path(path)
226       path.split("/").select{|p| !p.blank? }
227     end
228     helper_method :ensplat_path
229     
230     # Returns an array like [branch_ref, *tree_path]
231     def branch_with_tree(branch_ref, tree_path)
232       tree_path = tree_path.is_a?(Array) ? tree_path : ensplat_path(tree_path)
233       ensplat_path(branch_ref) + tree_path
234     end
235     helper_method :branch_with_tree
236     
237     def branch_and_path(branch_and_path, git)
238       branch_and_path = desplat_path(branch_and_path)
239       branch_ref = path = nil
240       heads = Array(git.heads).map{|h| h.name }.sort{|a,b| b.length <=> a.length }
241       heads.each do |head|
242         if branch_and_path.starts_with?(head)
243           branch_ref = head
244           path = ensplat_path(branch_and_path.sub(head, "")) || []
245           break
246         end
247       end
248       unless path # fallback
249         path = ensplat_path(branch_and_path)[1..-1]
250         branch_ref = ensplat_path(branch_and_path)[0]
251       end
252       [branch_ref, path]
253     end
254     
255     def find_current_site
256       @current_site ||= begin
257         if @project
258           @project.site
259         else
260           if !subdomain_without_common.blank?
261             Site.find_by_subdomain(subdomain_without_common)
262           end
263         end
264       end
265     end
266     
267     def pick_layout_based_on_site
268       if current_site && current_site.subdomain
269         current_site.subdomain
270       else
271         "application"
272       end
273     end
274     
275     def subdomain_without_common
276       tld_length = GitoriousConfig["gitorious_host"].split(".").length - 1
277       request.subdomains(tld_length).select{|s| s !~ /^(ww.|secure)$/}.first
278     end
279     
280     def redirect_to_current_site_subdomain
281       return unless request.get?
282       if !current_site.subdomain.blank?
283         if subdomain_without_common != current_site.subdomain
284           url_parameters = {:only_path => false, :host => "#{current_site.subdomain}.#{GitoriousConfig["gitorious_host"]}#{request.port_string}"}.merge(params)
285           redirect_to url_parameters
286         end
287       elsif !subdomain_without_common.blank?
288         redirect_to_top_domain
289       end
290     end
291     
292     def require_global_site_context
293       unless subdomain_without_common.blank?
294         redirect_to_top_domain
295       end
296     end
297     
298     def redirect_to_top_domain
299       host_without_subdomain = {
300         :only_path => false, 
301         :host => GitoriousConfig["gitorious_host"]
302       }
303       if ![80, 443].include?(request.port)
304         host_without_subdomain[:host] << ":#{request.port}"
305       end
306       redirect_to host_without_subdomain
307     end
308     
309     # A wrapper around ActionPack's #stale?, that always returns true
310     # if there's data in the flash hash
311     def stale_conditional?(etag, last_modified)
312       return true unless flash.empty?
313       stale?(:etag => [etag, current_user], :last_modified => last_modified)
314     end
315     
316   private  
317     def unshifted_polymorphic_path(repo, path_spec)
318       if path_spec[0].is_a?(Symbol)
319         path_spec.insert(1, repo.owner)
320       else
321         path_spec.unshift(repo.owner)
322       end
323     end
324 end