55import jakarta .servlet .http .HttpServletResponse ;
66import java .io .IOException ;
77import java .io .OutputStream ;
8+ import java .net .URL ;
89import java .nio .charset .StandardCharsets ;
910import java .time .Duration ;
1011import java .time .Instant ;
@@ -40,6 +41,8 @@ public final class HttpController extends AbstractInitializableComponent {
4041
4142 private DataSealer dataSealer ;
4243
44+ private String idpEntityID ;
45+
4346 public void setHttpClient (@ Nonnull HttpClient client ) {
4447 checkSetterPreconditions ();
4548 httpClient = Constraint .isNotNull (client , "HttpClient cannot be null" );
@@ -55,6 +58,12 @@ public void setDataSealer(@Nonnull DataSealer sealer) {
5558 dataSealer = Constraint .isNotNull (sealer , "DataSealer cannot be null" );
5659 }
5760
61+ public void setIdpEntityID (@ Nonnull String id ) {
62+ checkSetterPreconditions ();
63+ Constraint .isFalse (Strings .isNullOrEmpty (id ), "idpEntityId cannot be null or empty" );
64+ idpEntityID = id ;
65+ }
66+
5867 @ Override
5968 protected void doInitialize () throws ComponentInitializationException {
6069 super .doInitialize ();
@@ -68,6 +77,9 @@ protected void doInitialize() throws ComponentInitializationException {
6877 if (null == dataSealer ) {
6978 throw new ComponentInitializationException ("DataSealer cannot be null" );
7079 }
80+ if (Strings .isNullOrEmpty (idpEntityID )) {
81+ throw new ComponentInitializationException ("idpEntityId cannot be null or empty" );
82+ }
7183 }
7284
7385 @ PostMapping ("/fabricate" )
@@ -97,6 +109,16 @@ public void fabricate(@Nonnull HttpServletRequest httpRequest, @Nonnull HttpServ
97109 return ;
98110 }
99111
112+ // Check that the request Host header has the expected value. We will send it a nested request later, and this
113+ // is a little extra protection against SSRF. Sadly, the IdP does not really know its own hostname. We will use
114+ // the host portion of our entityID. This is a hacky approximation. In theory an IdP could use different
115+ // hostnames in its entityID and its SAML endpoints.
116+ String expectedHost = new URL (idpEntityID ).getHost ();
117+ if (!expectedHost .equals (httpRequest .getServerName ())) {
118+ sendError (httpResponse , 400 , "Unexpected Host, should be " + expectedHost );
119+ return ;
120+ }
121+
100122 if (authorityToken .startsWith ("E:" )) {
101123 sendError (httpResponse , 403 , "Authority token generator error: " + authorityToken );
102124 return ;
@@ -130,7 +152,7 @@ public void fabricate(@Nonnull HttpServletRequest httpRequest, @Nonnull HttpServ
130152
131153 String cookies = parts [2 ];
132154
133- String expectedPrefix = "https://" + httpRequest . getServerName () + "/idp/profile/SAML2/Redirect/SSO?" ;
155+ String expectedPrefix = "https://" + expectedHost + "/idp/profile/SAML2/Redirect/SSO?" ;
134156 if (!targetUrl .startsWith (expectedPrefix )) {
135157 sendError (httpResponse , 403 , "Invalid target URL" );
136158 return ;
0 commit comments