Skip to content

Commit 367ca8b

Browse files
authored
fix: fully implement HTTPServletResponse interface (#109)
Sometimes the object created by reify-ing the HTTPServletResponse causes errors to be thrown because it does not fully implement the reified interface -- just the methods necessary for the OpenSAML method using it. This fully implements the interfaces with dummy methods/return values as necessary to avoid those errors.
1 parent 72b4570 commit 367ca8b

5 files changed

Lines changed: 354 additions & 24 deletions

File tree

VERSION.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
4.2.0
1+
4.2.1

deps.edn

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
org.opensaml/opensaml-xmlsec-impl {:mvn/version "5.1.3"}
1818
potemkin/potemkin {:mvn/version "0.4.7"}
1919
pretty/pretty {:mvn/version "1.0.5"}
20-
ring/ring-codec {:mvn/version "1.2.0"}
20+
ring/ring-codec {:mvn/version "1.3.0"}
2121
jakarta.servlet/jakarta.servlet-api {:mvn/version "6.1.0"}}
2222

2323
:aliases
@@ -28,9 +28,9 @@
2828
org.clojure/tools.logging {:mvn/version "1.3.0"}
2929
org.apache.logging.log4j/log4j-core {:mvn/version "2.24.3"}
3030
org.apache.logging.log4j/log4j-slf4j2-impl {:mvn/version "2.24.3"}
31-
ring/ring {:mvn/version "1.13.0"}
31+
ring/ring {:mvn/version "1.14.2"}
3232
etaoin/etaoin {:mvn/version "1.1.42"}
33-
ring/ring-jetty-adapter {:mvn/version "1.13.0"}}
33+
ring/ring-jetty-adapter {:mvn/version "1.14.2"}}
3434
:extra-paths ["test" "e2e"]}
3535

3636
;; clojure -X:dev:test
@@ -59,7 +59,10 @@
5959
:unused-locals]
6060
:exclude-linters [:deprecations
6161
:unused-ret-vals
62-
:implicit-dependencies]}}
62+
:implicit-dependencies]
63+
;; Suppressing these warnings because it is a bug with eastwood and proxy
64+
;; https://github.com/jonase/eastwood/issues/16
65+
:ignored-faults {:unused-fn-args {saml20-clj.sp.servlet true}}}}
6366

6467
;; clojure -X:dev:cloverage
6568
:cloverage
@@ -95,6 +98,13 @@
9598
:include-license
9699
{:extra-paths ["license"]}
97100

101+
;; Start a Network REPL (nrepl) that you can connect your editor to.
102+
;;
103+
;; clojure -M:dev:nrepl (etc.)
104+
:nrepl
105+
{:extra-deps {nrepl/nrepl {:mvn/version "1.3.1"}}
106+
:main-opts ["-m" "nrepl.cmdline" "-p" "50605"]}
107+
98108
;; clojure -T:build build
99109
;; clojure -T:build deploy
100110
:build

src/saml20_clj/sp/request.clj

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
(:require [clojure.string :as str]
33
[java-time.api :as t]
44
[saml20-clj.coerce :as coerce]
5+
[saml20-clj.sp.servlet :as servlet]
56
[saml20-clj.state :as state])
67
(:import [org.opensaml.saml.common.messaging.context SAMLBindingContext SAMLEndpointContext SAMLPeerEntityContext]
78
[org.opensaml.saml.saml2.core AuthnRequest LogoutRequest NameIDType]
@@ -88,23 +89,9 @@
8889
(state/record-request! state-manager (.getID request)))
8990
(setup-message-context request credential sig-alg idp-url)))
9091

91-
(defn- map-making-servlet
92-
"Implements a minimum HttpServletResponse for HTTPRedirectDeflateEncoder"
93-
[]
94-
(let [response (atom {:status 302 :body "" :headers {}})
95-
servlet-wrapper (reify jakarta.servlet.http.HttpServletResponse
96-
(setHeader [_this name value]
97-
(swap! response update :headers assoc name value))
98-
(^void setCharacterEncoding [_ ^String _])
99-
(sendRedirect [this redirect]
100-
(.setHeader this "location" redirect)))
101-
wrapper-supplier (reify net.shibboleth.shared.primitive.NonnullSupplier
102-
(get [_] servlet-wrapper))]
103-
[wrapper-supplier #(deref response)]))
104-
10592
(defn- redirect-response
10693
[^MessageContext saml-request relay-state]
107-
(let [[servlet ->ring-request] (map-making-servlet)
94+
(let [[servlet ->ring-request] (servlet/map-making-servlet)
10895
^SAMLBindingContext binding-context (.getSubcontext saml-request SAMLBindingContext true)]
10996
;; set the relay state
11097
(.setRelayState binding-context relay-state)
@@ -155,10 +142,10 @@
155142
;; protocol binding specifying if IdP should use HTTP-Post or HTTP-Redirect to respond
156143
protocol-binding
157144
instant]
158-
:or {instant (t/instant)
159-
request-id (random-request-id)
160-
sig-alg -sig-alg
161-
protocol-binding :redirect}}]
145+
:or {instant (t/instant)
146+
request-id (random-request-id)
147+
sig-alg -sig-alg
148+
protocol-binding :redirect}}]
162149
(assert (non-blank-string? acs-url) "acs-url is required")
163150
(assert (non-blank-string? idp-url) "idp-url is required")
164151
(assert (non-blank-string? sp-name) "sp-name is required")

src/saml20_clj/sp/servlet.clj

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
(ns saml20-clj.sp.servlet
2+
"Jakarta servlet implementation for SAML request handling")
3+
4+
(set! *warn-on-reflection* true)
5+
6+
(defn map-making-servlet
7+
"Implements a complete HttpServletResponse for HTTPRedirectDeflateEncoder"
8+
[]
9+
(let [response (atom {:status 302 :body "" :headers {}})
10+
servlet-wrapper (reify jakarta.servlet.http.HttpServletResponse
11+
;; Redirect methods
12+
(sendRedirect [this redirect]
13+
(.setHeader this "location" redirect))
14+
(sendError [_this _sc])
15+
(sendError [_this _sc _msg])
16+
17+
;; Header methods
18+
(setHeader [_this name value]
19+
(swap! response update :headers assoc name value))
20+
(addHeader [_this name value]
21+
(swap! response update :headers
22+
#(update % name
23+
(fn [existing]
24+
(if existing
25+
(str existing "," value)
26+
value)))))
27+
(setIntHeader [this name value]
28+
(.setHeader this name (str value)))
29+
(addIntHeader [this name value]
30+
(.addHeader this name (str value)))
31+
(setDateHeader [this name date]
32+
(.setHeader this name (str date)))
33+
(addDateHeader [this name date]
34+
(.addHeader this name (str date)))
35+
(containsHeader [_this name]
36+
(contains? (:headers @response) name))
37+
38+
;; Status methods
39+
(setStatus [_this sc]
40+
(swap! response assoc :status sc))
41+
(getStatus [_this]
42+
(:status @response))
43+
44+
;; Content type and encoding
45+
(setContentType [_this type]
46+
(swap! response update :headers assoc "Content-Type" type))
47+
(getContentType [_this]
48+
(get-in @response [:headers "Content-Type"]))
49+
(^void setCharacterEncoding [_this ^String _encoding])
50+
(getCharacterEncoding [_this] "UTF-8")
51+
52+
;; Content length
53+
(setContentLength [_this len]
54+
(swap! response update :headers assoc "Content-Length" (str len)))
55+
(setContentLengthLong [_this len]
56+
(swap! response update :headers assoc "Content-Length" (str len)))
57+
58+
;; Locale
59+
(setLocale [_this _locale])
60+
(getLocale [_this] (java.util.Locale/getDefault))
61+
62+
;; Buffer management
63+
(getBufferSize [_this] 8192)
64+
(setBufferSize [_this _size])
65+
(isCommitted [_this] false)
66+
(reset [_this])
67+
(resetBuffer [_this])
68+
(flushBuffer [_this])
69+
70+
;; Output streams (return dummy implementations)
71+
(getOutputStream [_this]
72+
(proxy [jakarta.servlet.ServletOutputStream] []
73+
(write [_b])
74+
(isReady [] true)
75+
(setWriteListener [_listener])))
76+
(getWriter [_this]
77+
(java.io.PrintWriter.
78+
(java.io.StringWriter.)))
79+
80+
;; Cookies
81+
(addCookie [_this _cookie])
82+
83+
;; URL encoding
84+
(encodeURL [_this url] url)
85+
(encodeRedirectURL [_this url] url)
86+
87+
;; HTTP/2 trailer support
88+
(getTrailerFields [_this]
89+
(reify java.util.function.Supplier
90+
(get [_] {})))
91+
(setTrailerFields [_this _supplier])
92+
93+
;; Header collections (ServletResponse interface)
94+
(getHeader [_this name]
95+
(get-in @response [:headers name]))
96+
(getHeaders [_this name]
97+
(if-let [header (get-in @response [:headers name])]
98+
[header]
99+
[]))
100+
(getHeaderNames [_this]
101+
(keys (:headers @response))))
102+
wrapper-supplier (reify net.shibboleth.shared.primitive.NonnullSupplier
103+
(get [_] servlet-wrapper))]
104+
[wrapper-supplier #(deref response)]))

0 commit comments

Comments
 (0)