【问题标题】:Dynamic json templates that interpolate ruby variables and objects插入 ruby​​ 变量和对象的动态 json 模板
【发布时间】:2021-04-15 17:39:16
【问题描述】:

我们正在我们的应用中构建传出 webhook 功能。我们希望添加 webhook 的用户能够自定义发送的 json 有效负载。为此,我们需要一种允许将变量插入到最终有效负载中的语法。

示例: 用户管理的模板:

{:id=>"%{id}", :message=>"%{message}", :badge=>"%{badge}"}

希望的输出:

{
  "id": "f1ih5g3",
  "message": "Thanks for your help",
  "badge": {
    "id": "M7nk8ojK",
    "name": "Thanks Badge",
    "points": 10,
  }
}

因此,在上面的示例中,有一个主 webhook 对象,idmessage 的基本变量被插值并作为字符串返回。但是,当模板中包含对完整对象的引用时,我们还希望能够支持嵌套结构。当包含对完整对象的引用时,它应该返回该对象的 json 表示。

我们已经尝试使用 String#% 运算符,它适用于基本变量,但对于嵌套对象,它会产生如下的字符串化版本:

{
  "id": "f1ih5g3",
  "message": "Thanks for your help",
  "badge": "{:id=>\"M7nk8ojK\", :name=>\"Thanks Badge\", :points=>10}"
}

我探索过 RABL 和 Jsonnet,它们似乎并不真正支持动态模板(即由用户管理的模板)。

ERB 语法可以工作,但似乎不安全,因为模板中可能包含任意 ruby​​。

【问题讨论】:

    标签: ruby-on-rails json ruby


    【解决方案1】:
    require 'json'
    template = {:my_id=>"%{id}", :my_message=>"%{message}", :my_badge=>"%{badge}"}
    data = {
      id: 'f1ih5g3',
      badge: { id: "M7nk8ojK", name: "Thanks Badge", points: 10 }
    }
    output = template.each_with_object({}) { |(k, v), a|
      placeholder = /\A%\{(.*)\}\z/.match(v)&.captures.first
      a[k] = placeholder.nil? ? v : data[placeholder.to_sym]
    }.to_json
    JSON.parse(output)
    # {"my_id"=>"f1ih5g3", "my_message"=>nil, "my_badge"=>{"id"=>"M7nk8ojK", "name"=>"Thanks Badge", "points"=>10}}
    

    关于 XSS、无效模板或默认值(例如上面的 message)的问题留给读者作为练习。

    也未解决:根据用户输入编译模板。除非严格控制词汇上下文,否则使用 eval(如下)的解决方案可能是 RCE 漏洞。

    customer_input = %q|{:my_id=>"%{id}", :my_badge=>"%{badge}"}|
    template = eval customer_input
    

    可能需要考虑替代的、更安全的customer_input 格式。

    【讨论】:

    • 感谢提交。我们确实在寻找一个“安全”的解决方案,因为我们正在处理客户的意见,eval 确实不是你提到的票。
    【解决方案2】:

    如果是我,我会选择这样的解决方案。我会收集用户可以在一个类下使用的变量并编写以下代码。也许我误解了代码,因为我无法完全预测问题。

    require 'json'
    require 'pry'
    require 'securerandom'
    
    class CustomerArea
      ALLOWED_METHODS = %i[id message badge]
    
      def self.id
        SecureRandom.uuid
      end
    
      def self.message
        "Lorem ipsum dolar sit amet"
      end
    
      def self.badge
        {
          id: SecureRandom.uuid,
          name: "Foo",
          points: 5
        }
      end
    end
    
    
    class Processor
      def process(input)
        json_object = JSON.parse(input, symbolize_names: true)
        result      = {}
        
        json_object.each do |key, value|
          extracts = value.match /\A%{insert_(?<field>\w+)}\z/
          if extracts.nil?
            result[key] = value
            next
          end
    
          field           = extracts[:field].to_sym
          allowed_methods = CustomerArea.const_get(:ALLOWED_METHODS)
          next unless allowed_methods.include? field
    
          result[key] = CustomerArea.send(field)
        end
    
        result.to_json
      end
    end
    
    customer_input = '{ "id": "%{insert_id}", "message": "%{insert_message}", "badge": "%{insert_badge}"}'
    processor = Processor.new
    puts processor.process(customer_input)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-08-30
      • 1970-01-01
      • 2013-07-10
      • 1970-01-01
      • 1970-01-01
      • 2012-04-26
      • 1970-01-01
      • 2015-06-18
      相关资源
      最近更新 更多