filter out studio products
[opensuse:openfate.git] / app / controllers / application_controller.rb
1 # Filters added to this controller apply to all controllers in the application.
2 # Likewise, all the methods added will be available for all controllers.
3
4 class ApplicationController < ActionController::Base
5   include ExceptionNotification::Notifiable
6   helper :all # include all helpers, all the time
7   before_filter :set_return_to, :init_user, :set_client
8   protect_from_forgery
9   has_mobile_views
10
11   class InvalidHttpMethodError < Exception; end
12
13   def auto_complete_userid
14     params[:q].gsub!( /[\W]/, '') unless params[:q].blank?
15     render :text => $usernames.keys.select { |t| /#{params[:q]}/.match( t ) }.join("\n")
16   end
17
18   def load_userinfo
19     render :json => $usernames[params[:userid]]
20   end
21
22   protected
23
24   def valid_http_methods(*methods)
25     methods.map {|x| x.to_s.downcase.to_s}
26     unless methods.include? request.method
27       raise InvalidHttpMethodError, "Invalid HTTP Method: #{request.method.to_s.upcase}"
28     end
29   end
30   
31   def init_user
32     # debug user in config file
33     if (!CONFIG['user']['user_id'].nil?)
34       userid = CONFIG['user']['user_id'].downcase
35       logger.info "Debug mode, using userdata from config (#{userid}) for " + @return_to_path
36     elsif (!request.headers['HTTP_X_USERNAME'].nil?)
37       # ichain user, with secret key check
38       userid = CGI::escapeHTML(request.headers['HTTP_X_USERNAME']).downcase
39       logger.info "iChain authenticated access (#{userid}) to " + @return_to_path
40       if !$usernames.has_key?( userid )
41         $usernames[userid] = {:uid => userid, :email => request.headers['HTTP_X_EMAIL'],
42           :fullname => "#{request.headers['HTTP_X_FIRSTNAME']} #{request.headers['HTTP_X_LASTNAME']}"}
43       end
44       ichain_secret = request.headers['HTTP_X_OPENSUSE_DATA']
45       if (CONFIG['ichain']['secret'] != ichain_secret)
46         logger.error "iChain secret mismatch: #{ichain_secret} "
47         e = Exception.new("iChain secret mismatch: #{ichain_secret}")
48         ExceptionNotification::Notifier.deliver_exception_notification(e, self, request, {})
49         render_error :message => "Authentication error.", :status => 401
50         return
51       end 
52     elsif (session[:user] && session[:user].uid)
53       # user already in the session
54       userid = session[:user].uid
55       logger.info "session authenticated access (#{userid}) to " + @return_to_path
56     else 
57       logger.info "Anonymous access to " + @return_to_path
58     end
59     
60     if (userid.nil?)
61       # If no user was set, initialize the user object to anonymous
62       reset_session
63       session[:user] = User.new
64       session[:user].uid = 'openfate'
65       session[:user].organization = 'openSUSE.org'
66       session[:user].isAdmin = false
67       session[:user].isMember = false
68       session[:user].isAuthenticated = false
69       session[:user].isScreener = false
70     elsif( session[:user].nil? || session[:user].uid != userid) 
71       # check the login
72       login_user(userid)
73     end
74     set_session_user_id session[:user].uid
75     session[:editedFeatures] = Hash.new if session[:editedFeatures].nil?
76   end
77
78
79   def require_auth
80     if (!session[:user].isAuthenticated)
81       render :template => "main/show_login_form"
82       return
83     end
84   end
85   
86   
87   # load extended user attributes from users.opensuse.org
88   # Note: this function is not used at the moment as its call is commented.
89   def load_external_userdata( userid )
90     uri = URI.parse("http://users.opensuse.org/show/#{CGI::escape(userid)}.xml")
91     request = Net::HTTP::Get.new(uri.path)
92     request.add_field('x-username', userid)
93     logger.info "Loading users.o.o userdata for: #{userid}"
94     begin
95       Net::HTTP.start(uri.host, uri.port) do |http| 
96         http.read_timeout = 5
97         response = http.request(request)
98         unless( response.kind_of? Net::HTTPSuccess )
99           logger.error "users returned '#{response.code}', message: \n#{response.body}"
100         else
101           rootNode = XML::Document.string(response.body).root
102           session[:user].fullName = rootNode.find_first("/user/fullname").content
103           session[:user].email = rootNode.find_first("/user/email").content
104           if (rootNode.find("/user/member").length == 1) then session[:user].isMember = true end
105         end
106       end
107     rescue Exception => e
108       logger.error "Error while loading from users.o.o '#{uri}': " + e.to_s
109     end
110   end
111
112
113   def set_client
114     @client = 'openfate'
115     @appname = "openFATE"
116     if (CONFIG['apptype'] && !CONFIG['apptype'].empty?)
117       @client = CONFIG['apptype'].downcase
118       @appname = CONFIG['apptype']
119     elsif request.host.downcase == "partnerfate.novell.com"
120       @client = 'partnerfate'
121       @appname = "partnerFATE"
122     elsif request.host.downcase == "fate.novell.com"
123       @client = 'webfate'
124       @appname = "webFATE"
125     elsif request.host.downcase == "ideas.opensuse.org" || request.host.downcase == "hackweek.opensuse.org"
126       @client = 'ideas'
127       @appname = "ideas"
128     end
129     
130     # change template lookup path according to used client for this request
131     self.prepend_view_path(RAILS_ROOT + "/app/views/#{@client}")
132     logger.debug "openfate app type: #{@client}"
133     
134     if isPartnerFate
135       if session[:user].isAuthenticated == false
136         render :template => "main/show_login_form"
137         return
138       elsif session[:user].organization == 'openSUSE.org' && !session[:user].isAdmin
139         render_error :message => "Sorry, your account lacks permissions for partnerFATE access.<p/>" + 
140           "Please contact your technical account manager or featureadmin@suse.de.", :status => 401
141         return
142       end
143     end
144   end
145   
146   
147   def require_admin_user
148     if !(session[:user] && session[:user].isAdmin) then
149       render_error :message => "Unauthorized access", :status => 401
150     end
151   end
152
153
154   def set_return_to
155     # we cannot get the original protocol when behind lighttpd/apache
156     @return_to_host = params['return_to_host'] || "https://" + request.host
157     @return_to_path = params['return_to_path'] || request.env['REQUEST_URI'].gsub(/&/, '&amp;')
158     #logger.debug "Setting return_to: \"#{@return_to_path}\""
159   end
160
161
162   def render_error( opt={} )
163     @message = opt[:message] || "No message set"
164     @exception = opt[:exception]
165     @status = opt[:status] || 400
166     logger.error "ERROR: #{@status} #{@message}"
167     render :template => 'main/error'
168   end
169
170   
171   def isOpenFate
172     return @client == "openfate"
173   end
174
175
176   def isPartnerFate
177     return @client == "partnerfate"
178   end
179
180
181   def isIdeas
182     return @client == "ideas"
183   end
184
185   def isWebFate
186     return @client == "webfate"
187   end
188   
189   def feature_type_filter
190     # Filter for features in openFATE/Partnerfate and for ideas in Ideas mode
191     if isIdeas
192       return " and (@type = 'idea')"
193     else 
194       return " and not (@type = 'idea' or @type = 'requirement')"
195     end
196   end
197   
198   
199   def extract_errormessage( xml )
200     xml =~ /<k:x-sx-errormessage>([^<]+)/
201     if ($1) then return $1.strip
202     else return xml
203     end
204   end
205
206
207   def extract_errorcode( xml )
208     xml =~ /<k:x-sx-errorcode>([^<]+)/
209     if ($1) then return $1.strip
210     else return 0
211     end
212   end
213
214
215   # load feature either from keeper or from session if it 
216   # is unsaved. It is stored as string in the session because 
217   # XML::Smart and libxml cannot serialize.
218   def get_feature
219     if params[:id].nil? then
220       render_error( :message => "Missing feature id." )
221     elsif !session[:editedFeatures].has_key?(params[:id]) then
222       @feature = Feature.loadFeature( params[:id], session[:user], @client )
223     else
224       @feature = Feature.new session[:editedFeatures][params[:id]]
225       @feature.setModified(true)
226     end
227   end
228   
229   def get_all_tags
230     cacheid = "#{@client}_#{get_user_org}_tags"
231     tags = Rails.cache.fetch(cacheid, :expires_in => 120.minutes) do
232       logger.info( "Tag cache outdated, refreshing." )
233       if isWebFate then
234         Feature.all_tags
235       else
236         # Refresh list of visible features first
237         features = Feature.queryFeatures( "/feature[partnercontext/organization='#{get_user_org}']", session[:user], @client)
238         features = features.map{|f| f.id}
239         Feature.all_tags(features)
240       end
241     end
242     tags
243   end
244   
245   
246   # get the organization used in queries (always opensuse.org in openfate)
247   def get_user_org ()
248     if isOpenFate || isIdeas
249       return 'openSUSE.org'
250     elsif isWebFate
251       return 'Novell'
252     else
253       return session[:user].organization
254     end
255   end
256
257
258   def get_query_org ()
259     if isWebFate
260       return "partnercontext/organization"
261     else
262       return "partnercontext/organization='#{get_user_org()}'"
263     end
264   end
265
266
267   def get_query_all ()
268     if isWebFate
269       return "/feature"
270     else
271       return "/feature[partnercontext/organization='#{get_user_org()}']"
272     end
273   end
274
275
276   # sets the available products
277   def set_products
278     if isOpenFate then
279       @products = $productlist.products( ["openSUSE", "SUSE Studio"], false, @client ).
280         select{|e| !/^SL.+/.match( e.last["fatename"] ) && !/^SUSE L/.match( e.last["fatename"] ) &&
281           !/Studio Advanced/.match( e.last["fatename"] ) && !/Studio Extension/.match( e.last["fatename"] ) &&
282           !/Studio Onsite/.match( e.last["fatename"] ) && !/Studio Premium/.match( e.last["fatename"] ) &&
283           !/Studio Standard/.match( e.last["fatename"] )
284         }
285       @newproducts = $productlist.products( ["openSUSE", "SUSE Studio"], true, @client )
286     elsif isPartnerFate
287       prodlines = ["SUSE Linux Enterprise Server", "SUSE Linux Enterprise Desktop", 
288         "SUSE Linux Enterprise Software Development Kit", "SUSE Studio",
289         "SUSE Linux Enterprise Point of Service"]
290       @products = $productlist.products( prodlines, false, @client)
291       @newproducts = $productlist.products( prodlines, true, @client )
292     elsif isWebFate
293       @products = $productlist.products( [], false, @client )
294       @newproducts = $productlist.products( [], true, @client )
295     else
296       @products = $productlist.products( ["openSUSE", "SUSE Studio"], false, @client )
297       @newproducts = $productlist.products( ["openSUSE", "SUSE Studio"], true, @client )
298     end
299   end
300   
301   ## Create a session for the current user with userid
302   def login_user (userid)
303     #reset_session
304     if (( session[:user] = User.find_by_uid(userid) ).nil?)
305       session[:user] = User.new
306       session[:user].uid = userid
307       session[:user].organization = 'openSUSE.org'
308       session[:user].isAdmin = false
309       session[:user].isMember= false
310       session[:user].isScreener = false
311     end
312     session[:user].isAuthenticated = true
313     # we don't need external data atm
314     #load_external_userdata userid
315     logger.info "Logged in user: #{session[:user].uid}, partner: #{session[:user].organization}"
316   end
317
318   def set_session_user_id id
319     ActiveRecord::Base.connection.update("UPDATE sessions SET user_id = '#{id}' WHERE session_id = '#{request.session_options[:id]}'")
320   end
321
322   def rescue_action_locally( exception )
323     rescue_action_in_public( exception )
324   end
325
326   def rescue_action_in_public( exception )
327     logger.error "rescue_action: caught #{exception.class}: #{exception.message}"
328     case exception
329     when ActionController::RoutingError
330       render_error :status => 404, :message => "no such route"
331     when InvalidHttpMethodError
332       render_error :status => 400, :message => exception.message, :exception => exception
333     when ActionController::UnknownAction
334       render_error :status => 404, :message => "unknown action"
335     else
336       ExceptionNotification::Notifier.deliver_exception_notification( exception, self, request, {} ) if send_exception_mail?
337       render_error :status => 400, :code => extract_errorcode( exception.message ), :message => extract_errormessage( exception.message ),
338         :exception => exception
339     end
340   end
341
342   def render_error( opt={} )
343     # :code is a string that comes from the api, :status is the http status code
344     @status = opt[:status] || 400
345     @code = opt[:code] || @status
346     @message = opt[:message] || "No message set"
347     @exception = opt[:exception] if local_request?
348     logger.debug "Rendering error: #{@code}; #{@message}"
349     if request.xhr?
350       render :text => @message, :status => @status, :layout => false
351     else
352       render :template => 'main/error', :status => @status, :locals => {:code => @code, :message => @message,
353         :exception => @exception, :status => @status }
354     end
355   end
356
357   def send_exception_mail?
358     !local_request? && !Rails.env.development? && !ExceptionNotification::Notifier.exception_recipients.blank?
359   end
360
361 end