opf/openproject

View on GitHub
app/controllers/concerns/accounts/authentication_stages.rb

Summary

Maintainability
A
0 mins
Test Coverage
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2024 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++

module Accounts::AuthenticationStages
  def successful_authentication(user, reset_stages: true, just_registered: false)
    stages = authentication_stages after_activation: just_registered, reset: reset_stages

    if stages.empty?
      # setting params back_url to be used by redirect_after_login
      params[:back_url] = session.delete :back_url if session.include?(:back_url)

      if just_registered || session[:just_registered]
        finish_registration! user
      else
        login_user! user
      end
    else
      stage = stages.first

      session[:just_registered] = just_registered
      session[:authenticated_user_id] = user.id

      redirect_to stage.path
    end
  end

  def stage_success
    stage = session[:authentication_stages]&.first

    if stage && stage.to_s == params[:stage]
      if params[:secret] == stage_secrets[stage]
        session[:authentication_stages] = session[:authentication_stages].drop(1)

        successful_authentication User.find(session[:authenticated_user_id]), reset_stages: false
      else
        flash[:error] = I18n.t(:notice_auth_stage_verification_error, stage:)

        redirect_to signin_path
      end
    else
      flash[:error] = I18n.t(
        :notice_auth_stage_wrong_stage,
        expected: stage || "(none)",
        actual: params[:stage]
      )

      redirect_to signin_path
    end
  end

  def stage_failure
    flash[:error] = flash[:error] || I18n.t(:notice_auth_stage_error, stage: params[:stage])

    redirect_to signin_path
  end

  private

  def finish_registration!(user)
    session[:just_registered] = nil
    self.logged_user = user
    user.update last_login_on: Time.now

    flash[:notice] = I18n.t(:notice_account_registered_and_logged_in)
    redirect_after_login user
  end

  def authentication_stages(after_activation: false, reset: true)
    if OpenProject::Authentication::Stage.stages.select(&:active?).any?
      session.delete %i[authentication_stages stage_secrets back_url] if reset

      if session.include?(:authentication_stages)
        lookup_authentication_stages
      else
        init_authentication_stages after_activation:
      end
    else
      []
    end
  end

  def lookup_authentication_stages
    OpenProject::Authentication::Stage.find_all session[:authentication_stages]
  end

  def init_authentication_stages(after_activation:)
    stages = active_stages after_activation

    session[:authentication_stages] = stages.map(&:identifier)
    session[:stage_secrets] = new_stage_secrets

    # Remember back_url from params since we're redirecting
    # but don't use the referer
    session[:back_url] ||= params[:back_url]

    # Remember the autologin cookie decision
    session[:autologin_requested] = params[:autologin] == "1"

    stages
  end

  def active_stages(after_activation)
    OpenProject::Authentication::Stage
      .stages
      .select(&:active?)
      .select { |s| s.run_after_activation? || !after_activation }
  end

  def stage_secrets
    Hash(session[:stage_secrets])
  end

  def new_stage_secrets
    session[:authentication_stages]
      .index_with { |ident| stage_secret(ident) }
  end

  def stage_secret(_ident)
    SecureRandom.hex(16)
  end
end