X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/53fa082a61545d9161644bfd9c951b1c4e828c27..42d84bd014b3ab3a1906c9ba68b39835765fe705:/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 3652ebf818..df57e9e71f 100644 --- a/apps/workbench/app/controllers/application_controller.rb +++ b/apps/workbench/app/controllers/application_controller.rb @@ -14,6 +14,7 @@ class ApplicationController < ActionController::Base around_filter :require_thread_api_token, except: ERROR_ACTIONS before_filter :check_user_agreements, except: ERROR_ACTIONS before_filter :check_user_notifications, except: ERROR_ACTIONS + before_filter :load_filters_and_paging_params, except: ERROR_ACTIONS before_filter :find_object_by_uuid, except: [:index, :choose] + ERROR_ACTIONS theme :select_theme @@ -35,44 +36,58 @@ class ApplicationController < ActionController::Base render_error status: 422 end - def render_error(opts) - opts = {status: 500}.merge opts + def render_error(opts={}) + opts[:status] ||= 500 respond_to do |f| # 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') } + f.html { render({action: 'error'}.merge(opts)) } end end def render_exception(e) logger.error e.inspect logger.error e.backtrace.collect { |x| x + "\n" }.join('') if e.backtrace - if @object.andand.errors.andand.full_messages.andand.any? + err_opts = {status: 422} + if e.is_a?(ArvadosApiClient::ApiError) + err_opts.merge!(action: 'api_error', locals: {api_error: e}) + @errors = e.api_response[:errors] + elsif @object.andand.errors.andand.full_messages.andand.any? @errors = @object.errors.full_messages else @errors = [e.to_s] end - if e.is_a? ArvadosApiClient::NotLoggedInException - self.render_error status: 422 - else - set_thread_api_token do - self.render_error status: 422 - end + # If the user has an active session, and the API server is available, + # make user information available on the error page. + begin + load_api_token(session[:arvados_api_token]) + rescue ArvadosApiClient::ApiError + load_api_token(nil) + end + # Preload projects trees for the template. If that fails, set empty + # trees so error page rendering can proceed. (It's easier to rescue the + # exception here than in a template.) + begin + build_project_trees + rescue ArvadosApiClient::ApiError + @my_project_tree ||= [] + @shared_project_tree ||= [] end + render_error(err_opts) end def render_not_found(e=ActionController::RoutingError.new("Path not found")) logger.error e.inspect @errors = ["Path not found"] set_thread_api_token do - self.render_error status: 404 + self.render_error(action: '404', status: 404) end end - def find_objects_for_index + def load_filters_and_paging_params @limit ||= 200 if params[:limit] @limit = params[:limit].to_i @@ -88,10 +103,22 @@ class ApplicationController < ActionController::Base filters = params[:filters] if filters.is_a? String filters = Oj.load filters + elsif filters.is_a? Array + filters = filters.collect do |filter| + if filter.is_a? String + # Accept filters[]=["foo","=","bar"] + Oj.load filter + else + # Accept filters=[["foo","=","bar"]] + filter + end + end end @filters += filters end + end + def find_objects_for_index @objects ||= model_class @objects = @objects.filter(@filters).limit(@limit).offset(@offset) end @@ -118,12 +145,15 @@ class ApplicationController < ActionController::Base end helper_method :next_page_offset - def next_page_offset - if @objects.respond_to?(:result_offset) and - @objects.respond_to?(:result_limit) and - @objects.respond_to?(:items_available) - next_offset = @objects.result_offset + @objects.result_limit - if next_offset < @objects.items_available + def next_page_offset objects=nil + if !objects + objects = @objects + end + if objects.respond_to?(:result_offset) and + objects.respond_to?(:result_limit) and + objects.respond_to?(:items_available) + next_offset = objects.result_offset + objects.result_limit + if next_offset < objects.items_available next_offset else nil @@ -131,6 +161,13 @@ class ApplicationController < ActionController::Base end end + helper_method :next_page_href + def next_page_href with_params={} + if next_page_offset + url_for with_params.merge(offset: next_page_offset) + end + end + def show if !@object return render_not_found("object not found") @@ -154,26 +191,14 @@ class ApplicationController < ActionController::Base def choose params[:limit] ||= 40 - if !@objects - if params[:project_uuid] and !params[:project_uuid].empty? - # We want the chooser to show objects of the controllers's model_class - # type within a specific project specified by project_uuid, so fetch the - # project and request the contents of the project filtered on the - # controllers's model_class kind. - @objects = Group.find(params[:project_uuid]).contents({:filters => [['uuid', 'is_a', "arvados\##{ArvadosApiClient.class_kind(model_class)}"]]}) - end - find_objects_for_index if !@objects - end + find_objects_for_index if !@objects respond_to do |f| if params[:partial] f.json { render json: { content: render_to_string(partial: "choose_rows.html", - formats: [:html], - locals: { - multiple: params[:multiple] - }), - next_page_href: @next_page_href + formats: [:html]), + next_page_href: next_page_href(partial: params[:partial]) } } end @@ -244,7 +269,7 @@ class ApplicationController < ActionController::Base if @object.name and @object.name != '' @object.name = "Copy of #{@object.name}" else - @object.name = "Copy of unnamed #{@object.class_for_display.downcase}" + @object.name = "" end end @object.save! @@ -266,22 +291,7 @@ class ApplicationController < ActionController::Base end def current_user - return Thread.current[:user] if Thread.current[:user] - - if Thread.current[:arvados_api_token] - if session[:user] - if session[:user][:is_active] != true - Thread.current[:user] = User.current - else - Thread.current[:user] = User.new(session[:user]) - end - else - Thread.current[:user] = User.current - end - else - logger.error "No API token in Thread" - return nil - end + Thread.current[:user] end def model_class @@ -331,8 +341,7 @@ class ApplicationController < ActionController::Base [: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 + load_api_token(api_token) begin yield ensure @@ -358,72 +367,97 @@ class ApplicationController < ActionController::Base else @object = model_class.find(params[:uuid]) end - rescue ArvadosApiClient::NotFoundException => error + rescue ArvadosApiClient::NotFoundException, RuntimeError => error + if error.is_a?(RuntimeError) and (error.message !~ /^argument to find\(/) + raise + end render_not_found(error) return false end end def thread_clear - Thread.current[:arvados_api_token] = nil - Thread.current[:user] = nil + load_api_token(nil) Rails.cache.delete_matched(/^request_#{Thread.current.object_id}_/) yield Rails.cache.delete_matched(/^request_#{Thread.current.object_id}_/) end + # Set up the thread with the given API token and associated user object. + def load_api_token(new_token) + Thread.current[:arvados_api_token] = new_token + if new_token.nil? + Thread.current[:user] = nil + elsif (new_token == session[:arvados_api_token]) and + session[:user].andand[:is_active] + Thread.current[:user] = User.new(session[:user]) + else + Thread.current[:user] = User.current + end + end + + # If there's a valid api_token parameter, set up the session with that + # user's information. Return true if the method redirects the request + # (usually a post-login redirect); false otherwise. + def setup_user_session + return false unless params[:api_token] + Thread.current[:arvados_api_token] = params[:api_token] + begin + user = User.current + rescue ArvadosApiClient::NotLoggedInException + false # We may redirect to login, or not, based on the current action. + else + session[:arvados_api_token] = params[:api_token] + session[:user] = { + uuid: user.uuid, + email: user.email, + first_name: user.first_name, + last_name: user.last_name, + is_active: user.is_active, + is_admin: user.is_admin, + prefs: user.prefs + } + if !request.format.json? and request.method.in? ['GET', 'HEAD'] + # Repeat this request with api_token in the (new) session + # cookie instead of the query string. This prevents API + # tokens from appearing in (and being inadvisedly copied + # and pasted from) browser Location bars. + redirect_to strip_token_from_path(request.fullpath) + true + else + false + end + ensure + Thread.current[:arvados_api_token] = nil + end + end + # Save the session API token in thread-local storage, and yield. # This method also takes care of session setup if the request # provides a valid api_token parameter. # If a token is unavailable or expired, the block is still run, with # a nil token. def set_thread_api_token - # If an API token has already been found, pass it through. if Thread.current[:arvados_api_token] - yield + yield # An API token has already been found - pass it through. return + elsif setup_user_session + return # A new session was set up and received a response. end begin - # If there's a valid api_token parameter, use it to set up the session. - if (Thread.current[:arvados_api_token] = params[:api_token]) and - verify_api_token - session[:arvados_api_token] = params[:api_token] - u = User.current - session[:user] = { - uuid: u.uuid, - email: u.email, - first_name: u.first_name, - last_name: u.last_name, - is_active: u.is_active, - is_admin: u.is_admin, - prefs: u.prefs - } - if !request.format.json? and request.method.in? ['GET', 'HEAD'] - # Repeat this request with api_token in the (new) session - # cookie instead of the query string. This prevents API - # tokens from appearing in (and being inadvisedly copied - # and pasted from) browser Location bars. - redirect_to strip_token_from_path(request.fullpath) - return - end - end - - # With setup done, handle the request using the session token. - Thread.current[:arvados_api_token] = session[:arvados_api_token] - begin + load_api_token(session[:arvados_api_token]) + yield + rescue ArvadosApiClient::NotLoggedInException + # If we got this error with a token, it must've expired. + # Retry the request without a token. + unless Thread.current[:arvados_api_token].nil? + load_api_token(nil) yield - rescue ArvadosApiClient::NotLoggedInException - # If we got this error with a token, it must've expired. - # Retry the request without a token. - unless Thread.current[:arvados_api_token].nil? - Thread.current[:arvados_api_token] = nil - yield - end end ensure # Remove token in case this Thread is used for anything else. - Thread.current[:arvados_api_token] = nil + load_api_token(nil) end end @@ -442,15 +476,6 @@ class ApplicationController < ActionController::Base end end - def verify_api_token - begin - Link.where(uuid: 'just-verifying-my-api-token') - true - rescue ArvadosApiClient::NotLoggedInException - false - end - end - def ensure_current_user_is_admin unless current_user and current_user.is_admin @errors = ['Permission denied'] @@ -592,8 +617,8 @@ class ApplicationController < ActionController::Base (Job.limit(10) | PipelineInstance.limit(10)). sort_by do |x| - x.finished_at || x.started_at || x.created_at rescue x.created_at - end + (x.finished_at || x.started_at rescue nil) || x.modified_at || x.created_at + end.reverse end helper_method :my_project_tree @@ -855,4 +880,7 @@ class ApplicationController < ActionController::Base @objects_for end + def wiselinks_layout + 'body' + end end