how add authentication in a middleware or pre-route? - clojure

how to carry out an authentication process for each request sent. The problem i'm currently facing is i'm not able access user data which is sent as a request param. here is what i have tried
(pre-route[:any "/mainpage/*"] {:keys[data]}
(when (not(contains? data "userid"))
//false response
)
)
and the middleware
(defn for-auth [handler]
(fn [req]
(if (contains? (:body (:params req)))
(handler req)
(handler (assoc req :body {})
)
)
)
and i add the middlware too. but neither of them work.. Any idea to access user params..
Thanks

Sandy
Are you using wrap params?
..
(:use [ring.middleware params])
(def app
(-> (handler/site main-routes)
(wrap-base-url)
(wrap-params)))

Ok here is what i have done and it solves my problem..
(pre-route [:any "/mainroute/*"] {{:keys [jsondata]}:params}
(let [data (httputil/parse-json-data jsondata)]
(cond
(not (true? valid_req)) "false"
(not (true? version_check)) "false"
(not (true? user_valid)) "false"
)
)
)
here i have parsed my json data in middleware and added it to "params" with key name :jsondata..
it works perfectly..

Related

How do I make middleware to put a response header only when `:swagger {:deprecated true}`?

We use the compojure-api to get us some nice swagger integration in our ring apps. The :swagger {:deprecated true} meta works like a champ to get the swagger page correct, but I have a requirement that I put a specific header on the response when the route is :swagger {:deprecated true}. I am struggling to figure out how to do this with the middleware pattern that I've been using to do similar response header manipulations.
(ns bob.routes
(:require [clojure.tools.logging :as log]
[compojure.api.sweet :refer :all]
[ring.util.http-response :as status]
[schema.core :as s]
[ring.swagger.schema :as rs]))
(s/defschema BobResponse {:message (rs/describe String "Message")})
(defn wrap-bob-response-header [handler]
(fn [request]
(let [response (handler request)]
;; can I reach into the request or the response to see what
;; route served this and if it has the :swagger {:deprecated true}
;; meta on it and NOT emit the x-bob header if it does?
(assoc-in response [:headers "x-bob"] "Robert"))))
(defroutes bob-routes
(context "" []
:middleware [wrap-bob-response-header]
:tags ["bob"]
:description ["Tease out how to do swagger driven response header"]
(GET "/notdeprectated" [:as request]
:swagger {:deprecated false}
:new-relic-name "GET_notdeprecated"
:return BobResponse
(status/ok {:message "All is well"}))
(GET "/isdeprecated" [:as request]
:swagger {:deprecated true}
:new-relic-name "GET_isdeprecated"
:return BobResponse
(status/ok {:message "You came to the wrong neighborhood."}))))
How do I modify wrap-bob-response-header to only emit x-bob on routes with :swagger {:deprecated true}?
With Compojure-API, the middleware are invoked in-place, at the path context they are defined at. In your example, the wrap-bob-response-header doesn't yet know where the request is going to go (or will it even match anything). If it knew, you could use the injected route information from the request (see https://github.com/metosin/compojure-api/blob/master/src/compojure/api/api.clj#L71-L73) to determine if the endpoints would have the swagger information set.
What you could do, is mount the header-setting middleware only to the routes that need it.
There is a library called reitit (also by Metosin) which solves this by applying a route-first architecture: the full path lookup is done first and the middleware chain is applied after that. Because of this, all the middleware know the endpoint they are mounted to. Middleware can just query the endpoint data (either at request-time or at compile-time) and act accordingly. They can even decide not to mount to that spesific route.
Reitit is feature-par with compojure-api, just with different syntax, e.g. fully data-driven.
Good examples in the blog: https://www.metosin.fi/blog/reitit-ring/
PS. I'm co-author of both of the libs.
EDIT.
Solution to inject data to the response after a match:
1) create middleware that adds data (or meta-data) to the response
2) add or modify a restructuring handler to mount the middleware from 1 into the endpoint, with the given data (available in the handler)
3) read the data in the response pipeline and act accordingly
(defn wrap-add-response-data [handler data]
(let [with-data #(assoc % ::data data)]
(fn
([request]
(with-data (handler request)))
([request respond raise]
(handler #(respond (with-data %)) raise)))))
(defmethod compojure.api.meta/restructure-param :swagger [_ swagger acc]
(-> acc
(assoc-in [:info :public :swagger] swagger)
(update-in [:middleware] into `[[wrap-add-response-data ~swagger]])))
(def app
(api
(context "/api" []
(GET "/:kikka" []
:swagger {:deprecated? true}
(ok "jeah")))))
(app {:request-method :get, :uri "/api/kukka"})
; {:status 200, :headers {}, :body "jeah", ::data {:deprecated? true}}

Why do I get no username/password dialog when using buddy-auth?

I'm using buddy-auth and following the tutorial, when I run the example application I get a HTTP auth username/password dialog as expected, but in my own app I just get the "Unauthorized" exception, no dialog appears.
;; fn needs to return a non-falsey value to indicate a positive authentication, the returned value is stored under the `:identity` key in the request.
(defn auth-user [request authdata]
(let [username (:username authdata)
password (:password authdata)]
username)) ;; FIXME: lookup username/password
(def auth-backend (http-basic-backend {:realm "MyApp" :authfn auth-user}))
;; my endpoint handler
(defn test-handler [r]
(if (authenticated? r)
(render (str "LOGGED IN" (:identity r)))
(throw-unauthorized)))
;; ROUTES (compojure)
(defroutes app
(GET "/test" [] test-handler))
;; ring handler
(def site
(-> (routes app)
(wrap-authentication auth-backend) ;; <---
(wrap-defaults)
(wrap-with-exception-handling)))
I don't think auth-user is ever called.
It seems I need (wrap-authorization auth-backend) too.
(def site
(-> (routes app)
(wrap-authentication auth-backend)
(wrap-authorization auth-backend)
(wrap-defaults)
(wrap-with-exception-handling)))

Getting the http request when using compojure.route/not-found in Clojure

I'm building a server using Cloujre's Compojure. The default route is compojure.route/not-found, is there a way of getting the request that reached this route? I'd like print all requests that end up there.
You can use this kinda approach:
(def handler (-> your-routes
wrap-my-request-middleware ;; it has to be in this order
...))
Let's log in here the uri
(defn wrap-my-request-middleware
[handler]
(fn [request]
(let [response (handler request)]
(when (= 404 (:status response))
;; do whatever you like in here
(log/info (str "Request path: " (:uri request))))
response)));; fn needs to return reponse...

How to mock test POST requests with body as JSON using ring mock request?

I am using http-kit as the server with wrap-json-body from ring.middleware.json to get the stringified JSON content sent from the client as the request body. My core.clj is:
; core.clj
; ..
(defroutes app-routes
(POST "/sign" {body :body} (sign body)))
(def app (site #'app-routes))
(defn -main []
(-> app
(wrap-reload)
(wrap-json-body {:keywords? true :bigdecimals? true})
(run-server {:port 8080}))
(println "Server started."))
When I run the server using lein run the method works correctly. I am stringifying the JSON and sending it from the client. The sign method gets the json correctly as {"abc": 1}.
The problem is when during mock test. The sign method gets a ByteArrayInputStream and I am using json/generate-string to convert to string which fails in this case. I tried wrapping the handler in wrap-json-body but it is not work. Here are my test cases I tried out core_test.clj:
; core_test.clj
; ..
(deftest create-sign-test
(testing "POST sign"
(let [response
(wrap-json-body (core/app (mock/request :post "/sign" "{\"username\": \"jane\"}"))
{:keywords? true :bigdecimals? true})]
(is (= (:status response) 200))
(println response))))
(deftest create-sign-test1
(testing "POST sign1"
(let [response (core/app (mock/request :post "/sign" "{\"username\": \"jane\"}"))]
(is (= (:status response) 200))
(println response))))
(deftest create-sign-test2
(testing "POST sign2"
(let [response (core/app (-> (mock/body (mock/request :post "/sign")
(json/generate-string {:user 1}))
(mock/content-type "application/json")))]
(is (= (:status response) 200))
(println response))))
(deftest create-sign-test3
(testing "POST sign3"
(let [response
(wrap-json-body (core/app (mock/request :post "/sign" {:headers {"content-type" "application/json"}
:body "{\"foo\": \"bar\"}"}))
{:keywords? true :bigdecimals? true})]
(is (= (:status response) 200))
(println response))))
All of the fails with the following error:
Uncaught exception, not in assertion.
expected: nil
actual: com.fasterxml.jackson.core.JsonGenerationException: Cannot JSON encode object of class: class java.io.ByteArrayInputStream: java.io.ByteArrayInputStream#4db77402
How can I pass a JSON string as the body to the method in ring mock test?
There are three issues in your code.
Your test doesn't wrap your app handler in wrap-json-body so it might not get correctly parsed request body in your handler. You need to first wrap your app in wrap-json-body and then call it with your mock request. (You could also have your app handler to be already wrapped instead of wrapping it both in your main function and tests)
(let [handler (-> app (wrap-json-body {:keywords? true :bigdecimals? true})]
(handler your-mock-request))
Your mock request doesn't include proper content type and your wrap-json-body won't parse your request body to JSON. That's why your sign function gets ByteArrayInputStream instead of parsed JSON. You need to add content type to your mock request:
(let [request (-> (mock/request :post "/sign" "{\"username\": \"jane\"}")
(mock/content-type "application/json"))]
(handler request))
Verify that your sign function returns a response map with JSON as string in body. If it creates response body as input stream you need to parse it in your test function. Below I am using cheshire to parse it (converting JSON keys to keywords):
(cheshire.core/parse-stream (-> response :body clojure.java.io/reader) keyword)
Additionally instead of writing your JSON request body by hand you can use Cheshire to encode your data into JSON string:
(let [json-body (cheshire.core/generate-string {:username "jane"})]
...)
With those changes it should work correctly like in my slightly modified example:
(defroutes app-routes
(POST "/echo" {body :body}
{:status 200 :body body}))
(def app (site #'app-routes))
(let [handler (-> app (wrap-json-body {:keywords? true :bigdecimals? true}))
json-body (json/generate-string {:username "jane"})
request (-> (mock/request :post "/echo" json-body)
(mock/content-type "application/json"))
response (handler request)]
(is (= (:status response) 200))
(is (= (:body response) {:username "jane"})))

Why does my Ring middleware not see the :params map in the request?

I'm writing a Ring middleware, and also using Compojure. I want my middleware to look in the :params map to see if a specific key was provided by the user. In my middleware function, the request map does not contain a :params map, though. In the final request handler, there is a :params map. I'm thinking the :params map it not being set before my custom middleware, but I can't figure out how to get it to actually be set.
Any ideas?
(ns localshop.handler
(:use [ring.middleware.format-response :only [wrap-restful-response]]
[compojure.core])
(:require [localshop.routes.api.items :as routes-api-items]
[localshop.middleware.authorization :as authorization]
[compojure.handler :as handler]))
;; map the route handlers
(defroutes app-routes
(context "/api/item" [] routes-api-items/routes))
;; define the ring application
(def app
(-> (handler/api app-routes)
(authorization/require-access-token)
(wrap-restful-response)))
Above is my handler.clj file, and below is the middleware itself.
(ns localshop.middleware.authorization)
(defn require-access-token [handler]
(fn [request]
(if (get-in request [:params :token])
(handler request)
{:status 403 :body "No access token provided"})))
I actually figured this out. This works if you adjust the (def app ... ) portion of the code so that it matches the following:
(def app
(-> app-routes
(wrap-restful-response)
(authorization/require-access-token)
(handler/api)))