diff --git a/app/controllers/api/attempts/configuration_controller.rb b/app/controllers/api/attempts/configuration_controller.rb new file mode 100644 index 00000000000..101f787ecc2 --- /dev/null +++ b/app/controllers/api/attempts/configuration_controller.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Api + module Attempts + class ConfigurationController < ApplicationController + include RenderConditionConcern + prepend_before_action :skip_session_load + prepend_before_action :skip_session_expiration + + check_or_render_not_found -> { IdentityConfig.store.attempts_api_enabled } + + def index + render json: AttemptsConfigurationPresenter.new.configuration + end + end + end +end diff --git a/app/controllers/api/attempts/events_controller.rb b/app/controllers/api/attempts/events_controller.rb new file mode 100644 index 00000000000..77b81e8fb89 --- /dev/null +++ b/app/controllers/api/attempts/events_controller.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Api + module Attempts + class EventsController < ApplicationController + include RenderConditionConcern + check_or_render_not_found -> { IdentityConfig.store.attempts_api_enabled } + + prepend_before_action :skip_session_load + prepend_before_action :skip_session_expiration + + def poll + head :method_not_allowed + end + + def status + render json: { + status: :disabled, + reason: :not_yet_implemented, + } + end + end + end +end diff --git a/app/presenters/attempts_configuration_presenter.rb b/app/presenters/attempts_configuration_presenter.rb new file mode 100644 index 00000000000..3360da685d1 --- /dev/null +++ b/app/presenters/attempts_configuration_presenter.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +# https://openid.net/specs/openid-sharedsignals-framework-1_0-ID3.html#name-transmitter-configuration-m +class AttemptsConfigurationPresenter + include Rails.application.routes.url_helpers + + DELIVERY_METHOD_POLL = 'https://schemas.openid.net/secevent/risc/delivery-method/poll' + + def configuration + { + issuer: root_url, + jwks_uri: api_openid_connect_certs_url, + delivery_methods_supported: [ + DELIVERY_METHOD_POLL, + ], + delivery: [ + { + delivery_method: DELIVERY_METHOD_POLL, + url: api_attempts_poll_url, + }, + ], + status_endpoint: api_attempts_status_url, + } + end + + def url_options + {} + end +end diff --git a/config/routes.rb b/config/routes.rb index 5114f550a78..4201dae14e7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -19,6 +19,11 @@ post '/api/webhooks/socure/event' => 'socure_webhook#create' namespace :api do + namespace :attempts do + post '/poll' => 'events#poll', as: :poll + get '/status' => 'events#status', as: :status + end + namespace :internal do get '/sessions' => 'sessions#show' put '/sessions' => 'sessions#update' @@ -34,6 +39,9 @@ end end + # Attempts API + get '/.well-known/ssf-configuration' => 'api/attempts/configuration#index' + # SAML secret rotation paths constraints(path_year: SamlEndpoint.suffixes) do get '/api/saml/metadata(:path_year)' => 'saml_idp#metadata', format: false diff --git a/spec/controllers/api/attempts/events_controller_spec.rb b/spec/controllers/api/attempts/events_controller_spec.rb new file mode 100644 index 00000000000..32d1a728964 --- /dev/null +++ b/spec/controllers/api/attempts/events_controller_spec.rb @@ -0,0 +1,50 @@ +require 'rails_helper' + +RSpec.describe Api::Attempts::EventsController do + include Rails.application.routes.url_helpers + let(:enabled) { false } + + before do + allow(IdentityConfig.store).to receive(:attempts_api_enabled).and_return(enabled) + end + + describe '#poll' do + let(:action) { post :poll } + + context 'when the Attempts API is not enabled' do + it 'returns 404 not found' do + expect(action.status).to eq(404) + end + end + + context 'when the Attempts API is enabled' do + let(:enabled) { true } + it 'returns 405 method not allowed' do + expect(action.status).to eq(405) + end + end + end + + describe 'status' do + let(:action) { get :status } + + context 'when the Attempts API is not enabled' do + it 'returns 404 not found' do + expect(action.status).to eq(404) + end + end + + context 'when the Attempts API is enabled' do + let(:enabled) { true } + it 'returns a 200' do + expect(action.status).to eq(200) + end + + it 'returns the disabled status and reason' do + body = JSON.parse(action.body, symbolize_names: true) + expect(body[:status]).to eq('disabled') + expect(body[:reason]).to eq('not_yet_implemented') + end + end + end +end diff --git a/spec/presenters/attempts_configuration_presenter_spec.rb b/spec/presenters/attempts_configuration_presenter_spec.rb new file mode 100644 index 00000000000..a0a2d252b8e --- /dev/null +++ b/spec/presenters/attempts_configuration_presenter_spec.rb @@ -0,0 +1,29 @@ +require 'rails_helper' + +RSpec.describe AttemptsConfigurationPresenter do + include Rails.application.routes.url_helpers + + subject { AttemptsConfigurationPresenter.new } + + describe '#configuration' do + let(:configuration) { subject.configuration } + + it 'includes information about the RISC integration' do + aggregate_failures do + expect(configuration[:issuer]).to eq(root_url) + expect(configuration[:jwks_uri]).to eq(api_openid_connect_certs_url) + expect(configuration[:delivery_methods_supported]) + .to eq([AttemptsConfigurationPresenter::DELIVERY_METHOD_POLL]) + + expect(configuration[:delivery]).to eq( + [ + delivery_method: AttemptsConfigurationPresenter::DELIVERY_METHOD_POLL, + url: api_attempts_poll_url, + ], + ) + + expect(configuration[:status_endpoint]).to eq(api_attempts_status_url) + end + end + end +end