使用防伪和环模拟测试POST路由

时间:2016-03-14 11:25:39

标签: testing clojure mocking csrf ring

我想使用ring.mock为简单的POST请求编写测试 - 类似这样的内容:

(testing "id post route"
    (let [response (app (mock/request :post "/" {:id "Foo"}))]
      (is (= 302 (:status response)))))

但是,由于我使用wrap-csrf中间件,因此我收到了403状态响应,因为我没有提供防伪令牌。

有没有办法用ring.mock编写POST测试而不禁用wrap-csrf中间件?

3 个答案:

答案 0 :(得分:4)

你想测试什么?从您的问题中不清楚,并且不清楚为什么不应该禁用防伪中间件。

  1. 如果您正在测试Web服务,则根本不应使用CSRF令牌并切换到不同的安全机制(例如授权标头,API令牌等)。

  2. 如果要测试包括CSRF逻辑在内的端到端流程,则需要先通过调用相应的URL并从响应中提取它(例如解析隐藏字段)来获取有效的CSRF令牌。会话ID,以便您可以在测试请求中使用它们。

  3. 如果你想测试你的handler逻辑,那么在没有包装“基础设施”中间件的情况下测试它。如果您不能在测试中将它应用于处理程序函数并且问题消失,那么模仿反伪造中间件就没有意义了。

答案 1 :(得分:4)

首先,我想通过另一篇文章回应关于方法和推理的一些观点:

  1. CSRF令牌仅对HTML登录是必需的,但不需要或 适用于网络服务。
  2. 使用Ring / Compojure,可以直接测试处理程序,或仅将其包装在必要的中间件中。在下面的示例中,我需要app代表整个应用程序,但您可以绕过CSRF以便在需要时直接测试端点。这将允许您测试给定的端点,但不会测试CSRF保护是否正常工作。
  3. 假设上述情况,您可能需要测试登录过程是否正常工作以及CSRF是否已集成。以下测试助手应该帮助您沿着这条道路前进:

    (ns myapp.test.handler-test
      (:require [clojure.test :refer :all]
                [ring.mock.request :refer [request]]
                [net.cgrand.enlive-html :as html]
                [myapp.handler :refer [app]]))
    
    (defn get-session
      "Given a response, grab out just the key=value of the ring session"
      [resp]
      (let [headers (:headers resp)
            cookies (get headers "Set-Cookie")
            session-cookies (first (filter #(.startsWith % "ring-session") cookies))
            session-pair (first (clojure.string/split session-cookies #";"))]
        session-pair))
    
    (defn get-csrf-field
      "Given an HTML response, parse the body for an anti-forgery input field"
      [resp]
      (-> (html/select (html/html-snippet (:body resp)) [:input#__anti-forgery-token])
          first
          (get-in [:attrs :value])))
    
    (defn get-login-session!
      "Fetch a login page and return the associated session and csrf token"
      []
      (let [resp (app (request :get "/login"))]
        {:session (get-session resp)
         :csrf (get-csrf-field resp)}))
    
    (defn login!
      "Login a user given a username and password"
      [username password]
      (let [{:keys [csrf session]} (get-login-session!)
            req (-> (request :post "/login")
                    (assoc :headers {"cookie" session})
                    (assoc :params {:username username
                                    :password password})
                    (assoc :form-params {"__anti-forgery-token" csrf}))]
        (app req)
        session))
    

    在上文中,我假设/login页面使用了__anti-forgery-token隐藏的输入,并且您想对此进行测试。您还可以考虑将CSRF令牌放在会话数据中,这样可以更轻松地使用curl等工具进行测试,这些工具可以将会话数据从一个响应保存到文件中,以便在连续请求中使用。

    由于我将令牌从HTML正文中拉出来,我决定使用enlive以便我可以使用CSS选择器来定义我从哪里直接提取这些数据和陈述性的方式。

    要使用上述内容,您可以调用login!,然后使用它在要通过同一会话进行身份验证的后续请求中返回的会话数据,例如:

     (deftest test-home-page-authentication
      (testing "authenticated response"
        (let [session (login! "bob" "bob")
              request (-> (request :get "/")
                          (assoc :headers {"cookie" session}))]
          (is (= 200 (:status (app request)))))))
    

    这假设您在所使用的任何身份验证后端中设置了bob和bob的用户名/密码。您可能需要设置用户名/密码设置步骤,以便在此登录过程生效之前创建此用户。

答案 2 :(得分:0)

我们可以在使用csrf的测试中禁用(assoc site-defaults :security false)。完整的代码如下:

; Create a copy of testing app in utilities.testing
; by wrapping handler with testing middlewares

(ns utilities.testing
    (:require [your-web-app.handler :refer [path-handler]]
              [ring.middleware.defaults :refer [wrap-defaults site-defaults]]))

; Disabling CSRF for testing
(def app
    (-> path-handler
        (wrap-defaults (assoc site-defaults :security false))))

现在您可以在测试中使用此应用了

(ns users.views-test
    (:require [utilities.testing :refer [app]]
              ;...
              ))

;...
(testing "id post route"
    (let [response (app (mock/request :post "/" {:id "Foo"}))]
      (is (= 302 (:status response)))))