Безопасный и динамический вызов методов на основе пользовательского ввода в Rails - PullRequest
2 голосов
/ 20 марта 2012

У меня есть API, который я встроил в Rails. Он запускает некоторые методы, которые я определил в модуле, и отображает их возвращаемые значения как JSON. В то время как я разрабатывал, весь код для API был сам модуль (содержание не имеет значения), один маршрут:

  controller :cool do
    get "cool/query/*args" => :query
  end

и это:

class CoolController < ApplicationController
  include CoolModule
  def query
    args = params[:args].split("/")

    # convert the API URL to the method name
    method_symbol = args[0].tr("-","_").to_sym

    if !CoolModule.method_defined?(method_symbol)
      return nil
    end

    # is calling self.method a good idea here, or is there a better way?
    render json: self.method(method_symbol).call(args[1], args[2])

  end
end

Мой API (т.е. модуль) содержит ~ 30 функций, каждая из которых принимает переменное количество аргументов, логику маршрутизации которой я хотел бы сохранить в модуле (как сейчас).

Он будет использоваться как "средний конец" (можно сказать) между моим классным интерфейсом ajax и другим API, который я не контролирую и действительно является собственно бэк-эндом. Поэтому необходимо уделить особое внимание, так как он получает и пользовательский ввод, и отправляет запросы третьей стороне (за которую я несу ответственность).

Мои вопросы конкретно:

  1. Будет ли эта общая стратегия (имена методов непосредственно из запросов) безопасна / стабильна для производства?
  2. Если стратегия приемлема, а моя реализация - нет, какие изменения необходимы?
  3. Если стратегия в корне неверна, какие альтернативы мне следует использовать?

Пессимист во мне говорит: «мили case - when», но я буду благодарен вам за ваш вклад.

1 Ответ

1 голос
/ 20 марта 2012

Проблема с Module # method_defined? в том, что он может возвращать true для косвенных определений методов (других включенных модулей, унаследованных методов, если module является классом), а также для закрытых методов. Это означает, что вы (и, что важно, любой, кто касается кода), должен быть очень осторожным в том, что вы делаете с этим модулем.

Таким образом, вы могли бы использовать этот подход, но вам необходимо четко указать будущим сопровождающим, что любой метод в модуле автоматически является внешним интерфейсом. Лично я бы выбрал что-то более явное, например, простой белый список разрешенных имен методов API, например:

require 'set'

module CoolModule
  ALLOWED_API_METHODS = Set[
    :foo,
    :bar,
    ...
  ]

  def self.api_allowed? meth
    ALLOWED_API_METHODS.include? meth.to_sym
  end
end

Да, вы должны вести список, но это не так неприглядно, это документация с явным интерфейсом; и означает, что вы не получите ничего от более позднего кодера, решившего, что ему нужно добавить некоторые служебные методы в модуль для удобства, и, таким образом, случайно экспортировать их в ваш внешний API.

В качестве альтернативы единственному списку вы можете использовать метод define_for_api и использовать его вместо def для объявления методов интерфейса API

module CoolModule
  @registered_api_methods = Set.new
  def self.define_for_api meth, &block
    define method meth, &block
    @registered_api_methods << meth
  end

  def self.api_allowed? meth
    @registered_api_methods.include? meth.to_sym
  end

  def api_dispatch meth, *args
    raise ArgumentError unless self.class.api_allowed? meth
    send(meth *args)
  end

  define_for_api :foo do |*args|
    do_something_common
    ...
  end

  define_for_api :bar do
    do_something_common
    ...
  end

  # this one is just ordinary method internal to module
  private
  def do_something_common
  end
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...