diff --git a/app/controllers/admin/dfc_product_imports_controller.rb b/app/controllers/admin/dfc_product_imports_controller.rb index 58239786241..be55d822b0c 100644 --- a/app/controllers/admin/dfc_product_imports_controller.rb +++ b/app/controllers/admin/dfc_product_imports_controller.rb @@ -35,6 +35,15 @@ def index end @count = imported.compact.count + rescue Rack::OAuth2::Client::Error => e + flash[:error] = I18n.t( + 'admin.dfc_product_imports.index.oauth_error_html', + message: helpers.sanitize(e.message), + oidc_settings_link: helpers.link_to( + I18n.t('spree.admin.tab.oidc_settings'), Rails.application.routes.url_helpers.admin_oidc_settings_path + ) + ).html_safe + redirect_to admin_product_import_path rescue Faraday::Error, Addressable::URI::InvalidURIError, ActionController::ParameterMissing => e diff --git a/config/locales/en.yml b/config/locales/en.yml index 48ba87aee56..15242a01f31 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -849,6 +849,7 @@ en: index: title: "Importing a DFC product catalog" imported_products: "Imported products:" + oauth_error_html: "Authentication error: %{message}. See %{oidc_settings_link}" enterprise_fees: index: title: "Enterprise Fees" diff --git a/engines/dfc_provider/app/services/dfc_request.rb b/engines/dfc_provider/app/services/dfc_request.rb index c976a697877..bed4be4c654 100644 --- a/engines/dfc_provider/app/services/dfc_request.rb +++ b/engines/dfc_provider/app/services/dfc_request.rb @@ -72,7 +72,8 @@ def refresh_access_token! # It results in an empty config hash and we lose our config. ) client = strategy.client - client.token_endpoint = strategy.config.token_endpoint + binding.pry + client.token_endpoint = strategy.config.token_endpoint # can't work out what's happening under the hood or how to stub it client.refresh_token = @user.oidc_account.refresh_token token = client.access_token! @@ -81,5 +82,11 @@ def refresh_access_token! token: token.access_token, refresh_token: token.refresh_token ) + rescue Rack::OAuth2::Client::Error => e + @user.oidc_account.update!( + token: nil, + refresh_token: nil + ) + raise end end diff --git a/engines/dfc_provider/spec/services/dfc_request_spec.rb b/engines/dfc_provider/spec/services/dfc_request_spec.rb index 0e6fbfb8d34..5150296d672 100644 --- a/engines/dfc_provider/spec/services/dfc_request_spec.rb +++ b/engines/dfc_provider/spec/services/dfc_request_spec.rb @@ -63,27 +63,63 @@ # The absence of errors makes this test pass. end - it "refreshes the access token and retrieves the FDC catalog", vcr: true do - # A refresh is only attempted if the token is stale. - account.uid = "testdfc@protonmail.com" - account.refresh_token = ENV.fetch("OPENID_REFRESH_TOKEN") - account.updated_at = 1.day.ago - - response = nil - expect { - response = api.call( - "https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts" - ) - }.to change { - account.token - }.and change { - account.refresh_token - } - - json = JSON.parse(response) - - graph = DfcIo.import(json) - products = graph.select { |s| s.semanticType == "dfc-b:SuppliedProduct" } - expect(products).to be_present + describe "refreshing token when stale" do + before do + account.uid = "testdfc@protonmail.com" + account.refresh_token = ENV.fetch("OPENID_REFRESH_TOKEN") + account.updated_at = 1.day.ago + end + + it "refreshes the access token and retrieves the FDC catalog", vcr: true do + response = nil + expect { + response = api.call( + "https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts" + ) + }.to change { + account.token + }.and change { + account.refresh_token + } + + json = JSON.parse(response) + + graph = DfcIo.import(json) + products = graph.select { |s| s.semanticType == "dfc-b:SuppliedProduct" } + expect(products).to be_present + end + + context "with account tokens" do + before do + stub_request(:get, "http://example.net/api"). + to_return(status: 401) + + # allow_any_instance_of(OpenIDConnect::Client).to receive(:config).and_return(double(token_endpoint: "")) + stub_request(:get, "https://login.lescommuns.org/auth/realms/data-food-consortium/.well-known/openid-configuration"). + to_return(status: 200, body: {token_endpoint: "/token"}.to_json) + + + account.refresh_token = ENV.fetch("OPENID_REFRESH_TOKEN") + account.token = "anything" + end + + it "clears the token if authentication fails" do + allow_any_instance_of(OpenIDConnect::Client).to receive(:access_token!).and_raise( + Rack::OAuth2::Client::Error.new( + 1, { error: "invalid_grant", error_description: "session not active" } + ) + ) + + expect { + api.call( + "http://example.net/api" + ) + }.to change { + account.token + }.to(nil).and change { + account.refresh_token + }.to(nil) + end + end end end diff --git a/spec/fixtures/vcr_cassettes/DFC_Product_Import/shows_oauth_error_message.yml b/spec/fixtures/vcr_cassettes/DFC_Product_Import/shows_oauth_error_message.yml new file mode 100644 index 00000000000..cae501d4e6d --- /dev/null +++ b/spec/fixtures/vcr_cassettes/DFC_Product_Import/shows_oauth_error_message.yml @@ -0,0 +1,47 @@ +--- +http_interactions: +- request: + method: get + uri: https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts + body: + encoding: US-ASCII + string: '' + headers: + Content-Type: + - application/json + Authorization: + - "" + User-Agent: + - Faraday v2.9.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 403 + message: Forbidden + headers: + Server: + - openresty + Date: + - Tue, 21 Jan 2025 00:56:03 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '78' + Connection: + - keep-alive + X-Powered-By: + - Express + Access-Control-Allow-Origin: + - "*" + Etag: + - W/"4e-vJeBLxgahmv23yP9gdPJW/woako" + Strict-Transport-Security: + - max-age=15811200 + body: + encoding: UTF-8 + string: '{"message":"User access denied - token missing","error":"User not authorized"}' + recorded_at: Tue, 21 Jan 2025 00:56:04 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/system/admin/dfc_product_import_spec.rb b/spec/system/admin/dfc_product_import_spec.rb index bae9881278d..8f19dec45c9 100644 --- a/spec/system/admin/dfc_product_import_spec.rb +++ b/spec/system/admin/dfc_product_import_spec.rb @@ -74,6 +74,30 @@ expect(product.image).to be_present end + it "shows oauth error message", vcr: true do + allow_any_instance_of(DfcRequest).to receive(:refresh_access_token!).and_raise( + Rack::OAuth2::Client::Error.new( + 1, { error: "invalid_grant", error_description: "session not active" } + ) + ) + + user.update!(oidc_account: build(:testdfc_account)) + + visit admin_product_import_path + + select enterprise.name, from: "Enterprise" + url = "https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts" + fill_in "catalog_url", with: url + + click_button "Import" + + within ".flash" do + expect(page).to have_content "invalid_grant" + expect(page).to have_content "session not active" + expect(page).to have_link "OIDC Settings" + end + end + it "fails gracefully" do user.oidc_account.update!( uid: "anonymous@example.net",