app/controllers/application_controller.rb

Summary

Maintainability
A
45 mins
Test Coverage
class ApplicationController < ActionController::API
  SYSTEM_TOKEN_HEADER = 'System-Token'.freeze

  include ActionController::HttpAuthentication::Basic::ControllerMethods

  rescue_from ActionController::TranslatedError do |error|
    render json: { type: 'error', error: error.message, localized_error: error.localized_message }, status: error.status, location: nil
  end

  protected

  def authenticate_system(skip_on_duplicated: false)
    authenticate_or_request_with_http_basic('RMT API') do |login, password|
      @systems = System.get_by_credentials(login, password)
      if @systems.present?
        # Return now if we just detected duplicates and we were told to skip on
        # this situation.
        return true if skip_on_duplicated && @systems.size > 1

        @system = find_system_by_token_header(@systems)

        # If SYSTEM_TOKEN_HEADER is present, RMT assumes the client uses a SUSEConnect version
        # that supports this feature. In this case, refresh the token and include it in the response.
        if system_tokens_enabled? && request.headers.key?(SYSTEM_TOKEN_HEADER)
          @system.update(last_seen_at: Time.zone.now, system_token: SecureRandom.uuid)
          headers[SYSTEM_TOKEN_HEADER] = @system.system_token
        # only update last_seen_at each 3 minutes,
        # so that a system that calls SCC every second doesn't write + lock the database row
        elsif !@system.last_seen_at || @system.last_seen_at < 3.minutes.ago
          @system.touch(:last_seen_at)
        end
        true
      else
        logger.info _('Could not find system with login \"%{login}\" and password \"%{password}\"') %
          { login: login, password: password }
        error = ActionController::TranslatedError.new(N_('Invalid system credentials'))
        error.status = :unauthorized
        raise error
      end
    end
  end

  private

  # Token mechanism to detect duplicated systems.
  # 1: system doesn't send a token header (old SUSEConnect version)
  # 2: system sends a token, and it matches an existing system with that token
  # 3: system sends an empty token, it matches an existing system without token
  #    -> it's a new system (because the 'announce_system' call doesn't set the token yet)
  #       or a system that upgraded to a SUSEConnect version that supports tokens
  # 4: system sends an empty token, but a system with credentials and token exists (duplicate)
  # 5: system sends a token, it matches an existing system but mismatch the token (duplicate)

  def find_system_by_token_header(systems)
    return nil if systems.blank?

    # 1st case
    unless request.headers.key?(SYSTEM_TOKEN_HEADER)
      logger.info _('System with login \"%{login}\" authenticated without token header') %
        { login: systems.first.login }
      return systems.first
    end

    # 2nd/3rd case
    system_token = request.headers[SYSTEM_TOKEN_HEADER].presence
    system = systems.find { |s| s.system_token == system_token }
    if system
      logger.info _('System with login \"%{login}\" authenticated with token \"%{system_token}\"') %
        { login: system.login, system_token: system_token }
      return system
    end

    # 4th/5th case
    system = systems.first
    system.dup.tap do |ns|
      ns.created_at = Time.zone.now
      ns.system_token = system_token
      ns.activations = system.activations.map(&:dup)
      ns.save!
      logger.info _('System with login \"%{login}\" (ID %{new_id}) authenticated and duplicated from ID %{base_id} due to token mismatch') %
        { login: ns.login, new_id: ns.id, base_id: system.id }
    end
  end

  def system_tokens_enabled?
    !!!Settings.try(:connect_api).try(:disable_system_tokens)
  end
end