X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/e0cf8505654d0e1c20b3d6ad31eefdab1fa342fe..4a78635a105b43bac10f2f14a4a549f2b05c8a5c:/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 d7ffd9c792..e3f92efe23 100644 --- a/apps/workbench/app/controllers/application_controller.rb +++ b/apps/workbench/app/controllers/application_controller.rb @@ -8,24 +8,24 @@ class ApplicationController < ActionController::Base ERROR_ACTIONS = [:render_error, :render_not_found] around_filter :thread_clear - around_filter :thread_with_mandatory_api_token, except: ERROR_ACTIONS - around_filter :thread_with_optional_api_token + around_filter :set_thread_api_token + # Methods that don't require login should + # skip_around_filter :require_thread_api_token + 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 :find_object_by_uuid, except: [:index, :choose] + ERROR_ACTIONS theme :select_theme begin - rescue_from Exception, - :with => :render_exception - rescue_from ActiveRecord::RecordNotFound, - :with => :render_not_found - rescue_from ActionController::RoutingError, - :with => :render_not_found - rescue_from ActionController::UnknownController, - :with => :render_not_found - rescue_from ::AbstractController::ActionNotFound, - :with => :render_not_found + rescue_from(ActiveRecord::RecordNotFound, + ActionController::RoutingError, + ActionController::UnknownController, + AbstractController::ActionNotFound, + with: :render_not_found) + rescue_from(Exception, + ActionController::UrlGenerationError, + with: :render_exception) end def unprocessable(message=nil) @@ -55,13 +55,21 @@ class ApplicationController < ActionController::Base else @errors = [e.to_s] end - self.render_error status: 422 + if e.is_a? ArvadosApiClient::NotLoggedInException + self.render_error status: 422 + else + set_thread_api_token do + self.render_error status: 422 + end + end end def render_not_found(e=ActionController::RoutingError.new("Path not found")) logger.error e.inspect @errors = ["Path not found"] - self.render_error status: 404 + set_thread_api_token do + self.render_error status: 404 + end end def find_objects_for_index @@ -148,6 +156,10 @@ class ApplicationController < ActionController::Base 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 @@ -292,11 +304,15 @@ class ApplicationController < ActionController::Base protected + def strip_token_from_path(path) + path.sub(/([\?&;])api_token=[^&;]*[&;]?/, '\1') + end + def redirect_to_login respond_to do |f| f.html { if request.method.in? ['GET', 'HEAD'] - redirect_to arvados_api_client.arvados_login_url(return_to: request.url) + redirect_to arvados_api_client.arvados_login_url(return_to: strip_token_from_path(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 @@ -355,58 +371,51 @@ class ApplicationController < ActionController::Base Rails.cache.delete_matched(/^request_#{Thread.current.object_id}_/) end - def thread_with_api_token(login_optional = false) + # 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 + return + end + begin - try_redirect_to_login = true - if params[:api_token] - try_redirect_to_login = false - Thread.current[:arvados_api_token] = params[:api_token] - # Before copying the token into session[], do a simple API - # call to verify its authenticity. - if 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 request.fullpath.sub(%r{([&\?]api_token=)[^&\?]*}, '') - else - yield - end - else - @errors = ['Invalid API token'] - self.render_error status: 401 - end - elsif session[:arvados_api_token] - # In this case, the token must have already verified at some - # point, but it might have been revoked since. We'll try - # using it, and catch the exception if it doesn't work. - try_redirect_to_login = false - Thread.current[:arvados_api_token] = session[:arvados_api_token] - begin - yield - rescue ArvadosApiClient::NotLoggedInException - try_redirect_to_login = true + # 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 - else - logger.debug "No token received, session is #{session.inspect}" end - if try_redirect_to_login - unless login_optional - redirect_to_login - else - # login is optional for this route so go on to the regular controller + + # With setup done, handle the request using the session token. + Thread.current[:arvados_api_token] = session[:arvados_api_token] + begin + 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 @@ -417,32 +426,18 @@ class ApplicationController < ActionController::Base end end - def thread_with_mandatory_api_token - thread_with_api_token(true) do - if Thread.current[:arvados_api_token] - yield - elsif session[:arvados_api_token] - # Expired session. Clear it before refreshing login so that, - # if this login procedure fails, we end up showing the "please - # log in" page instead of getting stuck in a redirect loop. - session.delete :arvados_api_token - redirect_to_login - else - render 'users/welcome' - end - end - end - - # This runs after thread_with_mandatory_api_token in the filter chain. - def thread_with_optional_api_token + # Reroute this request if an API token is unavailable. + def require_thread_api_token if Thread.current[:arvados_api_token] - # We are already inside thread_with_mandatory_api_token. yield + elsif session[:arvados_api_token] + # Expired session. Clear it before refreshing login so that, + # if this login procedure fails, we end up showing the "please + # log in" page instead of getting stuck in a redirect loop. + session.delete :arvados_api_token + redirect_to_login else - # We skipped thread_with_mandatory_api_token. Use the optional version. - thread_with_api_token(true) do - yield - end + render 'users/welcome' end end @@ -733,7 +728,7 @@ class ApplicationController < ActionController::Base def get_n_objects_of_class dataclass, size @objects_map_for ||= {} - raise ArgumentError, 'Argument is not a data class' unless dataclass.is_a? Class + raise ArgumentError, 'Argument is not a data class' unless dataclass.is_a? Class and dataclass < ArvadosBase raise ArgumentError, 'Argument is not a valid limit size' unless (size && size>0) # if the objects_map_for has a value for this dataclass, and the