diff --git a/app/controllers/api/v1/base_controller.rb b/app/controllers/api/v1/base_controller.rb index e0fc7e0..6c74396 100644 --- a/app/controllers/api/v1/base_controller.rb +++ b/app/controllers/api/v1/base_controller.rb @@ -35,7 +35,6 @@ class BaseController < ActionController::API private - def decoded_token return @decoded_token if defined?(@decoded_token) @@ -117,8 +116,6 @@ def require_user false end - private - def find_user_from_token return unless decoded_token user_data = decoded_token[:user] diff --git a/app/controllers/api/v1/saved_scenarios_controller.rb b/app/controllers/api/v1/saved_scenarios_controller.rb index 16710f6..2116f06 100644 --- a/app/controllers/api/v1/saved_scenarios_controller.rb +++ b/app/controllers/api/v1/saved_scenarios_controller.rb @@ -68,7 +68,15 @@ def saved_scenario_params end def engine_client - MyEtm::Auth.engine_client(current_user)#, scopes: doorkeeper_token.scopes) + MyEtm::Auth.engine_client(current_user, active_version_tag, scopes: doorkeeper_token.scopes) + end + + def active_version_tag + if Version.tags.include?(saved_scenario_params[:version].to_s) + saved_scenario_params[:version] + else + Version::DEFAULT_TAG + end end end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index f140b25..bbd39ff 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -7,7 +7,9 @@ class ApplicationController < ActionController::Base before_action :set_locale before_action :configure_sentry before_action :store_user_location!, if: :storable_location? - before_action :store_redirect_url + before_action :set_active_version_tag + + helper_method :active_version_tag rescue_from CanCan::AccessDenied do |_exception| if current_user @@ -36,8 +38,8 @@ def set_locale session[:locale] || http_accept_language.preferred_language_from(I18n.available_locales) end - def last_visited_page - redirect_to cookies[:last_visited_page] || root_path + def active_version_tag + session[:active_version_tag] || Version::DEFAULT_TAG end private @@ -74,8 +76,8 @@ def configure_sentry end end - def engine_client - MyEtm::Auth.engine_client(current_user) + def engine_client(version_tag) + MyEtm::Auth.engine_client(current_user, version_tag) end # Internal: Renders a 404 page. @@ -136,9 +138,14 @@ def turbo_alert(message = nil) ) end - def store_redirect_url - if params[:redirect_url].present? - session[:redirect_url] = params[:redirect_url] - end + # Validates the version tag passed from the latest request and sets it in the + # session, so we can redirect back to that version later. + # + # TODO: somebody has to set this! + def set_active_version_tag + return unless params[:active_version] + return unless Version.tags.include?(params[:active_version].to_s) + + session[:active_version_tag] = params[:active_version] end end diff --git a/app/controllers/collections_controller.rb b/app/controllers/collections_controller.rb index af573eb..8a1a455 100644 --- a/app/controllers/collections_controller.rb +++ b/app/controllers/collections_controller.rb @@ -75,7 +75,7 @@ def show # POST /collections/create_transition def create_transition result = CreateInterpolatedCollection.call( - engine_client, + engine_client(create_transition_params[:version]), current_user.saved_scenarios.find(create_transition_params[:saved_scenario_ids]), current_user ) @@ -120,8 +120,8 @@ def confirm_destroy # DELETE /collections/:id def destroy DeleteCollection.call( - engine_client, - current_user.collections.find(params.require(:id)) + engine_client(@collection.version), + @collection ) redirect_to collections_path @@ -178,7 +178,7 @@ def create_collection_params end def create_transition_params - params.require(:collection).permit(:saved_scenario_ids) + params.require(:collection).permit(:version, :saved_scenario_ids) end def filter_params diff --git a/app/controllers/passthru_controller.rb b/app/controllers/passthru_controller.rb new file mode 100644 index 0000000..1b63a25 --- /dev/null +++ b/app/controllers/passthru_controller.rb @@ -0,0 +1,5 @@ +class PassthruController < ApplicationController + def last + redirect_to cookies[:etm_last_visited_page] || root_path + end +end \ No newline at end of file diff --git a/app/controllers/saved_scenario_history_controller.rb b/app/controllers/saved_scenario_history_controller.rb index 70448a2..4a79852 100644 --- a/app/controllers/saved_scenario_history_controller.rb +++ b/app/controllers/saved_scenario_history_controller.rb @@ -12,7 +12,10 @@ class SavedScenarioHistoryController < ApplicationController # GET /saved_scenarios/:id/history def index - version_tags_result = ApiScenario::VersionTags::FetchAll.call(engine_client, @saved_scenario) + version_tags_result = ApiScenario::VersionTags::FetchAll.call( + engine_client(@saved_scenario.version), + @saved_scenario + ) if version_tags_result.successful? @history = SavedScenarioHistoryPresenter.present(@saved_scenario, version_tags_result.value) @@ -32,7 +35,7 @@ def index # PUT /saved_scenarios/:id/history/:scenario_id def update result = ApiScenario::VersionTags::Update.call( - engine_client, + engine_client(@saved_scenario.version), params[:scenario_id], update_params[:description] ) diff --git a/app/controllers/saved_scenario_users_controller.rb b/app/controllers/saved_scenario_users_controller.rb index af78896..3cba662 100644 --- a/app/controllers/saved_scenario_users_controller.rb +++ b/app/controllers/saved_scenario_users_controller.rb @@ -37,7 +37,10 @@ def new # POST /saved_scenarios/:saved_scenario_id/users def create result = CreateSavedScenarioUser.call( - engine_client, @saved_scenario, current_user.name, scenario_user_params + engine_client(@saved_scenario.version), + @saved_scenario, + current_user.name, + scenario_user_params ) if result.successful? @@ -71,7 +74,7 @@ def create # PUT /saved_scenarios/:saved_scenario_id/users/:id def update result = UpdateSavedScenarioUser.call( - engine_client, + engine_client(@saved_scenario.version), @saved_scenario, @saved_scenario_user, scenario_user_params[:role_id]&.to_i @@ -110,7 +113,11 @@ def confirm_destroy # # PUT /saved_scenarios/:saved_scenario_id/users/:id def destroy - result = DestroySavedScenarioUser.call(engine_client, @saved_scenario, @saved_scenario_user) + result = DestroySavedScenarioUser.call( + engine_client(@saved_scenario.version), + @saved_scenario, + @saved_scenario_user + ) if result.successful? @saved_scenario.reload diff --git a/app/controllers/saved_scenarios_controller.rb b/app/controllers/saved_scenarios_controller.rb index 17d3c9a..8a17a8c 100644 --- a/app/controllers/saved_scenarios_controller.rb +++ b/app/controllers/saved_scenarios_controller.rb @@ -114,7 +114,7 @@ def publish @saved_scenario.update(private: false) ApiScenario::UpdatePrivacy.call_with_ids( - engine_client, + engine_client(@saved_scenario.version), @saved_scenario.all_scenario_ids, private: false ) @@ -127,7 +127,7 @@ def unpublish @saved_scenario.update(private: true) ApiScenario::UpdatePrivacy.call_with_ids( - engine_client, + engine_client(@saved_scenario.version), @saved_scenario.all_scenario_ids, private: true ) diff --git a/app/controllers/users/registrations_controller.rb b/app/controllers/users/registrations_controller.rb index c151d94..abdfa66 100644 --- a/app/controllers/users/registrations_controller.rb +++ b/app/controllers/users/registrations_controller.rb @@ -57,7 +57,6 @@ def configure_account_update_params # Check if the user is already signed in and redirect back to client or to root. def check_already_authenticated return unless user_signed_in? - redirect_uri = session[:redirect_url].chomp('/') token = MyEtm::Auth.user_jwt(current_user, client_id: params[:client_id]) redirect_url = URI(params[:redirect_url] || root_path) redirect_url.query = URI.encode_www_form(token: token) @@ -71,7 +70,7 @@ def stats_for_destroy personal_access_tokens: current_user.personal_access_tokens.not_expired.count, oauth_applications: current_user.oauth_applications.count, collections: 0 - # collections: current_user.collections.count + # collections: current_user.collections.count # TODO: Re-Implement } end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 78ebdf8..2b0e55e 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -30,11 +30,11 @@ def format_staff_config(config, app) etengine_url = Settings.etengine.uri || "http://YOUR_ETENGINE_URL" format(config, app.attributes.symbolize_keys.merge( - myetm_url: root_url.chomp("/root"), + myetm_url: root_url.chomp("/"), etengine_url: etengine_url, etmodel_url: Settings.etmodel.uri || "http://YOUR_ETMODEL_URL", collections_url: Settings.collections.uri || "http://YOUR_COLLECTIONS_URL", - etengine_uid: Doorkeeper::Application.find_by(uri: etengine_url)&.uid || "YOUR_ETEngine_ID_HERE" + etengine_uid: Doorkeeper::Application.find_by(uri: etengine_url)&.uid || "YOUR_ETEngine_ID" )) end diff --git a/app/models/version.rb b/app/models/version.rb index 45a021e..ff5151c 100644 --- a/app/models/version.rb +++ b/app/models/version.rb @@ -1,8 +1,10 @@ # frozen_string_literal: true +# TODO: port to db and hook into OAuth apps. This is a mess and not nice to keep up for beta and pro! # A valid version of the ETM class Version URL = "energytransitionmodel.com".freeze + DEFAULT_TAG = "latest" # Tag => prefix LIST = { @@ -11,6 +13,12 @@ class Version "stable.02" => "stable2." }.freeze + LOCAL_URLS = { + "collections" => Settings.collections.uri, + "model" => Settings.etmodel.uri, + "engine" => Settings.etengine.uri + }.freeze + # All available versions. Uses ActiveRecord syntax 'all' to # make future porting to db easier def self.all @@ -21,26 +29,39 @@ def self.tags LIST.keys end - def self.model_url(tag) - "https://#{LIST[tag]}#{Version::URL}" + def self.collections_url(tag = nil) + build_url("collections", tag) end - def self.engine_url(tag) - "https://#{LIST[tag]}engine.#{Version::URL}" + def self.model_url(tag = nil) + build_url("model", tag) end - # TODO: Collections url - - # TODO: urls for local development => Add a local version and - # exceptions for the urls + def self.engine_url(tag = nil) + build_url("engine", tag) + end def self.as_json(*) Version.tags.map do |tag| { tag: tag, - url: Version.model_url(tag), - engine_url: Version.engine_url(tag) + model_url: model_url(tag), + engine_url: engine_url(tag), + collections_url: collections_url(tag) } end end + + private + + def self.build_url(context, tag) + tag ||= DEFAULT_TAG + raise ArgumentError, "Invalid version tag: #{tag}" unless LIST.key?(tag) + + if Rails.env.development? + LOCAL_URLS[context] + else + "https://#{LIST[tag]}#{context == 'model' ? '' : "#{context}."}#{URL}" + end + end end diff --git a/app/services/create_staff_application.rb b/app/services/create_staff_application.rb index 20d93e2..704f555 100644 --- a/app/services/create_staff_application.rb +++ b/app/services/create_staff_application.rb @@ -25,7 +25,7 @@ def self.call(user, app_config, uri: nil) # Update application attributes app.attributes = app_config.to_model_attributes.merge( - owner: user, + owner_id: user.id, uri: parsed_uri.to_s, redirect_uri: redirect_uri.to_s ) diff --git a/app/views/layouts/_buttons.html.haml b/app/views/layouts/_buttons.html.haml index c1df0c8..78ac6d2 100644 --- a/app/views/layouts/_buttons.html.haml +++ b/app/views/layouts/_buttons.html.haml @@ -1,5 +1,5 @@ .flex.py-5 .flex.basis-full{class: 'lg:basis-3/4'} - - if cookies[:last_visited_page].present? + - if cookies[:etm_last_visited_page].present? .bg-gray-100.p-2.px-5.mr-0.ml-auto.rounded-md - = link_to t('continue_working'), last_visited_page_path, class: 'continue-button' + = link_to t('continue_working'), back_to_etm_path, class: 'continue-button' diff --git a/app/views/layouts/_sidebar.html.haml b/app/views/layouts/_sidebar.html.haml index 1faf8c2..db0e301 100644 --- a/app/views/layouts/_sidebar.html.haml +++ b/app/views/layouts/_sidebar.html.haml @@ -1,6 +1,5 @@ .sidebar.fixed.top-0.bottom-0.overflow-y-auto.bg-midnight-300{class: 'lg:left-0 w-[300px]'} - -# Where does the logo refer to? REMEMBER LAST VERSION IN COOKIE - %a.logo.p-5.mb-3.inline-block.w-full.text-midnight-800 + %a.logo.p-5.mb-3.inline-block.w-full.text-midnight-800{href: Version.model_url(active_version_tag)} = image_tag 'header/logo-round.png', class: 'h-8 inline mb-1 mr-2 hover:animate-spin' %span Energy Transition Model diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb index 8e160c4..f7cac85 100644 --- a/config/initializers/doorkeeper.rb +++ b/config/initializers/doorkeeper.rb @@ -41,7 +41,7 @@ # # access_token_class "Doorkeeper::AccessToken" # access_grant_class "Doorkeeper::AccessGrant" - application_class 'OAuthApplication' + application_class "OAuthApplication" # # Don't forget to include Doorkeeper ORM mixins into your custom models: # diff --git a/config/routes.rb b/config/routes.rb index 50cf62d..4cf8895 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -13,7 +13,7 @@ mount Sidekiq::Web => '/sidekiq' end - get '/last_visited_page', to: 'application#last_visited_page', as: :last_visited_page + get '/passthru/last', to: 'passthru#last', as: :back_to_etm namespace :identity do get '/', to: redirect('/identity/profile') diff --git a/lib/myetm/auth.rb b/lib/myetm/auth.rb index 96f8ac6..bd3c610 100644 --- a/lib/myetm/auth.rb +++ b/lib/myetm/auth.rb @@ -53,31 +53,41 @@ def user_jwt(user = nil, scopes: [], client_id: nil) end # Returns a Faraday client for a user, which will send requests to the specified client app. - def client_app_client(user, client_app) - client_app_client ||= begin - Faraday.new(client_app.uri) do |conn| - conn.request(:authorization, "Bearer", -> { - user_jwt(user, scopes: client_app.scopes, client_id: client_app.uid) }) - conn.request(:json) - conn.response(:json) - conn.response(:raise_error) - end + # + # If scopes are specified (e.g. from an access token) these scopes are granted + # Otherwise the configured app scopes are used + def client_for(user, client_app, scopes: []) + scopes = scopes.empty? ? client_app.scopes : scopes + + Faraday.new(client_app.uri) do |conn| + conn.request( + :authorization, + "Bearer", + -> { user_jwt(user, scopes: scopes, client_id: client_app.uid) } + ) + conn.request(:json) + conn.response(:json) + conn.response(:raise_error) end end - # TODO: use the uri's generated by Version to dynamiclly pick the correct - # model and engine - def engine_client(user) - engine = OAuthApplication.find_by(uri: Settings.etengine.uri) - client_app_client(user, engine) + # Returns a Faraday client for a version of ETEngine + # + # If scopes are specified (e.g. from an access token) these scopes are granted + # Otherwise the configured app scopes are used + def engine_client(user, version_tag = Version::DEFAULT_TAG, scopes: []) + uri = Version.engine_url(version_tag) + engine = OAuthApplication.find_by(uri: uri) + client_for(user, engine, scopes: scopes) end - def model_client(user) - model = OAuthApplication.find_by(uri: Settings.etmodel.uri) - client_app_client(user, model) + # Returns a Faraday client for a version of ETModel + def model_client(user, version_tag = Version::DEFAULT_TAG) + uri = Version.model_url(version_tag) + model = OAuthApplication.find_by(uri: uri) + client_for(user, model) end - # Checks if the token is in JWT format def jwt_format?(token) token.count(".") == 2 @@ -115,6 +125,6 @@ def verify_claims(decoded_token) end module_function :decode, :jwt_format?, :verify_claims, :signing_key_content, :user_jwt, - :signing_key, :model_client, :engine_client, :client_app_client + :signing_key, :model_client, :engine_client, :client_for end end diff --git a/spec/controllers/api/v1/versions_controller_spec.rb b/spec/controllers/api/v1/versions_controller_spec.rb index 5c63390..37ef1b9 100644 --- a/spec/controllers/api/v1/versions_controller_spec.rb +++ b/spec/controllers/api/v1/versions_controller_spec.rb @@ -19,8 +19,9 @@ expect(parsed_response['versions']).to be_present expect(parsed_response['versions'].map { |v| v['tag'] }).to match_array(Version.tags) - expect(parsed_response['versions'].first).to have_key('url') + expect(parsed_response['versions'].first).to have_key('model_url') expect(parsed_response['versions'].first).to have_key('engine_url') + expect(parsed_response['versions'].first).to have_key('collections_url') end end end diff --git a/spec/controllers/collections_controller_spec.rb b/spec/controllers/collections_controller_spec.rb index a547e81..5ff043d 100644 --- a/spec/controllers/collections_controller_spec.rb +++ b/spec/controllers/collections_controller_spec.rb @@ -26,7 +26,7 @@ end it 'redirects to the collection' do - post :create_transition, params: { collection: {saved_scenario_ids: scenario.id }} + post :create_transition, params: { collection: {saved_scenario_ids: scenario.id, version: Version.tags.last }} expect(response).to redirect_to( collection_path(Collection.last) diff --git a/spec/requests/saved_scenarios_spec.rb b/spec/requests/saved_scenarios_spec.rb index 5e1fff8..81d8cb5 100644 --- a/spec/requests/saved_scenarios_spec.rb +++ b/spec/requests/saved_scenarios_spec.rb @@ -38,7 +38,7 @@ let(:admin) { FactoryBot.create(:admin) } let!(:admin_scenario) { FactoryBot.create(:saved_scenario, user: admin, id: 648696) } - before { allow(MyEtm::Auth).to receive(:client_app_client).and_return(client) } + before { allow(MyEtm::Auth).to receive(:client_for).and_return(client) } describe "GET /index" do before do