rubygems/rubygems.org

View on GitHub
app/controllers/api/v1/api_keys_controller.rb

Summary

Maintainability
A
1 hr
Test Coverage
A
94%
class Api::V1::ApiKeysController < Api::BaseController
  include ApiKeyable

  def show
    authenticate_or_request_with_http_basic do |username, password|
      # strip username mainly to remove null bytes
      user = User.authenticate(username.strip, password)
      check_mfa(user) do
        key = generate_unique_rubygems_key
        api_key = user.api_keys.build(legacy_key_defaults.merge(hashed_key: hashed_key(key)))

        save_and_respond(api_key, key)
      end
    end
  end

  def create
    authenticate_or_request_with_http_basic do |username, password|
      user = User.authenticate(username, password)

      check_mfa(user) do
        key = generate_unique_rubygems_key
        build_params = { owner: user, hashed_key: hashed_key(key), **api_key_create_params }
        api_key = ApiKey.new(build_params)

        save_and_respond(api_key, key)
      end
    end
  end

  def update
    authenticate_or_request_with_http_basic do |username, password|
      user = User.authenticate(username, password)

      check_mfa(user) do
        api_key = user.api_keys.find_by!(hashed_key: hashed_key(key_param))

        if api_key.update(api_key_update_params)
          respond_with "Scopes for the API key #{api_key.name} updated"
        else
          errors = api_key.errors.full_messages
          respond_with "Failed to update scopes for the API key #{api_key.name}: #{errors}", status: :unprocessable_entity
        end
      end
    end
  end

  private

  def check_mfa(user)
    if user&.mfa_gem_signin_authorized?(otp)
      return render_mfa_setup_required_error if user.mfa_required_not_yet_enabled?
      return render_mfa_strong_level_required_error if user.mfa_required_weak_level_enabled?

      yield
    elsif user&.mfa_enabled?
      prompt_text = otp.present? ? t(:otp_incorrect) : t(:otp_missing)
      render plain: prompt_text, status: :unauthorized
    else
      false
    end
  end

  def save_and_respond(api_key, key)
    if api_key.errors.blank? && api_key.save
      Mailer.api_key_created(api_key.id).deliver_later
      respond_with key
    else
      respond_with api_key.errors.full_messages.to_sentence, status: :unprocessable_entity
    end
  end

  def respond_with(msg, status: :ok)
    respond_to do |format|
      format.any(:all) { render plain: msg, status: status }
      format.json { render json: { rubygems_api_key: msg, status: status } }
      format.yaml { render plain: { rubygems_api_key: msg, status: status }.to_yaml }
    end
  end

  def otp
    request.headers["HTTP_OTP"]
  end

  def key_param
    params.permit(:api_key).require(:api_key)
  end

  def api_key_create_params
    params.permit(:name, *ApiKey::API_SCOPES, :mfa, :rubygem_name)
  end

  def api_key_update_params
    params.permit(*ApiKey::API_SCOPES, :mfa)
  end
end