-
Notifications
You must be signed in to change notification settings - Fork 177
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The Okta login page is not rendered when OKTA_BROWSER_AUTH=true. This started happening after Okta released 2019.02.0, which introduced subresource integrity checks on certain JavaScript resources. This bug appears to be Windows specific. I could not reproduce it on any macOS Mojave configuration regardless of JDK 1.8 or JDK 11 version I tried. The root cause is that JavaFX WebEngine on Java 1.8.0_162 or later on Windows does not correctly handle all subresource integrity checks and will refuse to load certain referenced resources like CSS and JavaScript. - Strip subresource integrity directives from DOM before it gets to the JavaFX WebView (this is a hack, and an ugly one) Resolves #272
- Loading branch information
Showing
4 changed files
with
158 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
41 changes: 41 additions & 0 deletions
41
src/main/java/com/okta/tools/io/LoginPageInterceptingProtocolHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package com.okta.tools.io; | ||
|
||
import com.okta.tools.OktaAwsCliEnvironment; | ||
|
||
import java.io.IOException; | ||
import java.net.Proxy; | ||
import java.net.URI; | ||
import java.net.URL; | ||
import java.net.URLConnection; | ||
import java.util.Arrays; | ||
import java.util.function.BiFunction; | ||
import java.util.logging.Logger; | ||
|
||
final class LoginPageInterceptingProtocolHandler extends sun.net.www.protocol.https.Handler { | ||
private static final Logger LOGGER = Logger.getLogger(LoginPageInterceptingProtocolHandler.class.getName()); | ||
private final OktaAwsCliEnvironment environment; | ||
private final BiFunction<URL, URLConnection, URLConnection> filteringUrlConnectionFactory; | ||
|
||
LoginPageInterceptingProtocolHandler(OktaAwsCliEnvironment environment, BiFunction<URL, URLConnection, URLConnection> filteringUrlConnectionFactory) { | ||
this.environment = environment; | ||
this.filteringUrlConnectionFactory = filteringUrlConnectionFactory; | ||
} | ||
|
||
@Override | ||
protected URLConnection openConnection(URL url, Proxy proxy) throws IOException { | ||
URLConnection urlConnection = super.openConnection(url, proxy); | ||
if (environment.oktaOrg.equals(url.getHost()) && | ||
Arrays.asList( | ||
URI.create(environment.oktaAwsAppUrl).getPath(), | ||
"/login/login.htm", | ||
"/auth/services/devicefingerprint" | ||
).contains(url.getPath()) | ||
) { | ||
LOGGER.finest(() -> String.format("[%s] Using filtering URLConnection", url)); | ||
return filteringUrlConnectionFactory.apply(url, urlConnection); | ||
} else { | ||
LOGGER.finest(() -> String.format("[%s] Using unmodified URLConnection", url)); | ||
return urlConnection; | ||
} | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
src/main/java/com/okta/tools/io/SubresourceIntegrityStrippingHack.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package com.okta.tools.io; | ||
|
||
import com.okta.tools.OktaAwsCliEnvironment; | ||
|
||
import java.io.ByteArrayOutputStream; | ||
import java.io.PrintWriter; | ||
import java.net.URL; | ||
import java.util.logging.Logger; | ||
|
||
public class SubresourceIntegrityStrippingHack { | ||
private static final Logger LOGGER = Logger.getLogger(SubresourceIntegrityStrippingHack.class.getName()); | ||
|
||
private SubresourceIntegrityStrippingHack() {} | ||
|
||
public static void overrideHttpsProtocolHandler(OktaAwsCliEnvironment environment) { | ||
try { | ||
URL.setURLStreamHandlerFactory(protocol -> "https".equals(protocol) ? | ||
new LoginPageInterceptingProtocolHandler(environment, | ||
SubresourceIntegrityStrippingURLConnection::new) : | ||
null | ||
); | ||
LOGGER.finest("Successfully registered custom protocol handler"); | ||
} catch (Exception e) { | ||
LOGGER.warning(() -> { | ||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); | ||
e.printStackTrace(new PrintWriter(outputStream)); | ||
return String.format("Unable to register custom protocol handler:%n%s", outputStream.toString()); | ||
}); | ||
} | ||
} | ||
} |
83 changes: 83 additions & 0 deletions
83
src/main/java/com/okta/tools/io/SubresourceIntegrityStrippingURLConnection.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package com.okta.tools.io; | ||
|
||
import org.jsoup.Jsoup; | ||
import org.jsoup.nodes.DataNode; | ||
import org.jsoup.nodes.Document; | ||
import org.jsoup.nodes.Element; | ||
import org.jsoup.nodes.Node; | ||
import org.jsoup.select.Elements; | ||
|
||
import java.io.ByteArrayInputStream; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.io.OutputStream; | ||
import java.net.URISyntaxException; | ||
import java.net.URL; | ||
import java.net.URLConnection; | ||
import java.nio.charset.StandardCharsets; | ||
import java.util.logging.Logger; | ||
|
||
/** | ||
* <p> | ||
* Inspired by a find/replace workaround: | ||
* https://stackoverflow.com/questions/52572853/failed-integrity-metadata-check-in-javafx-webview-ignores-systemprop | ||
* </p> | ||
* <p> | ||
* {@literal @bogeylnj} built the original version of this fix and had this comment: | ||
* <blockquote> | ||
* "javaFX.WebEngine with >1.8.0._162 cannot handle "integrity=" (attribute <link> or <script>) checks on files retrievals properly. | ||
* This custom stream handler will disable the integrity checks by replacing "integrity=" and "integrity =" with a "integrity.disabled" counterpart | ||
* This is very susceptible to breaking if Okta changes the response body again as we are making changes based on the format of the characters in their response" | ||
* </blockquote> | ||
* </p> | ||
* <p> | ||
* The current fix expands on the find/replace solution by using JSoup to do a robust HTML5 parse to find and disable | ||
* the integrity assertions within the DOM and JavaScript content. If I was feeling particularly bold, I'd parse the | ||
* JavaScript with a JavaScript parser, but I like sleep and people using broken software like timely fixes. | ||
* </p> | ||
*/ | ||
final class SubresourceIntegrityStrippingURLConnection extends URLConnection { | ||
private static final Logger LOGGER = Logger.getLogger(SubresourceIntegrityStrippingURLConnection.class.getName()); | ||
private final URLConnection httpsURLConnection; | ||
|
||
SubresourceIntegrityStrippingURLConnection(URL url, URLConnection httpsURLConnection) { | ||
super(url); | ||
this.httpsURLConnection = httpsURLConnection; | ||
} | ||
|
||
@Override | ||
public void connect() throws IOException { | ||
httpsURLConnection.connect(); | ||
} | ||
|
||
@Override | ||
public InputStream getInputStream() throws IOException { | ||
try { | ||
Document document = Jsoup.parse( | ||
httpsURLConnection.getInputStream(), | ||
StandardCharsets.UTF_8.name(), | ||
httpsURLConnection.getURL().toURI().toASCIIString() | ||
); | ||
LOGGER.finest(document::toString); | ||
Elements scriptsAssertingIntegrity = document.select("script:containsData(integrity)"); | ||
for (Element scriptAssertingIntegrity : scriptsAssertingIntegrity) { | ||
String scriptWithSuppressedIntegrity = scriptAssertingIntegrity.data() | ||
.replace("integrity", "integrityDisabled"); | ||
for (Node dataNode : scriptAssertingIntegrity.dataNodes()) { | ||
dataNode.remove(); | ||
} | ||
scriptAssertingIntegrity.appendChild(new DataNode(scriptWithSuppressedIntegrity)); | ||
} | ||
document.select("script[integrity^=sha]").removeAttr("integrity"); | ||
LOGGER.finest(document::toString); | ||
return new ByteArrayInputStream(document.toString().getBytes(StandardCharsets.UTF_8)); | ||
} catch (URISyntaxException e) { | ||
throw new IOException(e); | ||
} | ||
} | ||
|
||
@Override | ||
public OutputStream getOutputStream() throws IOException { | ||
return httpsURLConnection.getOutputStream(); | ||
} | ||
} |