Compojure: how to map query parameters - clojure

I'm trying to make any of the following mappings work to map http://mysite.org/add?http://sitetoadd.com or http://mysite.org/add?u=http://sitetoadd.com
(GET "/add?:url" [url] url)
(GET "/add?u=:url" [url] url)
(GET "/add" {params :params} (params :u))
(GET "/add" {params :params} (params "u"))
(GET "/add" [u] u)
But it just fails and I don't know why. On the other hand, this works:
(GET "/add/:url" [url] url)
but I can't use it because I have to pass a url and http://mysite.org/add/http://sitetoadd.com is invalid while http://mysite.org/add?http://sitetoadd.com is ok.
EDIT: dumping request i've seen that params is empty. I thought that it would contain POST and GET parameters, but the only place where I can find the params I pass is in :query-string ("u=asd"). It seems that a middleware is needed to parse query strings. My question stands still, by the way.

See https://github.com/weavejester/compojure under Breaking Changes. The params map is no longer bound by default. If you have your example routes inside a "(defroutes main-routes ... )", make sure you activate it through "(handler/site main-routes)" as explained on https://github.com/weavejester/compojure/wiki/Getting-Started as it is the site or api method that makes sure the params map gets bound by default.
Here's an example that works:
(ns hello-world
(:use compojure.core, ring.adapter.jetty)
(:require [compojure.route :as route]
[compojure.handler :as handler]))
(defroutes main-routes
(GET "/" {params :params} (str "<h1>Hello World</h1>" (pr-str params)))
(route/not-found "<h1>Page not found</h1>"))
(defn -main [& m]
(run-jetty (handler/site main-routes) {:port 8080}))

Related

ring anti-forgery token is unbound in rendered html

I need a CSRF token in order for my websockets client to connect to my server. In order to get the token to my client, I embed the token from ring.middleware.anti-forgery/*anti-forgery-token* in a <meta> tag, like so:
(defn head []
[:head
...
(let [csrf-token (force ring.middleware.anti-forgery/*anti-forgery-token*)]
[:meta {:name "csrf-token"
:content csrf-token}])
...])
However, instead of getting an actual token back (i.e. a 60-character-long random base64 string) in the rendered tag, it renders as
<meta content="Unbound: #'ring.middleware.anti-forgery/*anti-forgery-token*" name="csrf-token">
I also have ring-default's site-defaults wrapped around the routes as middleware, which includes ring.middleware.anti-forgery.
Here is the relevant file, handler.clj:
(ns spotify-client.handler
(:require
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]
[hiccup.page :refer [include-js html5]]
[ring.middleware.anti-forgery]
[compojure.core :refer :all]))
(defn head []
[:head
...
(let [csrf-token (force ring.middleware.anti-forgery/*anti-forgery-token*)]
[:meta {:name "csrf-token"
:content csrf-token}])
...])
(defn loading-page []
(html5
(head)
[:body {:class "body-container"}
[:div#app]
(include-js "/js/app.js")
[:script "spotify_client.core.init_BANG_()"]]))
(defn index-handler [_]
{:status 200
:headers {"Content-Type" "text/html"}
:body (loading-page)
(defroutes main-routes
(GET "/" [] index-handler)
...
)
(def app (wrap-defaults main-routes site-defaults))
I'm also running all of this using lein run.
Now, according to this link, the anti-forgery token var would become bound in the context of whatever the current request was, and the ring-anti-forgery middleware is what binds it. When my project was brand new and I had just added wrap-defaults (on the initial ten or so calls to start/reload the server), the token var was bound and rendered correctly. However, after no signficant changes, the token var will not bind and it renders as the "Unbound" string. Once the ring-anti-forgery middleware stopped being called(?) and stopped binding *anti-forgery-token* to an actual value, the var was never bound again. I've tried making a new project a couple of times already and the token var stops being bound after 5-10 server reloads. What could cause the ring-anti-forgery middleware within ring-defaults to stop working even though wrap-defaults is in my code?
According to the docs, the *anti-forgery-token* is a dynamic variable that is only defined within the context of a wrap-anti-forgery middleware call.
Add [ring.middleware.anti-forgery :refer [wrap-anti-forgery]] to your require form, and then add wrap-anti-forgery to your app declaration:
(ns spotify-client.handler
(:require
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]
[hiccup.page :refer [include-js html5]]
[ring.middleware.anti-forgery :refer [wrap-anti-forgery]]
[compojure.core :refer :all]))
...
(def app
(-> main-routes
(wrap-anti-forgery)
(wrap-defaults site-defaults))

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}}

Clojure friend causes redirect loop with basic auth

I am using ring with compojure and friend to realize basic password authentication in a toy app. Now I was trying to implement an example and my ring server causes a redirect loop in every browser.
This is my code:
(ns kassenclo.handlers
(:use [ring.util.response]
[ring.middleware.resource]
[ring.middleware.params])
(:require [compojure.core :refer :all]
[compojure.route :as route]
[ring.middleware.session :refer [wrap-session]]
[ring.middleware.keyword-params :refer [wrap-keyword-params]]
[kassenclo.views :as views]
[cemerick.friend :as friend]
[cemerick.friend.workflows :refer [make-auth]]))
(defroutes app
(GET "/" [] views/tally-view)
(GET "/inventory" [] (friend/authorize #{::user} views/inventory-view))
(route/not-found (html [:h1 "This page could not be found, please go back."])))
(defn auth-workflow [request]
(let [speak (get-in request [:params :speak])
credential-fn (get-in request [::friend/auth-config :credential-fn])]
(make-auth (credential-fn speak))))
(defn auth-credential [password]
(if (= password "pass")
{:identity password :roles #{::user}}))
(def handler
(-> app
(friend/authenticate {:workflows [auth-workflow]
:credential-fn auth-credential})
(wrap-keyword-params)
(wrap-params)
(wrap-session)
(wrap-resource "public")))
Simple debugging shows me that the server is alternating between auth-workflow and auth-credential a few times before it is stopped. Can anybody point out to me what I am missing?
// Edit:
The curious thing is that this redirect-loop happens on every route, even on / where friend is not used in the defroutes command.
I found out that the make-auth function, which wraps the authentication-map so it has the correct form has to be applied on the return value of the auth-credential before returning it. If it happens afterwards like in my original post friend rejects it and we get a authentication loop.

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)))

Compojure route params empty

My Compojure web app ([compojure "1.0.1"]) always receives an empty parameter map, despite adding wrap-params etc. Code sample below:
(defroutes public-routes
(PUT "/something" {params :params}
(println (str "Params: " params))
(do-put-something params)))
(def myapp
(-> public-routes
ring-params/wrap-params))
(defn start-server []
(future (jetty/run-jetty (var myapp) {:port 8080})))
I've tried adding the wrap-params, wrap-keyword-params and wrap-multipart-params but when I PUT to the endpoint using httpie (or my client), I find that params is always empty. Can anyone help?
Thanks!
The only problem with your example code was that it lacks a ring response hash-map in the route body. The solution is evaluate to a ring response instead of using println. When you call println in your route it prints to standard out where the server process is running, which has nothing to do with the response to the API call.
(defroutes public-routes
(PUT "/something" {params :params}
{:status 200
:body (str "Params: " params)}))
This produces a 200 response with Params: {"foo" "bar"} as the response body.
I am using this to test your PUT route:
curl -X PUT -d "foo=bar" http://127.0.0.1:8080/something