18F/identity-dashboard

View on GitHub
app/controllers/service_config_wizard_controller.rb

Summary

Maintainability
A
45 mins
Test Coverage
class ServiceConfigWizardController < AuthenticatedController
  include ::Wicked::Wizard
  STEPS = WizardStep::STEPS
  steps(*STEPS)
  UPLOAD_STEP = 'logo_and_cert'
  attr_reader :wizard_step_model

  before_action :redirect_unless_flagged_in
  before_action -> { authorize step, policy_class: ServiceConfigPolicy }
  before_action :get_model_for_step, except: :new
  after_action :verify_authorized
  # We get false positives from `verify_policy_scoped` if we never instantiate a model
  after_action :verify_policy_scoped, unless: -> { when_skipping_models }
  helper_method %i[
    issuer_read_only?
    draft_service_provider
    show_saml_options?
    show_oidc_options?
    show_proof_failure_url?
  ]

  def new
    redirect_to service_config_wizard_path(Wicked::FIRST_STEP)
  end

  def show
    render_wizard
  end

  def update
    return destroy if can_cancel?
    if step == UPLOAD_STEP
      attach_cert
      remove_certificates
      attach_logo_file if logo_file_param
    end
    unless skippable && params[:wizard_step].blank?
      @model.data = @model.data.merge(wizard_step_params)
    end
    skip_step if @model.valid? && @model.save
    render_wizard
  end

  def destroy
    saved_steps = policy_scope(WizardStep).where(user: current_user)
    authorize saved_steps.last, :destroy?
    saved_steps.destroy_all
    redirect_to finish_wizard_path
  end

  def issuer_read_only?
    false # This will have to be updated when we add the ability to edit existing service providers
  end

  def draft_service_provider
    @service_provider ||= begin
      all_wizard_data = WizardStep.current_step_data_for_user(current_user)
      ServiceProvider.new(**transform_to_service_provider_attributes(all_wizard_data))
    end
  end

  def show_saml_options?
    auth_step && auth_step.identity_protocol == 'saml'
  end

  def show_oidc_options?
    !show_saml_options?
  end

  def show_proof_failure_url?
    auth_step && auth_step.ial.to_i > 1
  end

  private

  def get_model_for_step
    # The FINISH_STEP has no data. It's mostly a redirect. It doesn't need a model
    return if step == Wicked::FINISH_STEP
    @model = policy_scope(WizardStep).find_or_initialize_by(step_name: step)
  end

  def auth_step
    # Should this be `@model.auth_step` ?
    @auth_step ||= policy_scope(WizardStep).find_by(user: current_user, step_name: 'authentication')
  end

  def redirect_unless_flagged_in
    redirect_to service_providers_path unless IdentityConfig.store.service_config_wizard_enabled
  end

  def finish_wizard_path
    service_providers_path
  end

  def wizard_step_params
    permit_params = [
      :acs_url,
      :active,
      :agency_id,
      :allow_prompt_login,
      :approved,
      :assertion_consumer_logout_service_url,
      :block_encryption,
      :description,
      :friendly_name,
      :group_id,
      :ial,
      :default_aal,
      :identity_protocol,
      :issuer,
      :logo,
      :metadata_url,
      :return_to_sp_url,
      :failure_to_proof_url,
      :push_notification_url,
      :signed_response_message_requested,
      :sp_initiated_login_url,
      :logo_file,
      :app_name,
      :prod_config,
      :redirect_uris,
      attribute_bundle: [],
      help_text: {},
    ]
    permit_params << :production_issuer if current_user.admin?
    # TODO: resync this with changes in https://gitlab.login.gov/lg/identity-dashboard/-/merge_requests/69
    permit_params << :email_nameid_format_allowed if current_user.admin?
    params.require(:wizard_step).permit(*permit_params)
  end

  # relies on ServiceProvider#certs_are_pems for validation
  def attach_cert
    return if params.dig(:wizard_step, :cert).blank?

    crt = params[:wizard_step].delete(:cert).read
    @model.certs << crt unless crt.blank?
  end

  def remove_certificates
    return if params.dig(:wizard_step, :remove_certificates).blank?

    to_remove_serials = params[:wizard_step].delete(:remove_certificates)

    to_remove_serials.each do |serial|
      @model.remove_certificate(serial)
    end
  end

  def logo_file_param
    params[:wizard_step]&.fetch(:logo_file, nil)
  end

  def attach_logo_file
    @model.attach_logo(logo_file_param)
  end

  def skippable
    step == UPLOAD_STEP
  end

  def can_cancel?
    params[:commit].present? &&
      params[:commit].downcase == 'cancel' &&
      IdentityConfig.store.service_config_wizard_enabled &&
      step == STEPS.last &&
      current_user.admin?
  end

  def when_skipping_models
    action_name == 'new' ||
      step == STEPS.first ||
      step == Wicked::FINISH_STEP
  end

  def transform_to_service_provider_attributes(wizard_step_data)
    if wizard_step_data.has_key?('redirect_uris')
      wizard_step_data['redirect_uris'] = Array(wizard_step_data['redirect_uris'])
    end

    # This won't be enough to actually transfer the file to the new record
    # TODO: we'll have to add some code to do that file attach transfer
    if wizard_step_data.has_key?('logo_name')
      wizard_step_data['logo'] = wizard_step_data.delete('logo_name')
    end

    # Clear out extra data from the wizard steps in case we put data
    # temporarily in the wizard steps that the service provider doesn't have attributes for
    (wizard_step_data.keys - ServiceProvider.new.attributes.keys).each do |extra_data|
      wizard_step_data.delete[extra_data]
    end

    wizard_step_data
  end
end