From 15182c2d2470410be03726e6c9aaf83810c85234 Mon Sep 17 00:00:00 2001 From: Jon Koops Date: Wed, 2 Oct 2024 12:29:29 +0200 Subject: [PATCH] Resolve Keycloak JS from NPM package metadata (#499) Closes #492 Signed-off-by: Jon Koops --- pages/app/index.ftl | 8 +- .../java/org/keycloak/webbuilder/Context.java | 14 +--- .../org/keycloak/webbuilder/Extensions.java | 4 +- .../java/org/keycloak/webbuilder/News.java | 4 +- .../org/keycloak/webbuilder/Versions.java | 10 ++- .../webbuilder/builders/AppBuilder.java | 46 ++++++----- .../webbuilder/builders/ChangelogBuilder.java | 3 +- .../webbuilder/misc/NpmPackageInfo.java | 24 ------ .../org/keycloak/webbuilder/npm/Package.java | 20 +++++ .../org/keycloak/webbuilder/npm/Registry.java | 23 ++++++ .../webbuilder/npm/SemanticVersion.java | 44 +++++++++++ .../org/keycloak/webbuilder/npm/Version.java | 71 +++++++++++++++++ .../keycloak/webbuilder/utils/JsonParser.java | 6 +- static/app/app-legacy.js | 79 +++++++++++++++++++ static/app/app.js | 4 +- 15 files changed, 294 insertions(+), 66 deletions(-) delete mode 100644 src/main/java/org/keycloak/webbuilder/misc/NpmPackageInfo.java create mode 100644 src/main/java/org/keycloak/webbuilder/npm/Package.java create mode 100644 src/main/java/org/keycloak/webbuilder/npm/Registry.java create mode 100644 src/main/java/org/keycloak/webbuilder/npm/SemanticVersion.java create mode 100644 src/main/java/org/keycloak/webbuilder/npm/Version.java create mode 100644 static/app/app-legacy.js diff --git a/pages/app/index.ftl b/pages/app/index.ftl index 9cabf6f4..168cea5a 100644 --- a/pages/app/index.ftl +++ b/pages/app/index.ftl @@ -2,8 +2,12 @@ <@tmpl.page current="test-app" title="Test application" noindex=true nocsp=true> - - +<#if version.majorVersion < 26> + + +<#else> + +
diff --git a/src/main/java/org/keycloak/webbuilder/Context.java b/src/main/java/org/keycloak/webbuilder/Context.java index d898a5bf..8519fb51 100644 --- a/src/main/java/org/keycloak/webbuilder/Context.java +++ b/src/main/java/org/keycloak/webbuilder/Context.java @@ -34,10 +34,8 @@ public class Context { private FreeMarker freeMarker; private AsciiDoctor asciiDoctor; - private JsonParser jsonParser; public Context(File rootDir) throws Exception { - jsonParser = new JsonParser(); freeMarker = new FreeMarker(rootDir); asciiDoctor = new AsciiDoctor(rootDir); @@ -62,12 +60,12 @@ public void init() throws Exception { config = loadConfig(); links = new Links(config); - versions = new Versions(versionsDir, jsonParser); - extensions = new Extensions(extensionsDir, jsonParser); + versions = new Versions(versionsDir); + extensions = new Extensions(extensionsDir); blogs = new Blogs(blogDir, versions, config, freeMarker, asciiDoctor); guidesMetadata = new YamlParser().read(new File(getWebSrcDir(),"/guides.yaml"), GuidesMetadata.class); guides = new Guides(guidesMetadata, tmpDir, getWebSrcDir(), asciiDoctor); - news = new News(newsDir, blogs, jsonParser, config); + news = new News(newsDir, blogs, config); freeMarker.init(this); asciiDoctor.init(this); @@ -78,7 +76,7 @@ public void close() { } private Config loadConfig() { - Config config = jsonParser.read(new File(getWebSrcDir(),"/config.json"), Config.class); + Config config = JsonParser.read(new File(getWebSrcDir(),"/config.json"), Config.class); config.setPublish(System.getProperties().containsKey("publish")); if (System.getenv().containsKey("KC_URL")) { @@ -113,10 +111,6 @@ public News news() { return news; } - public JsonParser json() { - return jsonParser; - } - public FreeMarker freeMarker() { return freeMarker; } diff --git a/src/main/java/org/keycloak/webbuilder/Extensions.java b/src/main/java/org/keycloak/webbuilder/Extensions.java index fb700b04..26769a45 100644 --- a/src/main/java/org/keycloak/webbuilder/Extensions.java +++ b/src/main/java/org/keycloak/webbuilder/Extensions.java @@ -8,9 +8,9 @@ public class Extensions extends LinkedList { - public Extensions(File extensionsDir, JsonParser json) { + public Extensions(File extensionsDir) { for (File extensionFile : extensionsDir.listFiles((dir, name) -> name.endsWith(".json"))) { - add(json.read(extensionFile, Extension.class)); + add(JsonParser.read(extensionFile, Extension.class)); } Collections.sort(this); } diff --git a/src/main/java/org/keycloak/webbuilder/News.java b/src/main/java/org/keycloak/webbuilder/News.java index 2f20ad03..ea798fee 100644 --- a/src/main/java/org/keycloak/webbuilder/News.java +++ b/src/main/java/org/keycloak/webbuilder/News.java @@ -10,11 +10,11 @@ public class News extends LinkedList { - public News(File newsDir, Blogs blogs, JsonParser json, Config config) throws ParseException { + public News(File newsDir, Blogs blogs, Config config) throws ParseException { File[] newsFiles = newsDir.listFiles((dir, name) -> name.endsWith(".json")); if (newsFiles != null) { for (int i = 0; i < newsFiles.length && i < config.getMaxNews(); i++) { - NewsItem news = json.read(newsFiles[i], NewsItem.class); + NewsItem news = JsonParser.read(newsFiles[i], NewsItem.class); news.setDate(Constants.DATE_IN.parse(newsFiles[i].getName())); add(news); } diff --git a/src/main/java/org/keycloak/webbuilder/Versions.java b/src/main/java/org/keycloak/webbuilder/Versions.java index bae9e53a..9df01fbf 100644 --- a/src/main/java/org/keycloak/webbuilder/Versions.java +++ b/src/main/java/org/keycloak/webbuilder/Versions.java @@ -4,7 +4,6 @@ import org.keycloak.webbuilder.utils.JsonParser; import java.io.File; -import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -15,9 +14,9 @@ public class Versions extends LinkedList { - public Versions(File versionsDir, JsonParser json) { + public Versions(File versionsDir) { for (File versionFile : versionsDir.listFiles((dir, name) -> name.endsWith(".json"))) { - add(json.read(versionFile, Version.class)); + add(JsonParser.read(versionFile, Version.class)); } Collections.sort(this); @@ -74,6 +73,11 @@ public String getVersionShorter() { return split[0] + "." + split[1]; } + public int getMajorVersion() { + String[] split = version.split("\\."); + return Integer.parseInt(split[0]); + } + public Date getDate() { return date; } diff --git a/src/main/java/org/keycloak/webbuilder/builders/AppBuilder.java b/src/main/java/org/keycloak/webbuilder/builders/AppBuilder.java index 1b147574..dc005fdb 100644 --- a/src/main/java/org/keycloak/webbuilder/builders/AppBuilder.java +++ b/src/main/java/org/keycloak/webbuilder/builders/AppBuilder.java @@ -2,34 +2,44 @@ import org.apache.commons.compress.archivers.ArchiveEntry; import org.apache.commons.compress.archivers.ArchiveInputStream; -import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; -import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.io.IOUtils; -import org.keycloak.webbuilder.misc.NpmPackageInfo; +import org.keycloak.webbuilder.npm.Package; +import org.keycloak.webbuilder.npm.Registry; +import org.keycloak.webbuilder.npm.Version; -import java.io.BufferedInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; -import java.net.URL; +import java.nio.file.Path; public class AppBuilder extends AbstractBuilder { @Override protected void build() throws Exception { - NpmPackageInfo npmPackageInfo = context.json().read(new URL("https://registry.npmjs.org/keycloak-js/"), NpmPackageInfo.class); - - File f = new File(context.getTargetDir(), "app/keycloak.js"); - if (!f.isFile()) { - URL u = new URL("https://registry.npmjs.org/keycloak-js/-/keycloak-js-" + npmPackageInfo.getDistTags().getLatest() + ".tgz"); - try (ArchiveInputStream i = new TarArchiveInputStream(new GzipCompressorInputStream(new BufferedInputStream(u.openStream())))) { - for (ArchiveEntry e = i.getNextEntry(); e != null; e = i.getNextEntry()) { - if (e.getName().equals("package/dist/keycloak.js")) { - try (OutputStream o = new FileOutputStream(f)) { - IOUtils.copy(i, o); - } - } - } + Package packageInfo = Registry.getPackage("keycloak-js"); + Version latestVersion = packageInfo.getVersionByTag("latest"); + File targetFile = new File(context.getTargetDir(), "app/keycloak.js"); + + // Skip if target file already exists. + if (targetFile.isFile()) { + return; + } + + boolean useLegacy = latestVersion.getSemanticVersion().getMajor() < 26; + String entryPoint = latestVersion.resolveEntryPoint(useLegacy); + String sourcePath = Path.of("package", entryPoint).normalize().toString(); + ArchiveInputStream tarball = latestVersion.getDist().getTarballStream(); + + for (ArchiveEntry entry = tarball.getNextEntry(); entry != null; entry = tarball.getNextEntry()) { + // Loop trough until we find a file that matches the package entrypoint. + if (!entry.getName().equals(sourcePath)) { + continue; + } + + // Copy the file over when found. + try (OutputStream outputStream = new FileOutputStream(targetFile)) { + IOUtils.copy(tarball, outputStream); } } } diff --git a/src/main/java/org/keycloak/webbuilder/builders/ChangelogBuilder.java b/src/main/java/org/keycloak/webbuilder/builders/ChangelogBuilder.java index f3c0569b..0d4e9f4b 100644 --- a/src/main/java/org/keycloak/webbuilder/builders/ChangelogBuilder.java +++ b/src/main/java/org/keycloak/webbuilder/builders/ChangelogBuilder.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.keycloak.webbuilder.Versions; import org.keycloak.webbuilder.misc.ChangeLogEntry; +import org.keycloak.webbuilder.utils.JsonParser; import org.kohsuke.github.GHIssue; import org.kohsuke.github.GitHub; import org.kohsuke.github.GitHubBuilder; @@ -49,7 +50,7 @@ protected void build() throws Exception { File changeLogFile = new File(releaseCacheDir, "changelog.json"); if (changeLogFile.exists()) { - Versions.ChangeLog changeLog = new Versions.ChangeLog(Arrays.asList(context.json().read(changeLogFile, ChangeLogEntry[].class))); + Versions.ChangeLog changeLog = new Versions.ChangeLog(Arrays.asList(JsonParser.read(changeLogFile, ChangeLogEntry[].class))); if (v.getBlogTemplate() >= 3) { for (ChangeLogEntry e : changeLog.getAll()) { diff --git a/src/main/java/org/keycloak/webbuilder/misc/NpmPackageInfo.java b/src/main/java/org/keycloak/webbuilder/misc/NpmPackageInfo.java deleted file mode 100644 index 83531dcd..00000000 --- a/src/main/java/org/keycloak/webbuilder/misc/NpmPackageInfo.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.keycloak.webbuilder.misc; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -@JsonIgnoreProperties(ignoreUnknown = true) -public class NpmPackageInfo { - - @JsonProperty("dist-tags") - private DistTags distTags; - - public DistTags getDistTags() { - return distTags; - } - - public class DistTags { - private String latest; - - public String getLatest() { - return latest; - } - } - -} diff --git a/src/main/java/org/keycloak/webbuilder/npm/Package.java b/src/main/java/org/keycloak/webbuilder/npm/Package.java new file mode 100644 index 00000000..97b69ba6 --- /dev/null +++ b/src/main/java/org/keycloak/webbuilder/npm/Package.java @@ -0,0 +1,20 @@ +package org.keycloak.webbuilder.npm; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Map; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Package { + @JsonProperty("dist-tags") + private Map distTags; + + @JsonProperty("versions") + private Map versions; + + public Version getVersionByTag(String tag) { + String versionName = distTags.get(tag); + return versions.get(versionName); + } +} diff --git a/src/main/java/org/keycloak/webbuilder/npm/Registry.java b/src/main/java/org/keycloak/webbuilder/npm/Registry.java new file mode 100644 index 00000000..5e312e37 --- /dev/null +++ b/src/main/java/org/keycloak/webbuilder/npm/Registry.java @@ -0,0 +1,23 @@ +package org.keycloak.webbuilder.npm; + +import org.keycloak.webbuilder.utils.JsonParser; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; + +public class Registry { + public static URI REGISTRY_URI; + + static { + try { + REGISTRY_URI = new URI("https://registry.npmjs.org"); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + + public static Package getPackage(String name) throws MalformedURLException { + return JsonParser.read(REGISTRY_URI.resolve("/" + name).toURL(), Package.class); + } +} diff --git a/src/main/java/org/keycloak/webbuilder/npm/SemanticVersion.java b/src/main/java/org/keycloak/webbuilder/npm/SemanticVersion.java new file mode 100644 index 00000000..0a453db2 --- /dev/null +++ b/src/main/java/org/keycloak/webbuilder/npm/SemanticVersion.java @@ -0,0 +1,44 @@ +package org.keycloak.webbuilder.npm; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SemanticVersion { + private static final Pattern VERSION_PATTERN = Pattern.compile("^(\\d+)\\.(\\d+)\\.(\\d+)$"); + + private final int major; + private final int minor; + private final int patch; + + private SemanticVersion(int major, int minor, int patch) { + this.major = major; + this.minor = minor; + this.patch = patch; + } + + public int getMajor() { + return major; + } + + public int getMinor() { + return minor; + } + + public int getPatch() { + return patch; + } + + public static SemanticVersion fromString(String versionString) { + Matcher matcher = VERSION_PATTERN.matcher(versionString); + + if (!matcher.matches()) { + return null; + } + + int major = Integer.parseInt(matcher.group(1)); + int minor = Integer.parseInt(matcher.group(2)); + int patch = Integer.parseInt(matcher.group(3)); + + return new SemanticVersion(major, minor, patch); + } +} diff --git a/src/main/java/org/keycloak/webbuilder/npm/Version.java b/src/main/java/org/keycloak/webbuilder/npm/Version.java new file mode 100644 index 00000000..52418c1e --- /dev/null +++ b/src/main/java/org/keycloak/webbuilder/npm/Version.java @@ -0,0 +1,71 @@ +package org.keycloak.webbuilder.npm; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.commons.compress.archivers.ArchiveInputStream; +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; +import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.net.URL; +import java.util.Map; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Version { + @JsonProperty("version") + private String version; + + @JsonProperty("main") + private String main; + + @JsonProperty("exports") + private Map exports; + + @JsonProperty("dist") + private Dist dist; + + public SemanticVersion getSemanticVersion() { + return SemanticVersion.fromString(version); + } + + public Dist getDist() { + return dist; + } + + public String resolveEntryPoint(boolean useLegacy) { + if (useLegacy) { + return main; + } + + Export defaultExport = exports.get("."); + return defaultExport != null ? defaultExport.getDefaultPath() : null; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Export { + @JsonProperty("default") + private String defaultPath; + + public String getDefaultPath() { + return defaultPath; + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Dist { + @JsonProperty("tarball") + private String tarball; + + public ArchiveInputStream getTarballStream() throws IOException { + URL url = new URL(tarball); + + return new TarArchiveInputStream( + new GzipCompressorInputStream( + new BufferedInputStream(url.openStream()) + ) + ); + } + } +} diff --git a/src/main/java/org/keycloak/webbuilder/utils/JsonParser.java b/src/main/java/org/keycloak/webbuilder/utils/JsonParser.java index bd12344a..3f93d913 100644 --- a/src/main/java/org/keycloak/webbuilder/utils/JsonParser.java +++ b/src/main/java/org/keycloak/webbuilder/utils/JsonParser.java @@ -9,9 +9,9 @@ public class JsonParser { - private final ObjectMapper mapper = new ObjectMapper(); + private static final ObjectMapper mapper = new ObjectMapper(); - public T read(File f, Class t) { + public static T read(File f, Class t) { try { return mapper.readValue(f, t); } catch (Exception e) { @@ -19,7 +19,7 @@ public T read(File f, Class t) { } } - public T read(URL url, Class t) { + public static T read(URL url, Class t) { try { return mapper.readValue(url, t); } catch (Exception e) { diff --git a/static/app/app-legacy.js b/static/app/app-legacy.js new file mode 100644 index 00000000..81ed41c1 --- /dev/null +++ b/static/app/app-legacy.js @@ -0,0 +1,79 @@ +function init() { + document.getElementById('config-form').onsubmit = function() { + updateConfig(); + return false; + } + + var conf = loadConfig(); + + if (conf.url && conf.realm && conf.client) { + var keycloak = new Keycloak({ + url: conf.url, + realm: conf.realm, + clientId: conf.client + }); + + keycloak.init({ + checkLoginIframe: false + }).then(function(auth) { + document.getElementById('login').onclick = keycloak.login; + document.getElementById('logout').onclick = keycloak.logout; + + show('config-view'); + hide('config-edit'); + + if (auth) { + var name; + if (keycloak.tokenParsed['family_name'] || keycloak.tokenParsed['given_name']) { + name = keycloak.tokenParsed['given_name'] + ' ' + keycloak.tokenParsed['family_name'] + } else { + name = keycloak.tokenParsed.preferred_username; + } + document.getElementById('user-details').innerHTML = name; + hide('login'); + show('logout') + show('display-user'); + + } else { + console.info('Not Authenticated'); + hide('display-user'); + show('login'); + hide('logout') + } + }) + } else { + show('config-edit'); + hide('config-view'); + } +} + +function show(id) { + document.getElementById(id).classList.remove('hide'); + document.getElementById(id).classList.add('show'); +} + +function hide(id) { + document.getElementById(id).classList.add('hide'); + document.getElementById(id).classList.remove('show'); +} + +function updateConfig() { + var url = document.getElementById('url').value; + var realm = document.getElementById('realm').value; + var client = document.getElementById('client').value; + + window.location.href = window.location.href.split('#')[0] + '#url=' + url + '&realm=' + realm + '&client=' + client; +} + +function loadConfig() { + var h = window.location.hash.substring(1).split('&'); + var r = {}; + for (var i = 0; i < h.length; i++) { + var t = h[i].split('=') + r[t[0]] = t[1]; + } + return r; +} + +window.onhashchange = init; +window.onload = init; \ No newline at end of file diff --git a/static/app/app.js b/static/app/app.js index 81ed41c1..551d251d 100644 --- a/static/app/app.js +++ b/static/app/app.js @@ -1,3 +1,5 @@ +import Keycloak from './keycloak.js'; + function init() { document.getElementById('config-form').onsubmit = function() { updateConfig(); @@ -76,4 +78,4 @@ function loadConfig() { } window.onhashchange = init; -window.onload = init; \ No newline at end of file +init();