app/periscope/periscope_controller.rb in periscope_rails-0.0.8 vs app/periscope/periscope_controller.rb in periscope_rails-0.0.9

- old
+ new

@@ -1,90 +1,90 @@ -class PeriscopeController < ActionController::Base - before_filter :authenticate - protect_from_forgery :except => [:look, :login] - - def look - if !params[:sql].nil? - render :json => run_sql(params[:sql]) - else - render :json => {:error => "Command not understood"} - end - end - - def login - render :json => get_info() - end - - private - - def authenticate - unless PeriscopeRails::Config.check_password(params[:password].to_s) - render :json => {:error => "Password invalid."} - end - end - - def run_sql(sql_command) - #TODO: protect based on CFG, not blacklist - bad_words = %W{drop delete update into insert index add remove grant revoke create createdb} - bad_words += %W{createuser createrole destroy disconnect exec execute dropdb primary key rollback ; --} - - rows = nil - error_message = nil - command = sql_command.to_s.strip - command_words = command.downcase.gsub(/[^a-zA-Z0-9]/, " ").gsub(/\s+/, " ").split(" ") - if command == "" - #nothing - elsif (command_words & bad_words).size > 0 - error_message = "Potentially harmful keyword found, blocking script." - else - begin #for whole query - active_record = PeriscopeRails::Config.get_active_record() - begin #just for costing - begin - is_postgres = ActiveRecord::Base.connection.instance_of? ActiveRecord::ConnectionAdapters::PostgreSQLAdapter - rescue Exception => e - is_postgres = false - end - active_record.transaction do #costing - if PeriscopeRails::Config.block_expensive_queries? and is_postgres - active_record.connection.select_all("explain #{command}")[0]["QUERY PLAN"] =~ /rows=(\d+) width=(\d+)\)$/ - row_count, width = $1.to_i, $2.to_i - if row_count > 0 and width > 0 - raise "Command blocked, it may be too slow. Estimated at #{row_count} rows, commands must return fewer than #{PeriscopeRails::Config.max_rows} rows." if row_count > PeriscopeRails::Config.max_rows - raise "Command blocked, it may be too slow. Estimated at #{row_count * width} bytes, commands use less than #{PeriscopeRails::Config.max_size} bytes." if row_count * width > PeriscopeRails::Config.max_size - else - puts "Warning: Periscope was unable to cost this query (2): #{command}" - end - end - raise "OK" #abort all transactions for extra protection - end - rescue Exception => e - puts "Warning: Periscope was unable to cost this query (1): #{command}" unless e.message == "OK" - raise e if e.message.include?("Command blocked") - end - active_record.transaction do #execution - rows = active_record.connection.select_all(command) - rows.each do |row| - row.each_key do |column| - if PeriscopeRails::Config.matches_filter(column) - row[column] = '[FILTERED]' - end - end - end - raise "OK" #abort all transactions for extra protection - end - rescue Exception => e - error_message = e.message unless e.message == "OK" - end - end - return {:error => error_message, :data => rows} - end - - def get_info - tables = [] - table_names = ActiveRecord::Base.connection.tables.sort - table_names.each do |table_name| - tables << {:name => table_name, :columns => ActiveRecord::Base.connection.columns(table_name)} - end - return {:tables => tables, :error => nil} - end -end +class PeriscopeController < ActionController::Base + before_filter :authenticate + protect_from_forgery :except => [:look, :login] + + def look + if !params[:sql].nil? + render :json => run_sql(params[:sql]) + else + render :json => {:error => "Command not understood"} + end + end + + def login + render :json => get_info() + end + + private + + def authenticate + unless PeriscopeRails::Config.check_password(params[:password].to_s) + render :json => {:error => "Password invalid."} + end + end + + def run_sql(sql_command) + #TODO: protect based on CFG, not blacklist + bad_words = %W{drop delete update into insert index add remove grant revoke create createdb} + bad_words += %W{createuser createrole destroy disconnect exec execute dropdb primary key rollback ; --} + + rows = nil + error_message = nil + command = sql_command.to_s.strip + command_words = command.downcase.gsub(/[^a-zA-Z0-9_]/, " ").gsub(/\s+/, " ").split(" ") + if command == "" + #nothing + elsif (command_words & bad_words).size > 0 + error_message = "Potentially harmful keyword found, blocking script." + else + begin #for whole query + active_record = PeriscopeRails::Config.get_active_record() + begin #just for costing + begin + is_postgres = ActiveRecord::Base.connection.instance_of? ActiveRecord::ConnectionAdapters::PostgreSQLAdapter + rescue Exception => e + is_postgres = false + end + active_record.transaction do #costing + if PeriscopeRails::Config.block_expensive_queries? and is_postgres + active_record.connection.select_all("explain #{command}")[0]["QUERY PLAN"] =~ /rows=(\d+) width=(\d+)\)$/ + row_count, width = $1.to_i, $2.to_i + if row_count > 0 and width > 0 + raise "Command blocked, it may be too slow. Estimated at #{row_count} rows, commands must return fewer than #{PeriscopeRails::Config.max_rows} rows." if row_count > PeriscopeRails::Config.max_rows + raise "Command blocked, it may be too slow. Estimated at #{row_count * width} bytes, commands use less than #{PeriscopeRails::Config.max_size} bytes." if row_count * width > PeriscopeRails::Config.max_size + else + puts "Warning: Periscope was unable to cost this query (2): #{command}" + end + end + raise "OK" #abort all transactions for extra protection + end + rescue Exception => e + puts "Warning: Periscope was unable to cost this query (1): #{command}" unless e.message == "OK" + raise e if e.message.include?("Command blocked") + end + active_record.transaction do #execution + rows = active_record.connection.select_all(command) + rows.each do |row| + row.each_key do |column| + if PeriscopeRails::Config.matches_filter(column) + row[column] = '[FILTERED]' + end + end + end + raise "OK" #abort all transactions for extra protection + end + rescue Exception => e + error_message = e.message unless e.message == "OK" + end + end + return {:error => error_message, :data => rows} + end + + def get_info + tables = [] + table_names = ActiveRecord::Base.connection.tables.sort + table_names.each do |table_name| + tables << {:name => table_name, :columns => ActiveRecord::Base.connection.columns(table_name)} + end + return {:tables => tables, :error => nil} + end +end