17830: Customizes ActionDispatch::RequestId middleware to our needs.
[arvados.git] / services / api / app / controllers / application_controller.rb
index d5bc3f35d7e04fc47f9795f8a3643f4aef8bcf57..c39bdde4b878a26446404979b08e8c3bd08e2b75 100644 (file)
@@ -45,6 +45,7 @@ class ApplicationController < ActionController::Base
   before_action :load_required_parameters
   before_action(:find_object_by_uuid,
                 except: [:index, :create] + ERROR_ACTIONS)
+  before_action(:set_nullable_attrs_to_null, only: [:update, :create])
   before_action :load_limit_offset_order_params, only: [:index, :contents]
   before_action :load_where_param, only: [:index, :contents]
   before_action :load_filters_param, only: [:index, :contents]
@@ -52,6 +53,7 @@ class ApplicationController < ActionController::Base
   before_action :reload_object_before_update, :only => :update
   before_action(:render_404_if_no_object,
                 except: [:index, :create] + ERROR_ACTIONS)
+  before_action :only_admin_can_bypass_federation
 
   attr_writer :resource_attrs
 
@@ -61,7 +63,6 @@ class ApplicationController < ActionController::Base
                 :with => :render_error)
     rescue_from(ActiveRecord::RecordNotFound,
                 ActionController::RoutingError,
-                ActionController::UnknownController,
                 AbstractController::ActionNotFound,
                 :with => :render_not_found)
   end
@@ -138,10 +139,21 @@ class ApplicationController < ActionController::Base
     render_not_found "Object not found" if !@object
   end
 
+  def only_admin_can_bypass_federation
+    unless !params[:bypass_federation] || current_user.andand.is_admin
+      send_error("The bypass_federation parameter is only permitted when current user is admin", status: 403)
+    end
+  end
+
   def render_error(e)
     logger.error e.inspect
-    if !e.is_a? RequestError and (e.respond_to? :backtrace and e.backtrace)
-      logger.error e.backtrace.collect { |x| x + "\n" }.join('')
+    if e.respond_to? :backtrace and e.backtrace
+      # This will be cleared by lograge after adding it to the log.
+      # Usually lograge would get the exceptions, but in our case we're catching
+      # all of them with exception handlers that cannot re-raise them because they
+      # don't get propagated.
+      Thread.current[:exception] = e.inspect
+      Thread.current[:backtrace] = e.backtrace.collect { |x| x + "\n" }.join('')
     end
     if (@object.respond_to? :errors and
         @object.errors.andand.full_messages.andand.any?)
@@ -170,7 +182,7 @@ class ApplicationController < ActionController::Base
       if params[pname].is_a?(Boolean)
         return params[pname]
       else
-        logger.warn "Warning: received non-boolean parameter '#{pname}' on #{self.class.inspect}."
+        logger.warn "Warning: received non-boolean value #{params[pname].inspect} for boolean parameter #{pname} on #{self.class.inspect}, treating as false."
       end
     end
     false
@@ -184,7 +196,7 @@ class ApplicationController < ActionController::Base
     end
     err[:errors] ||= args
     err[:errors].map! do |err|
-      err += " (" + Thread.current[:request_id] + ")"
+      err += " (#{request.request_id})"
     end
     err[:error_token] = [Time.now.utc.to_i, "%08x" % rand(16 ** 8)].join("+")
     status = err.delete(:status) || 422
@@ -348,7 +360,7 @@ class ApplicationController < ActionController::Base
     %w(created_at modified_by_client_uuid modified_by_user_uuid modified_at).each do |x|
       @attrs.delete x.to_sym
     end
-    @attrs = @attrs.symbolize_keys if @attrs.is_a? HashWithIndifferentAccess
+    @attrs = @attrs.symbolize_keys if @attrs.is_a? ActiveSupport::HashWithIndifferentAccess
     @attrs
   end
 
@@ -385,7 +397,7 @@ class ApplicationController < ActionController::Base
     if not current_user
       respond_to do |format|
         format.json { send_error("Not logged in", status: 401) }
-        format.html { redirect_to '/auth/joshid' }
+        format.html { redirect_to '/login' }
       end
       false
     end
@@ -407,17 +419,9 @@ class ApplicationController < ActionController::Base
   end
 
   def set_current_request_id
-    req_id = request.headers['X-Request-Id']
-    if !req_id || req_id.length < 1 || req_id.length > 1024
-      # Client-supplied ID is either missing or too long to be
-      # considered friendly.
-      req_id = "req-" + Random::DEFAULT.rand(2**128).to_s(36)[0..19]
-    end
-    response.headers['X-Request-Id'] = Thread.current[:request_id] = req_id
-    Rails.logger.tagged(req_id) do
+    Rails.logger.tagged(request.request_id) do
       yield
     end
-    Thread.current[:request_id] = nil
   end
 
   def append_info_to_payload(payload)
@@ -428,7 +432,7 @@ class ApplicationController < ActionController::Base
   end
 
   def disable_api_methods
-    if Rails.configuration.API.DisabledAPIs.include?(controller_name + "." + action_name)
+    if Rails.configuration.API.DisabledAPIs[controller_name + "." + action_name]
       send_error("Disabled", status: 404)
     end
   end
@@ -473,6 +477,29 @@ class ApplicationController < ActionController::Base
     @object = @objects.first
   end
 
+  def nullable_attributes
+    []
+  end
+
+  # Go code may send empty values (ie: empty string instead of NULL) that
+  # should be translated to NULL on the database.
+  def set_nullable_attrs_to_null
+    nullify_attrs(resource_attrs.to_hash).each do |k, v|
+      resource_attrs[k] = v
+    end
+  end
+
+  def nullify_attrs(a = {})
+    new_attrs = a.to_hash.symbolize_keys
+    (new_attrs.keys & nullable_attributes).each do |attr|
+      val = new_attrs[attr]
+      if (val.class == Integer && val == 0) || (val.class == String && val == "")
+        new_attrs[attr] = nil
+      end
+    end
+    return new_attrs
+  end
+
   def reload_object_before_update
     # This is necessary to prevent an ActiveRecord::ReadOnlyRecord
     # error when updating an object which was retrieved using a join.
@@ -543,7 +570,7 @@ class ApplicationController < ActionController::Base
       if @objects.respond_to? :except
         list[:items_available] = @objects.
           except(:limit).except(:offset).
-          distinct.count(:id)
+          count(@distinct ? :id : '*')
       end
     when 'none'
     else
@@ -576,7 +603,7 @@ class ApplicationController < ActionController::Base
         # Make sure params[key] is either true or false -- not a
         # string, not nil, etc.
         if not params.include?(key)
-          params[key] = info[:default]
+          params[key] = info[:default] || false
         elsif [false, 'false', '0', 0].include? params[key]
           params[key] = false
         elsif [true, 'true', '1', 1].include? params[key]
@@ -627,6 +654,11 @@ class ApplicationController < ActionController::Base
         location: "query",
         required: false,
       },
+      bypass_federation: {
+        type: 'boolean',
+        required: false,
+        description: 'bypass federation behavior, list items from local instance database only'
+      }
     }
   end