X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/116dbb500bd013ff76c7fe5d773e4bcfff645777..27f5c1635d56c3f3cb6c5ef069c28db939eec2a1:/apps/workbench/app/controllers/application_controller.rb diff --git a/apps/workbench/app/controllers/application_controller.rb b/apps/workbench/app/controllers/application_controller.rb index 9812ba0906..2fbd8c560f 100644 --- a/apps/workbench/app/controllers/application_controller.rb +++ b/apps/workbench/app/controllers/application_controller.rb @@ -1,10 +1,21 @@ class ApplicationController < ActionController::Base + respond_to :html, :json, :js protect_from_forgery + + ERROR_ACTIONS = [:render_error, :render_not_found] + around_filter :thread_clear - around_filter :thread_with_api_token, :except => [:render_exception, :render_not_found] - before_filter :find_object_by_uuid, :except => [:index, :render_exception, :render_not_found] + around_filter(:thread_with_mandatory_api_token, + except: [:index, :show] + ERROR_ACTIONS) + around_filter :thread_with_optional_api_token + before_filter :check_user_agreements, except: ERROR_ACTIONS + before_filter :check_user_notifications, except: ERROR_ACTIONS + around_filter :using_reader_tokens, only: [:index, :show] + before_filter :find_object_by_uuid, except: [:index] + ERROR_ACTIONS + before_filter :check_my_folders, :except => ERROR_ACTIONS + theme :select_theme - unless Rails.application.config.consider_all_requests_local + begin rescue_from Exception, :with => :render_exception rescue_from ActiveRecord::RecordNotFound, @@ -13,30 +24,36 @@ class ApplicationController < ActionController::Base :with => :render_not_found rescue_from ActionController::UnknownController, :with => :render_not_found - rescue_from ActionController::UnknownAction, + rescue_from ::AbstractController::ActionNotFound, :with => :render_not_found end def unprocessable(message=nil) @errors ||= [] + @errors << message if message render_error status: 422 end def render_error(opts) + opts = {status: 500}.merge opts respond_to do |f| - f.html { render opts.merge(controller: 'application', action: 'error') } + # json must come before html here, so it gets used as the + # default format when js is requested by the client. This lets + # ajax:error callback parse the response correctly, even though + # the browser can't. f.json { render opts.merge(json: {success: false, errors: @errors}) } + f.html { render opts.merge(controller: 'application', action: 'error') } end end def render_exception(e) logger.error e.inspect logger.error e.backtrace.collect { |x| x + "\n" }.join('') if e.backtrace - if @object and @object.errors and @object.errors.full_messages + if @object.andand.errors.andand.full_messages.andand.any? @errors = @object.errors.full_messages else - @errors = [e.inspect] + @errors = [e.to_s] end self.render_error status: 422 end @@ -47,12 +64,32 @@ class ApplicationController < ActionController::Base self.render_error status: 404 end - def index - @objects ||= model_class.all + @limit ||= 200 + if params[:limit] + @limit = params[:limit].to_i + end + + @offset ||= 0 + if params[:offset] + @offset = params[:offset].to_i + end + + @filters ||= [] + if params[:filters] + filters = params[:filters] + if filters.is_a? String + filters = Oj.load filters + end + @filters += filters + end + + @objects ||= model_class + @objects = @objects.filter(@filters).limit(@limit).offset(@offset).all respond_to do |f| f.json { render json: @objects } f.html { render } + f.js { render } end end @@ -61,8 +98,21 @@ class ApplicationController < ActionController::Base return render_not_found("object not found") end respond_to do |f| - f.json { render json: @object } - f.html { render } + f.json { render json: @object.attributes.merge(href: url_for(@object)) } + f.html { + if request.method == 'GET' + render + else + redirect_to params[:return_to] || @object + end + } + f.js { render } + end + end + + def render_content + if !@object + return render_not_found("object not found") end end @@ -71,7 +121,21 @@ class ApplicationController < ActionController::Base end def update - if @object.update_attributes params[@object.class.to_s.underscore.singularize.to_sym] + updates = params[@object.class.to_s.underscore.singularize.to_sym] + updates.keys.each do |attr| + if @object.send(attr).is_a? Hash + if updates[attr].is_a? String + updates[attr] = Oj.load updates[attr] + end + if params[:merge] || params["merge_#{attr}".to_sym] + # Merge provided Hash with current Hash, instead of + # replacing. + updates[attr] = @object.send(attr).with_indifferent_access. + deep_merge(updates[attr].with_indifferent_access) + end + end + end + if @object.update_attributes updates show else self.render_error status: 422 @@ -79,14 +143,23 @@ class ApplicationController < ActionController::Base end def create - @object ||= model_class.new params[model_class.to_s.singularize.to_sym] + @new_resource_attrs ||= params[model_class.to_s.underscore.singularize] + @new_resource_attrs ||= {} + @new_resource_attrs.reject! { |k,v| k.to_s == 'uuid' } + @object ||= model_class.new @new_resource_attrs @object.save! - redirect_to @object + show end def destroy if @object.destroy - redirect_to(params[:return_to] || :back) + respond_to do |f| + f.json { render json: @object } + f.html { + redirect_to(params[:return_to] || :back) + } + f.js { render } + end else self.render_error status: 422 end @@ -105,22 +178,97 @@ class ApplicationController < ActionController::Base controller_name.classify.constantize end + def breadcrumb_page_name + (@breadcrumb_page_name || + (@object.friendly_link_name if @object.respond_to? :friendly_link_name) || + action_name) + end + + def index_pane_list + %w(Recent) + end + + def show_pane_list + %w(Attributes Metadata JSON API) + end + protected - + + def redirect_to_login + respond_to do |f| + f.html { + if request.method == 'GET' + redirect_to $arvados_api_client.arvados_login_url(return_to: request.url) + else + flash[:error] = "Either you are not logged in, or your session has timed out. I can't automatically log you in and re-attempt this request." + redirect_to :back + end + } + f.json { + @errors = ['You do not seem to be logged in. You did not supply an API token with this request, and your session (if any) has timed out.'] + self.render_error status: 422 + } + end + false # For convenience to return from callbacks + end + + def using_reader_tokens(login_optional=false) + if params[:reader_tokens].is_a?(Array) and params[:reader_tokens].any? + Thread.current[:reader_tokens] = params[:reader_tokens] + end + begin + yield + rescue ArvadosApiClient::NotLoggedInException + if login_optional + raise + else + return redirect_to_login + end + ensure + Thread.current[:reader_tokens] = nil + end + end + + def using_specific_api_token(api_token) + start_values = {} + [:arvados_api_token, :user].each do |key| + start_values[key] = Thread.current[key] + end + Thread.current[:arvados_api_token] = api_token + Thread.current[:user] = nil + begin + yield + ensure + start_values.each_key { |key| Thread.current[key] = start_values[key] } + end + end + def find_object_by_uuid if params[:id] and params[:id].match /\D/ params[:uuid] = params.delete :id end - @object = model_class.where(uuid: params[:uuid]).first + if not model_class + @object = nil + elsif params[:uuid].is_a? String + if params[:uuid].empty? + @object = nil + else + @object = model_class.find(params[:uuid]) + end + else + @object = model_class.where(uuid: params[:uuid]).first + end end def thread_clear Thread.current[:arvados_api_token] = nil Thread.current[:user] = nil + Rails.cache.delete_matched(/^request_#{Thread.current.object_id}_/) yield + Rails.cache.delete_matched(/^request_#{Thread.current.object_id}_/) end - def thread_with_api_token + def thread_with_api_token(login_optional = false) begin try_redirect_to_login = true if params[:api_token] @@ -158,19 +306,12 @@ class ApplicationController < ActionController::Base logger.debug "No token received, session is #{session.inspect}" end if try_redirect_to_login - respond_to do |f| - f.html { - if request.method == 'GET' - redirect_to $arvados_api_client.arvados_login_url(return_to: request.url) - else - flash[:error] = "Either you are not logged in, or your session has timed out. I can't automatically log you in and re-attempt this request." - redirect_to :back - end - } - f.json { - @errors = ['You do not seem to be logged in. You did not supply an API token with this request, and your session (if any) has timed out.'] - self.render_error status: 422 - } + unless login_optional + redirect_to_login + else + # login is optional for this route so go on to the regular controller + Thread.current[:arvados_api_token] = nil + yield end end ensure @@ -179,6 +320,25 @@ class ApplicationController < ActionController::Base end end + def thread_with_mandatory_api_token + thread_with_api_token do + yield + end + end + + # This runs after thread_with_mandatory_api_token in the filter chain. + def thread_with_optional_api_token + if Thread.current[:arvados_api_token] + # We are already inside thread_with_mandatory_api_token. + yield + else + # We skipped thread_with_mandatory_api_token. Use the optional version. + thread_with_api_token(true) do + yield + end + end + end + def verify_api_token begin Link.where(uuid: 'just-verifying-my-api-token') @@ -194,4 +354,99 @@ class ApplicationController < ActionController::Base self.render_error status: 401 end end + + def check_user_agreements + if current_user && !current_user.is_active && current_user.is_invited + signatures = UserAgreement.signatures + @signed_ua_uuids = UserAgreement.signatures.map &:head_uuid + @required_user_agreements = UserAgreement.all.map do |ua| + if not @signed_ua_uuids.index ua.uuid + Collection.find(ua.uuid) + end + end.compact + if @required_user_agreements.empty? + # No agreements to sign. Perhaps we just need to ask? + current_user.activate + if !current_user.is_active + logger.warn "#{current_user.uuid.inspect}: " + + "No user agreements to sign, but activate failed!" + end + end + if !current_user.is_active + render 'user_agreements/index' + end + end + true + end + + def select_theme + return Rails.configuration.arvados_theme + end + + @@notification_tests = [] + + @@notification_tests.push lambda { |controller, current_user| + AuthorizedKey.limit(1).where(authorized_user_uuid: current_user.uuid).each do + return nil + end + return lambda { |view| + view.render partial: 'notifications/ssh_key_notification' + } + } + + #@@notification_tests.push lambda { |controller, current_user| + # Job.limit(1).where(created_by: current_user.uuid).each do + # return nil + # end + # return lambda { |view| + # view.render partial: 'notifications/jobs_notification' + # } + #} + + @@notification_tests.push lambda { |controller, current_user| + Collection.limit(1).where(created_by: current_user.uuid).each do + return nil + end + return lambda { |view| + view.render partial: 'notifications/collections_notification' + } + } + + @@notification_tests.push lambda { |controller, current_user| + PipelineInstance.limit(1).where(created_by: current_user.uuid).each do + return nil + end + return lambda { |view| + view.render partial: 'notifications/pipelines_notification' + } + } + + def check_my_folders + @my_top_level_folders = lambda do + @top_level_folders ||= Group. + filter([['group_class','=','folder'], + ['owner_uuid','=',current_user.uuid]]). + sort_by { |x| x.name || '' } + end + end + + def check_user_notifications + @notification_count = 0 + @notifications = [] + + if current_user + @showallalerts = false + @@notification_tests.each do |t| + a = t.call(self, current_user) + if a + @notification_count += 1 + @notifications.push a + end + end + end + + if @notification_count == 0 + @notification_count = '' + end + end end