pedestal

simongray 2020-11-03T15:27:39.095Z

In case the user is trying to access some path that they’re not allowed into, I want my service to short-circuit and return HTTP status 403 immediately. What is the right way to do this? Should I throw an exception in one interceptor and then handle it in another?

simongray 2020-11-03T15:44:48.098300Z

Also, based on http://pedestal.io/guides/hello-world-content-types > One important note here: As soon as any interceptor attaches a response to the context map, Pedestal considers the request handled. Remaining interceptors won’t be called, and only the ones that are already on the stack will have their `:leave` functions called. This is a “short-circuit” behavior like an early-exit in code. I would expect an interceptor chain consisting of two interceptors where the first one attaches a 403 response to short-circuit the chain, but it doesn’t seem to do so? This is the code for the interceptor:

(ic/interceptor
    {:name  ::session-guard
     :enter (fn [ctx]
              (assoc ctx :response {:status 403}))})
It is the first interceptor in a vector containing two of them. If it is the only interceptor in the chain I get my 403 response, but otherwise it just continues on the to the next interceptor. Kind of unexpected?

simongray 2020-11-03T15:48:06.101100Z

This is the relevant route:

#{["/guarded" :get [(ic/session-guard conf) (guarded-page)] :route-name ::guarded-page]}
(ic/session-guard conf) creates the interceptor that attaches a 403 response, while (guarded-page) creates a ring handler that makes a regular 200 response. When both are present I always get the 200 and if only the first interceptor is there I get the 403. No short-circuiting in sight?

simongray 2020-11-03T16:20:45.104100Z

So I followed the Pedestal source code all the way to

(defn- terminator-inject
  [context]
  (interceptor.chain/terminate-when context #(ring-response/response? (:response %))))
Which checks whether the response is a valid Ring response:
(defn response?
  "True if the supplied value is a valid response map."
  {:added "1.1"}
  [resp]
  (and (map? resp)
       (integer? (:status resp))
       (map? (:headers resp))))
And then I noticed that :headers must be set, so I went and modified my interceptor to also return a headers map:
(defn session-guard
  [{:keys [users]}]
  (ic/interceptor
    {:name  ::session-guard
     :enter (fn [ctx]
              (assoc ctx :response {:status 403
                                    :headers {}}))}))
and now it short-circuits as expected 🙂 I would still like to know if it makes more sense to do this as part of error-handling though. Any thoughts? Also, sorry for a wall of text.

souenzzo 2020-11-03T16:36:50.104700Z

there is a io.pedestal.http/response?, that is used by the standard not-found-interceptor 😉

Louis Kottmann 2020-11-03T18:32:04.105600Z

actually I discovered that if you just assoc a response to the context in an interceptor, it stops the chain right there and sends your response

Louis Kottmann 2020-11-03T18:32:11.105900Z

(assoc context :response (u/forbidden msg))

orestis 2020-11-03T18:32:23.106500Z

There’s also “terminate” http://pedestal.io/api/pedestal.interceptor/io.pedestal.interceptor.chain.html#var-terminate

Louis Kottmann 2020-11-03T18:32:33.106800Z

but I wonder if that's as valid as I tested

Louis Kottmann 2020-11-03T18:32:51.107200Z

yeah I did see terminate, but why not just set the response if it's enough?

chrisulloa 2020-11-03T18:33:45.107700Z

Yeah the :response value should trigger the short-circuiting. I wasn’t aware of the header requirement, but in our services we always attach content-type headers.

👌 1
chrisulloa 2020-11-03T18:34:51.108900Z

To me throwing an error in your interceptor, then catching it in another error interceptor and returning a 403 seems like a good way to do it. Especially if you are in a place where it’s easier to throw an exception than return a response. But would be interesting to know how other people handle that.

👍 1
chrisulloa 2020-11-03T18:35:15.109300Z

We usually check permissions at the very beginning of the interceptor stack.

chrisulloa 2020-11-03T18:35:20.109600Z

So don’t find myself needing to do that.

Louis Kottmann 2020-11-03T18:35:49.110100Z

for the header, I use the :leave interceptor io.pedestal.http/json-body and never had an issue (I never set them manually)

chrisulloa 2020-11-03T18:37:08.110500Z

json-body docs

Set the Content-Type header to "application/json" and convert the body to
JSON if the body is a collection and a type has not been set.

simongray 2020-11-03T21:49:33.111100Z

seems kind of sloppy that two only slightly different predicate functions are in use for termination and not-found… wonder why?