98b31a5f5ae2d92a9627515e5c4d18216a1e17ba
[arvados.git] / app / controllers / application_controller.rb
1 class ApplicationController < ActionController::Base
2   protect_from_forgery
3   before_filter :uncamelcase_params_hash_keys
4   around_filter :thread_with_auth_info, :except => [:render_error, :render_not_found]
5   before_filter :find_object_by_uuid, :except => :index
6
7   before_filter :remote_ip
8   before_filter :login_required, :except => :render_not_found
9
10   before_filter :catch_redirect_hint
11
12   def catch_redirect_hint
13     if !current_user
14       if params.has_key?('redirect_to') then
15         session[:redirect_to] = params[:redirect_to]
16       end
17     end
18   end
19
20   unless Rails.application.config.consider_all_requests_local
21     rescue_from Exception,
22     :with => :render_error
23     rescue_from ActiveRecord::RecordNotFound,
24     :with => :render_not_found
25     rescue_from ActionController::RoutingError,
26     :with => :render_not_found
27     rescue_from ActionController::UnknownController,
28     :with => :render_not_found
29     rescue_from ActionController::UnknownAction,
30     :with => :render_not_found
31   end
32
33   def render_error(e)
34     logger.error e.inspect
35     logger.error e.backtrace.collect { |x| x + "\n" }.join('') if e.backtrace
36     if @object and @object.errors and @object.errors.full_messages
37       errors = @object.errors.full_messages
38     else
39       errors = [e.inspect]
40     end
41     render json: { errors: errors }, status: 422
42   end
43
44   def render_not_found(e=ActionController::RoutingError.new("Path not found"))
45     logger.error e.inspect
46     render json: { errors: ["Path not found"] }, status: 404
47   end
48
49   def index
50     @objects ||= model_class.
51       joins("LEFT JOIN metadata permissions ON permissions.tail=#{table_name}.uuid AND permissions.head=#{model_class.sanitize Thread.current[:user_uuid]} AND permissions.metadata_class='permission' AND permissions.name='visible_to'").
52       where("#{table_name}.created_by_user=? OR permissions.head IS NOT NULL",
53             Thread.current[:user_uuid])
54     if params[:where]
55       where = params[:where]
56       where = JSON.parse(where) if where.is_a?(String)
57       conditions = ['1=1']
58       where.each do |attr,value|
59         if (!value.nil? and
60             attr.to_s.match(/^[a-z][_a-z0-9]+$/) and
61             model_class.columns.collect(&:name).index(attr))
62           if value.is_a? Array
63             conditions[0] << " and #{table_name}.#{attr} in (?)"
64             conditions << value
65           else
66             conditions[0] << " and #{table_name}.#{attr}=?"
67             conditions << value
68           end
69         end
70       end
71       if conditions.length > 1
72         conditions[0].sub!(/^1=1 and /, '')
73         @objects = @objects.
74           where(*conditions)
75       end
76     end
77     if params[:eager] and params[:eager] != '0' and params[:eager] != 0 and params[:eager] != ''
78       @objects.each(&:eager_load_associations)
79     end
80     render_list
81   end
82
83   def show
84     if @object
85       render json: @object.as_api_response(:superuser)
86     else
87       render_not_found("object not found")
88     end
89   end
90
91   def create
92     @attrs = params[resource_name]
93     if @attrs.nil?
94       raise "no #{resource_name} (or #{resource_name.camelcase(:lower)}) provided with request #{params.inspect}"
95     end
96     if @attrs.class == String
97       @attrs = uncamelcase_hash_keys(JSON.parse @attrs)
98     end
99     @object = model_class.new @attrs
100     @object.save
101     show
102   end
103
104   def update
105     @attrs = params[resource_name]
106     if @attrs.is_a? String
107       @attrs = uncamelcase_hash_keys(JSON.parse @attrs)
108     end
109     @object.update_attributes @attrs
110     show
111   end
112
113   def current_user
114     Thread.current[:user]
115   end
116
117   protected
118
119   # Authentication
120   def login_required
121     if !current_user
122       respond_to do |format|
123         format.html  {
124           redirect_to '/auth/joshid'
125         }
126         format.json {
127           render :json => { 'error' => 'Not logged in' }.to_json
128         }
129       end
130     end
131   end
132
133   def thread_with_auth_info
134     begin
135       if params[:api_token]
136         @api_client_auth = ApiClientAuthorization.
137           includes(:api_client, :user).
138           where('api_token=?', params[:api_token]).
139           first
140         if @api_client_auth
141           session[:user_id] = @api_client_auth.user.id
142           session[:user_uuid] = @api_client_auth.user.uuid
143           session[:api_client_uuid] = @api_client_auth.api_client.uuid
144         end
145       end
146       Thread.current[:api_client_trusted] = session[:api_client_trusted]
147       Thread.current[:api_client_ip_address] = remote_ip
148       Thread.current[:api_client_uuid] = session[:api_client_uuid]
149       Thread.current[:user_uuid] = session[:user_uuid]
150       Thread.current[:remote_ip] = remote_ip
151       Thread.current[:user] = User.find(session[:user_id]) rescue nil
152       yield
153     ensure
154       Thread.current[:api_client_trusted] = nil
155       Thread.current[:api_client_ip_address] = nil
156       Thread.current[:api_client_uuid] = nil
157       Thread.current[:user_uuid] = nil
158       Thread.current[:remote_ip] = nil
159       Thread.current[:user] = nil
160     end
161   end
162   # /Authentication
163
164   def model_class
165     controller_name.classify.constantize
166   end
167
168   def resource_name             # params[] key used by client
169     controller_name.singularize
170   end
171
172   def table_name
173     controller_name
174   end
175
176   def find_object_by_uuid
177     if params[:id] and params[:id].match /\D/
178       params[:uuid] = params.delete :id
179     end
180     @object = model_class.where('uuid=?', params[:uuid]).first
181   end
182
183   def self.accept_attribute_as_json(attr, force_class=nil)
184     before_filter lambda { accept_attribute_as_json attr, force_class }
185   end
186   def accept_attribute_as_json(attr, force_class)
187     if params[resource_name].is_a? Hash
188       if params[resource_name][attr].is_a? String
189         params[resource_name][attr] = JSON.parse params[resource_name][attr]
190         if force_class and !params[resource_name][attr].is_a? force_class
191           raise TypeError.new("#{resource_name}[#{attr.to_s}] must be a #{force_class.to_s}")
192         end
193       end
194     end
195   end
196
197   def uncamelcase_params_hash_keys
198     self.params = uncamelcase_hash_keys(params)
199   end
200
201   def uncamelcase_hash_keys(h, max_depth=-1)
202     if h.is_a? Hash and max_depth != 0
203       nh = Hash.new
204       h.each do |k,v|
205         if k.class == String
206           nk = k.underscore
207         elsif k.class == Symbol
208           nk = k.to_s.underscore.to_sym
209         else
210           nk = k
211         end
212         nh[nk] = uncamelcase_hash_keys(v, max_depth-1)
213       end
214       h.replace(nh)
215     end
216     h
217   end
218
219   def render_list
220     @object_list = {
221       :kind  => "orvos##{resource_name}List",
222       :etag => "",
223       :self_link => "",
224       :next_page_token => "",
225       :next_link => "",
226       :items => @objects.as_api_response(:superuser)
227     }
228     render json: @object_list
229   end
230
231 private
232   def remote_ip
233     # Caveat: this is highly dependent on the proxy setup. YMMV.
234     if request.headers.has_key?('HTTP_X_REAL_IP') then
235       # We're behind a reverse proxy
236       @remote_ip = request.headers['HTTP_X_REAL_IP']
237     else
238       # Hopefully, we are not!
239       @remote_ip = request.env['REMOTE_ADDR']
240     end
241   end
242 end