From 28ca6ccc7141faaa7a243c4ab1e4a9e5b27f7a9b Mon Sep 17 00:00:00 2001 From: Erich Bremer Date: Thu, 25 May 2023 12:13:35 -0400 Subject: [PATCH 1/8] wip --- dependency-reduced-pom.xml | 2 +- pom.xml | 2 +- .../com/ebremer/halcyon/HalcyonSettings.java | 54 ++++++++++--------- src/main/java/com/ebremer/halcyon/INIT.java | 11 ++-- .../shiro/KeycloakPublicKeyFetcher.java | 2 +- .../com/ebremer/halcyon/gui/HomePage.html | 1 - .../halcyon/imagebox/CustomPortConfig.java | 21 ++++++++ .../com/ebremer/halcyon/imagebox/Main.java | 12 ++--- src/main/java/com/ebremer/ns/HAL.java | 1 + 9 files changed, 66 insertions(+), 40 deletions(-) create mode 100644 src/main/java/com/ebremer/halcyon/imagebox/CustomPortConfig.java diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index a74b7c80..d03886ae 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -10,7 +10,7 @@ com.ebremer Halcyon Halcyon - 0.4.1 + 0.4.2 A whole slide image annotation, management, and visualization system github diff --git a/pom.xml b/pom.xml index 6c146f74..49c88a94 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.ebremer Halcyon - 0.4.1 + 0.4.2 jar Halcyon A whole slide image annotation, management, and visualization system diff --git a/src/main/java/com/ebremer/halcyon/HalcyonSettings.java b/src/main/java/com/ebremer/halcyon/HalcyonSettings.java index 65856b60..fb763884 100644 --- a/src/main/java/com/ebremer/halcyon/HalcyonSettings.java +++ b/src/main/java/com/ebremer/halcyon/HalcyonSettings.java @@ -36,7 +36,7 @@ * * @author erich */ -public class HalcyonSettings { +public final class HalcyonSettings { private final String webfiles = "/ib"; private final long MaxAgeReaderPool = 600; private final long ReaderPoolScanDelay = 600; @@ -48,18 +48,23 @@ public class HalcyonSettings { private Property RDFStoreLocation = null; private Property RDFSecurityStoreLocation = null; private Model m; - public static final String realm = "master"; private final Property urlpathprefix; - private static final int DEFAULTSPARQLPORT = 8887; private final Property SPARQLPORT; private final Property MULTIVIEWERLOCATION; - public static final String VERSION = "0.4.1"; - public static Resource HALCYONAGENT = ResourceFactory.createResource(HAL.NS+"/VERSION/"+VERSION); private static final String MasterSettingsLocation = "settings.ttl"; private Resource Master; private final HashMap mappings; private final String Realm = "master"; + public static final String realm = "master"; + public static final int DEFAULTHTTPPORT = 8888; + public static final int DEFAULTHTTPSPORT = 9999; + public static final int DEFAULTSPARQLPORT = 8887; + public static final String DEFAULTHOSTNAME = "http://localhost"; + public static final String DEFAULTHOSTIP = "0.0.0.0"; + public static final String VERSION = "0.4.2"; + public static Resource HALCYONAGENT = ResourceFactory.createResource(HAL.NS+"/VERSION/"+VERSION); + private HalcyonSettings() { File f = new File(MasterSettingsLocation); mappings = new HashMap<>(); @@ -103,7 +108,7 @@ public String getHostName() { QuerySolution sol = results.nextSolution(); return sol.get("HostName").asLiteral().getString(); } - return "http://localhost:8888"; + return "http://localhost:"+DEFAULTHTTPPORT; } public String getProxyHostName() { @@ -115,7 +120,7 @@ public String getProxyHostName() { QuerySolution sol = results.nextSolution(); return sol.get("ProxyHostName").asLiteral().getString(); } - return "http://localhost:8888"; + return DEFAULTHOSTNAME+":"+DEFAULTHTTPPORT; } public long getMaxAgeReaderPool() { @@ -138,7 +143,7 @@ public static HalcyonSettings getSettings() { } public void GenerateDefaultSettings() { - Master = m.createResource("http://localhost"); + Master = m.createResource(DEFAULTHOSTNAME); m = ModelFactory.createDefaultModel(); m.setNsPrefix("", HAL.NS); m.add(Master, RDF.type, HalcyonSettingsFile); @@ -165,16 +170,6 @@ public String GetMasterID() { return null; } - public int GetHostPort() { - try { - URL url = new URL(getHostName()); - return url.getPort(); - } catch (MalformedURLException ex) { - Logger.getLogger(HalcyonSettings.class.getName()).log(Level.SEVERE, null, ex); - } - return 8888; - } - public int GetSPARQLPort() { ParameterizedSparqlString pss = new ParameterizedSparqlString( "select ?port where {?s :SPARQLport ?port}"); pss.setNsPrefix("", HAL.NS); @@ -187,6 +182,19 @@ public int GetSPARQLPort() { return DEFAULTSPARQLPORT; } + public String GetHostIP() { + ParameterizedSparqlString pss = new ParameterizedSparqlString( "select ?port where {?s ?p ?ip}"); + pss.setNsPrefix("", HAL.NS); + pss.setIri("p", HAL.HostIP.getURI()); + QueryExecution qe = QueryExecutionFactory.create(pss.toString(),m); + ResultSet results = qe.execSelect(); + if (results.hasNext()) { + QuerySolution sol = results.nextSolution(); + return sol.get("ip").asLiteral().getString(); + } + return DEFAULTHOSTIP; + } + public int GetHTTPPort() { String qs = "prefix : <"+HAL.NS+"> select ?port where {?s :HTTPPort ?port}"; Query query = QueryFactory.create(qs); @@ -196,7 +204,7 @@ public int GetHTTPPort() { QuerySolution sol = results.nextSolution(); return sol.get("port").asLiteral().getInt(); } - return 8080; + return DEFAULTHTTPPORT; } public boolean isHTTPSenabled() { @@ -213,7 +221,7 @@ public int GetHTTPSPort() { QuerySolution sol = results.nextSolution(); return sol.get("port").asLiteral().getInt(); } - return 443; + return DEFAULTHTTPSPORT; } public String getRDFStoreLocation() { @@ -266,10 +274,8 @@ public static void main(String[] args) throws MalformedURLException { loci.common.DebugTools.setRootLevel("WARN"); HalcyonSettings s = HalcyonSettings.getSettings(); System.out.println("Proxy Host Name "+s.getProxyHostName()); - System.out.println("Port "+s.GetHostPort()); - Iterator i = s.getStorageLocations().iterator(); - while (i.hasNext()) { - StorageLocation sl = i.next(); + System.out.println("Port "+s.GetHTTPPort()); + for (StorageLocation sl : s.getStorageLocations()) { System.out.println(sl.path.toUri()+" **** "+sl.urlpath); } } diff --git a/src/main/java/com/ebremer/halcyon/INIT.java b/src/main/java/com/ebremer/halcyon/INIT.java index f4a8f3a5..7db203c1 100644 --- a/src/main/java/com/ebremer/halcyon/INIT.java +++ b/src/main/java/com/ebremer/halcyon/INIT.java @@ -46,12 +46,13 @@ public Model getDefaultSettings() { m.createResource("http://localhost") .addProperty(RDF.type, HAL.HalcyonSettingsFile) .addProperty(HAL.RDFStoreLocation, "TDB2") - .addProperty(HAL.HostName, "http://localhost:8888") - .addLiteral(HAL.HTTPPort, 8888) - .addLiteral(HAL.HTTPSPort, 9999) - .addProperty(HAL.ProxyHostName, "http://localhost:8888") + .addProperty(HAL.HostName, "http://localhost:"+HalcyonSettings.DEFAULTHTTPPORT) + //.addProperty(HAL.HostIP, "0.0.0.0") //not fully implemented yet + .addLiteral(HAL.HTTPPort, HalcyonSettings.DEFAULTHTTPPORT) + .addLiteral(HAL.HTTPSPort, HalcyonSettings.DEFAULTHTTPSPORT) + .addProperty(HAL.ProxyHostName, "http://localhost:"+HalcyonSettings.DEFAULTHTTPPORT) .addLiteral(HAL.HTTPSenabled, false) - .addLiteral(HAL.SPARQLport, 8887); + .addLiteral(HAL.SPARQLport, HalcyonSettings.DEFAULTSPARQLPORT); return m; } diff --git a/src/main/java/com/ebremer/halcyon/fuseki/shiro/KeycloakPublicKeyFetcher.java b/src/main/java/com/ebremer/halcyon/fuseki/shiro/KeycloakPublicKeyFetcher.java index 3c65624c..4dddb565 100644 --- a/src/main/java/com/ebremer/halcyon/fuseki/shiro/KeycloakPublicKeyFetcher.java +++ b/src/main/java/com/ebremer/halcyon/fuseki/shiro/KeycloakPublicKeyFetcher.java @@ -22,7 +22,7 @@ public class KeycloakPublicKeyFetcher { private static KeycloakPublicKeyFetcher kpkf = null; - private final String oidcConfigurationUrl = "http://localhost:"+HalcyonSettings.getSettings().GetHostPort() + "/auth/realms/master/protocol/openid-connect/certs"; + private final String oidcConfigurationUrl = "http://localhost:"+HalcyonSettings.getSettings().GetHTTPPort() + "/auth/realms/master/protocol/openid-connect/certs"; private static PublicKey publicKey = null; private KeycloakPublicKeyFetcher() { diff --git a/src/main/java/com/ebremer/halcyon/gui/HomePage.html b/src/main/java/com/ebremer/halcyon/gui/HomePage.html index d24410d2..b51d9d33 100644 --- a/src/main/java/com/ebremer/halcyon/gui/HomePage.html +++ b/src/main/java/com/ebremer/halcyon/gui/HomePage.html @@ -17,7 +17,6 @@ diff --git a/src/main/java/com/ebremer/halcyon/imagebox/CustomPortConfig.java b/src/main/java/com/ebremer/halcyon/imagebox/CustomPortConfig.java new file mode 100644 index 00000000..2b61b273 --- /dev/null +++ b/src/main/java/com/ebremer/halcyon/imagebox/CustomPortConfig.java @@ -0,0 +1,21 @@ +package com.ebremer.halcyon.imagebox; + +import com.ebremer.halcyon.HalcyonSettings; +import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class CustomPortConfig { + /* + private final HalcyonSettings settings = HalcyonSettings.getSettings(); + + @Bean + public WebServerFactoryCustomizer webServerFactoryCustomizer() { + return factory -> { + factory.setPort(settings.GetHTTPPort()); + factory.set + }; + }*/ +} diff --git a/src/main/java/com/ebremer/halcyon/imagebox/Main.java b/src/main/java/com/ebremer/halcyon/imagebox/Main.java index 108f0f02..8b1771c9 100644 --- a/src/main/java/com/ebremer/halcyon/imagebox/Main.java +++ b/src/main/java/com/ebremer/halcyon/imagebox/Main.java @@ -55,7 +55,7 @@ public static SessionIdMapper getSessionIdMapper() { @Order(Ordered.HIGHEST_PRECEDENCE) public ServletRegistrationBean proxyServletRegistrationBean() { ServletRegistrationBean bean = new ServletRegistrationBean(new HalcyonProxyServlet(), "/sparql/*"); - bean.addInitParameter("targetUri", "http://localhost:8887/rdf"); + bean.addInitParameter("targetUri", "http://localhost:"+settings.GetSPARQLPort()+"/rdf"); bean.addInitParameter(ProxyServlet.P_PRESERVECOOKIES, "true"); bean.addInitParameter(ProxyServlet.P_HANDLEREDIRECTS, "true"); return bean; @@ -69,7 +69,8 @@ public UndertowServletWebServerFactory embeddedServletContainerFactory() { int ioThreads = cores; int taskThreads = 16*cores; System.out.println("ioThreads :"+ioThreads+"\ntaskThreads :"+taskThreads); - factory.setIoThreads(ioThreads); + factory.setIoThreads(ioThreads); + factory.setPort(settings.GetHTTPPort()); if (settings.isHTTPSenabled()) { factory.addBuilderCustomizers(builder -> { SSLContext ssl; @@ -77,7 +78,7 @@ public UndertowServletWebServerFactory embeddedServletContainerFactory() { ssl = getSSLContext(); System.out.println("HTTPS PORT : "+settings.GetHTTPSPort()); builder - .addHttpsListener(settings.GetHTTPSPort(), "0.0.0.0", ssl) + .addHttpsListener(settings.GetHTTPSPort(), settings.GetHostIP(), ssl) .setServerOption(UndertowOptions.ENABLE_HTTP2, true) .setServerOption(UndertowOptions.MAX_PARAMETERS, 100000) .setServerOption(UndertowOptions.MAX_CONCURRENT_REQUESTS_PER_CONNECTION, 100); @@ -184,16 +185,13 @@ private SSLContext createSSLContext(final KeyStore keyStore, final KeyStore trus KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); keyManagerFactory.init(keyStore, keyStorePassword.toCharArray()); keyManagers = keyManagerFactory.getKeyManagers(); - TrustManager[] trustManagers; TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(trustStore); trustManagers = trustManagerFactory.getTrustManagers(); - SSLContext sslContext; sslContext = SSLContext.getInstance("TLS"); sslContext.init(keyManagers, trustManagers, null); - return sslContext; } @@ -215,6 +213,6 @@ public static void main(String[] args) throws NoSuchAlgorithmException { SpringApplication app = new SpringApplication(Main.class); app.setBannerMode(Mode.CONSOLE); ApplicationContext yay = app.run(args); - System.out.println("===================== " +app.getWebApplicationType()); + System.out.println("===================== Welcome to Halcyon!"); } } diff --git a/src/main/java/com/ebremer/ns/HAL.java b/src/main/java/com/ebremer/ns/HAL.java index 0c5ba1bc..4f138390 100644 --- a/src/main/java/com/ebremer/ns/HAL.java +++ b/src/main/java/com/ebremer/ns/HAL.java @@ -83,6 +83,7 @@ public class HAL { public static final Property RDFStoreLocation = m.createProperty(NS+"RDFStoreLocation"); public static final Property MasterStorageLocation = m.createProperty(NS+"MasterStorageLocation"); public static final Property HostName = m.createProperty(NS+"HostName"); + public static final Property HostIP = m.createProperty(NS+"HostIP"); public static final Property HTTPPort = m.createProperty(NS+"HTTPPort"); public static final Property HTTPSPort = m.createProperty(NS+"HTTPSPort"); public static final Property ProxyHostName = m.createProperty(NS+"ProxyHostName"); From e8fc7727b3fcf38c01d8d7154357ec2b7059aebf Mon Sep 17 00:00:00 2001 From: Erich Bremer Date: Fri, 26 May 2023 09:17:07 -0400 Subject: [PATCH 2/8] wip --- README.md | 74 +------------------ .../com/ebremer/halcyon/HalcyonSettings.java | 6 +- src/main/java/com/ebremer/halcyon/INIT.java | 2 +- .../com/ebremer/halcyon/imagebox/Main.java | 4 +- .../com/ebremer/halcyon/imagebox/Stack.java | 7 +- src/main/resources/settings.ttl | 1 + 6 files changed, 12 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index b8418ca6..51c73699 100644 --- a/README.md +++ b/README.md @@ -33,74 +33,6 @@ mvn -Pwindows-installer jpackage:jpackage ## SSL: -[How to enable HTTPS in a Spring Boot Java application](https://www.thomasvitale.com/https-spring-boot-ssl-certificate/) - -```sh -keytool -genkeypair -alias halcyon -keyalg RSA -keysize 4096 -storetype PKCS12 -keystore halcyon.p12 -validity 3650 -storepass password -``` - -### Private key (seems the same as above, will test) - -```sh -keytool -genkey -keyalg RSA -alias selfsigned -keystore halcyon.jks -storepass password -validity 360 -keysize 2048 -``` - -### See also: - -[How set up Spring Boot to run HTTPS / HTTP ports](https://stackoverflow.com/questions/30896234/how-set-up-spring-boot-to-run-https-http-ports/49740689) - - -## Migrations to p12 - -```sh -keytool -importkeystore -srckeystore cacerts -destkeystore cacerts -deststoretype pkcs12 - -keytool -delete -noprompt -alias halcyon -keystore cacerts.p12 -storepass changeit -``` - -### Export key: - -```sh -keytool -export -keystore cacerts.p12 -alias halcyon -file halcyon.cer -``` - -### Import key - -```sh -keytool -import -alias halcyon -keystore C:\bin\graalvm\lib\security\cacerts -file erich-bremer.pem -``` - -### Convert apache key to right format: - -```sh -openssl pkcs12 -export -in [filename-certificate] -inkey [filename-key] -name [host] -out [filename-new-PKCS-12.p12] -``` - -[Import an existing SSL certificate and private key for Wowza Streaming Engine](https://www.wowza.com/docs/how-to-import-an-existing-ssl-certificate-and-private-key#:~:text=You%20can't%20directly%20import,12%20file%20into%20your%20keystore.) - - -## Importing new/renewal PEM certs into certificate store - -```sh -openssl pkcs12 -export -in wow.fullchain -inkey atoz2022.key -name shared > server.p12 - -keytool -importkeystore -srckeystore server.p12 -destkeystore cacerts.p12 -srcstoretype pkcs12 -alias shared -``` - -## Extra - -```sh -keytool -import -alias sbu -keystore cacerts.p12 -trustcacerts -file inter.crt -keytool -list -v -keystore cacerts.p12 -keytool -list -v -keystore cacerts.p12 -keytool -list -v -keystore cacerts.p12 | more -keytool -import -alias sbu -keystore cacerts.p12 -trustcacerts -file inter.crt -keytool -import -alias sbu -keystore /home/bremer/graalvm/lib/security/cacerts -trustcacerts -file inter.crt -keytool -list -v -keystore cacerts.p12 -keytool -list -v -keystore cacerts.p12 | grep alias -keytool -list -v -keystore cacerts.p12 | grep Alias -keytool -list -v -keystore cacerts.p12 | grep Alias | more -keytool -list -v -keystore cacerts.p12 | grep atoz -keytool -importkeystore -deststorepass changeit -destkeystore cacerts.p12 -srckeystore atoz.p12 -srcstoretype PKCS12 -keytool -list -v -keystore cacerts.p12 | grep atoz -``` +openssl req -new -newkey rsa:2048 -nodes -keyout beak.key -out beak.csr +openssl pkcs12 -export -in beak.crt -inkey beak.key -name Halcyon -out beak.p12 +keytool -importkeystore -deststorepass changeit -destkeystore cacerts -srckeystore beak.p12 -srcstoretype PKCS12 diff --git a/src/main/java/com/ebremer/halcyon/HalcyonSettings.java b/src/main/java/com/ebremer/halcyon/HalcyonSettings.java index fb763884..599cfcae 100644 --- a/src/main/java/com/ebremer/halcyon/HalcyonSettings.java +++ b/src/main/java/com/ebremer/halcyon/HalcyonSettings.java @@ -8,11 +8,9 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; -import java.net.URL; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; -import java.util.Iterator; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.jena.query.ParameterizedSparqlString; @@ -183,13 +181,13 @@ public int GetSPARQLPort() { } public String GetHostIP() { - ParameterizedSparqlString pss = new ParameterizedSparqlString( "select ?port where {?s ?p ?ip}"); + ParameterizedSparqlString pss = new ParameterizedSparqlString( "select ?ip where {?s ?p ?ip}"); pss.setNsPrefix("", HAL.NS); pss.setIri("p", HAL.HostIP.getURI()); QueryExecution qe = QueryExecutionFactory.create(pss.toString(),m); ResultSet results = qe.execSelect(); if (results.hasNext()) { - QuerySolution sol = results.nextSolution(); + QuerySolution sol = results.next(); return sol.get("ip").asLiteral().getString(); } return DEFAULTHOSTIP; diff --git a/src/main/java/com/ebremer/halcyon/INIT.java b/src/main/java/com/ebremer/halcyon/INIT.java index 7db203c1..496abc17 100644 --- a/src/main/java/com/ebremer/halcyon/INIT.java +++ b/src/main/java/com/ebremer/halcyon/INIT.java @@ -47,7 +47,7 @@ public Model getDefaultSettings() { .addProperty(RDF.type, HAL.HalcyonSettingsFile) .addProperty(HAL.RDFStoreLocation, "TDB2") .addProperty(HAL.HostName, "http://localhost:"+HalcyonSettings.DEFAULTHTTPPORT) - //.addProperty(HAL.HostIP, "0.0.0.0") //not fully implemented yet + .addProperty(HAL.HostIP, "0.0.0.0") //not fully implemented yet .addLiteral(HAL.HTTPPort, HalcyonSettings.DEFAULTHTTPPORT) .addLiteral(HAL.HTTPSPort, HalcyonSettings.DEFAULTHTTPSPORT) .addProperty(HAL.ProxyHostName, "http://localhost:"+HalcyonSettings.DEFAULTHTTPPORT) diff --git a/src/main/java/com/ebremer/halcyon/imagebox/Main.java b/src/main/java/com/ebremer/halcyon/imagebox/Main.java index 8b1771c9..5935f2ba 100644 --- a/src/main/java/com/ebremer/halcyon/imagebox/Main.java +++ b/src/main/java/com/ebremer/halcyon/imagebox/Main.java @@ -172,8 +172,8 @@ public void addResourceHandlers(ResourceHandlerRegistry registry) { } private final String keyStorePassword = "changeit"; - private final String serverKeystore = "cacerts.p12"; - private final String serverTruststore = "cacerts.p12"; + private final String serverKeystore = "cacerts"; + private final String serverTruststore = "cacerts"; private final String trustStorePassword = "changeit"; public SSLContext getSSLContext() throws Exception { diff --git a/src/main/java/com/ebremer/halcyon/imagebox/Stack.java b/src/main/java/com/ebremer/halcyon/imagebox/Stack.java index c6baa89d..125a4b33 100644 --- a/src/main/java/com/ebremer/halcyon/imagebox/Stack.java +++ b/src/main/java/com/ebremer/halcyon/imagebox/Stack.java @@ -67,12 +67,11 @@ public Stack(IFormatReader reader) { } public int getBest(float r) { - //System.out.println("CALLING RATIO : "+r); int best = neolist.size()-1; - //System.out.println("STACK RATIO : "+r+" "+best+" "+ratio[best]+" "+index[best]); - while ((r0)) { + float rr = 0.8f*ratio[best]; + while ((r0)) { best--; - // System.out.println("STACK RATIO : "+r+" "+best+" "+ratio[best]+" "+index[best]); + rr = 0.8f*ratio[best]; } return index[best]; } diff --git a/src/main/resources/settings.ttl b/src/main/resources/settings.ttl index 791808dc..9bb47a6a 100644 --- a/src/main/resources/settings.ttl +++ b/src/main/resources/settings.ttl @@ -4,6 +4,7 @@ a :HalcyonSettingsFile; :RDFStoreLocation "TDB2"; :HostName "http://localhost:8888"; + :HostIP "0.0.0.0"; :HTTPPort 8888; :HTTPSPort 9999; :ProxyHostName "http://localhost:8888"; From c925adbee99dd842c3685799f832356f4feed730 Mon Sep 17 00:00:00 2001 From: Erich Bremer Date: Fri, 26 May 2023 11:16:09 -0400 Subject: [PATCH 3/8] wip --- .../java/com/ebremer/halcyon/gui/About.html | 6 +- .../ebremer/halcyon/imagebox/ImageServer.java | 79 ++++++++++--------- .../halcyon/wicket/FeatureManager.java | 13 +-- 3 files changed, 47 insertions(+), 51 deletions(-) diff --git a/src/main/java/com/ebremer/halcyon/gui/About.html b/src/main/java/com/ebremer/halcyon/gui/About.html index 6e7f49f9..13921ef5 100644 --- a/src/main/java/com/ebremer/halcyon/gui/About.html +++ b/src/main/java/com/ebremer/halcyon/gui/About.html @@ -12,17 +12,17 @@

Halcyon

Tammy DiPrima Programming - MultiViewer +   Joseph Balsamo Programming - Security Interfacing +   Feiqiao Wang Programming - Security Interfacing +   Jonas Almeida diff --git a/src/main/java/com/ebremer/halcyon/imagebox/ImageServer.java b/src/main/java/com/ebremer/halcyon/imagebox/ImageServer.java index c0a73be4..4da3bb01 100644 --- a/src/main/java/com/ebremer/halcyon/imagebox/ImageServer.java +++ b/src/main/java/com/ebremer/halcyon/imagebox/ImageServer.java @@ -19,7 +19,6 @@ import javax.imageio.ImageWriter; import javax.imageio.plugins.jpeg.JPEGImageWriteParam; import javax.imageio.stream.ImageOutputStream; -import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; @@ -36,7 +35,7 @@ public class ImageServer extends HttpServlet { Path fpath = Paths.get(System.getProperty("user.dir")+"/"+settings.getwebfiles()); @Override - protected void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException { + protected void doGet( HttpServletRequest request, HttpServletResponse response ) { String iiif = request.getParameter("iiif"); if (iiif!=null) { IIIFProcessor i = null; @@ -77,9 +76,12 @@ protected void doGet( HttpServletRequest request, HttpServletResponse response ) response.setContentType("application/json"); response.setHeader("Access-Control-Allow-Origin", "*"); response.setStatus(500); - PrintWriter writer=response.getWriter(); - writer.append(nt.GetImageInfo()); - writer.flush(); + try (PrintWriter writer=response.getWriter()) { + writer.append(nt.GetImageInfo()); + writer.flush(); + } catch (IOException ex) { + Logger.getLogger(ImageServer.class.getName()).log(Level.SEVERE, null, ex); + } } else if (i.tilerequest) { BufferedImage originalImage; if (i.fullrequest) { @@ -95,40 +97,40 @@ protected void doGet( HttpServletRequest request, HttpServletResponse response ) i.h = nt.GetHeight()-i.y; } } - //String fileType = target.substring(target.lastIndexOf('.') + 1); originalImage = nt.FetchImage(i.x, i.y, i.w, i.h, i.tx, i.tx); ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ServletOutputStream sos = response.getOutputStream(); - if (i.imageformat == ImageFormat.JPG) { - ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next(); - JPEGImageWriteParam jpegParams = new JPEGImageWriteParam(null); - jpegParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); - jpegParams.setCompressionQuality(0.7f); - ImageOutputStream imageOut = ImageIO.createImageOutputStream(baos); - writer.setOutput(imageOut); - writer.write(null,new IIOImage(originalImage,null,null),jpegParams); - baos.flush(); - byte[] imageInByte = baos.toByteArray(); - baos.close(); - response.setContentType("image/jpg"); - response.setContentLength(imageInByte.length); - response.setHeader("Access-Control-Allow-Origin", "*"); - sos.write(imageInByte); - sos.close(); - } else if (i.imageformat == ImageFormat.PNG) { - ImageWriter writer = ImageIO.getImageWritersByFormatName("png").next(); - ImageWriteParam pjpegParams = writer.getDefaultWriteParam(); - ImageOutputStream imageOut=ImageIO.createImageOutputStream(baos); - writer.setOutput(imageOut); - writer.write(null,new IIOImage(originalImage,null,null),pjpegParams); - baos.flush(); - byte[] imageInByte = baos.toByteArray(); - baos.close(); - response.setContentType("image/png"); - response.setContentLength(imageInByte.length); - response.setHeader("Access-Control-Allow-Origin", "*"); - sos.write(imageInByte); - sos.close(); + try (ServletOutputStream sos = response.getOutputStream()) { + if (i.imageformat == ImageFormat.JPG) { + ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next(); + JPEGImageWriteParam jpegParams = new JPEGImageWriteParam(null); + jpegParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + jpegParams.setCompressionQuality(0.7f); + ImageOutputStream imageOut = ImageIO.createImageOutputStream(baos); + writer.setOutput(imageOut); + writer.write(null,new IIOImage(originalImage,null,null),jpegParams); + baos.flush(); + byte[] imageInByte = baos.toByteArray(); + baos.close(); + response.setContentType("image/jpg"); + response.setContentLength(imageInByte.length); + response.setHeader("Access-Control-Allow-Origin", "*"); + sos.write(imageInByte); + } else if (i.imageformat == ImageFormat.PNG) { + ImageWriter writer = ImageIO.getImageWritersByFormatName("png").next(); + ImageWriteParam pjpegParams = writer.getDefaultWriteParam(); + ImageOutputStream imageOut=ImageIO.createImageOutputStream(baos); + writer.setOutput(imageOut); + writer.write(null,new IIOImage(originalImage,null,null),pjpegParams); + baos.flush(); + byte[] imageInByte = baos.toByteArray(); + baos.close(); + response.setContentType("image/png"); + response.setContentLength(imageInByte.length); + response.setHeader("Access-Control-Allow-Origin", "*"); + sos.write(imageInByte); + } + } catch (IOException ex) { + Logger.getLogger(ImageServer.class.getName()).log(Level.SEVERE, null, ex); } } else if (i.inforequest) { //System.out.println("IMAGE INFORMATION REQUEST : "+request.getQueryString()); @@ -139,6 +141,8 @@ protected void doGet( HttpServletRequest request, HttpServletResponse response ) try (PrintWriter writer = response.getWriter()) { writer.append(nt.GetImageInfo()); writer.flush(); + } catch (IOException ex) { + Logger.getLogger(ImageServer.class.getName()).log(Level.SEVERE, null, ex); } } else { System.out.println("unknown IIIF request"); @@ -147,7 +151,6 @@ protected void doGet( HttpServletRequest request, HttpServletResponse response ) } else { } - //System.out.println("DONE : "+time.getTime(getFullURL(request))); } public static String getFullURL(HttpServletRequest request) { diff --git a/src/main/java/com/ebremer/halcyon/wicket/FeatureManager.java b/src/main/java/com/ebremer/halcyon/wicket/FeatureManager.java index 243e5937..57ed6ba3 100644 --- a/src/main/java/com/ebremer/halcyon/wicket/FeatureManager.java +++ b/src/main/java/com/ebremer/halcyon/wicket/FeatureManager.java @@ -27,7 +27,6 @@ import java.util.Iterator; import java.util.logging.Level; import java.util.logging.Logger; -import org.apache.jena.graph.Graph; import org.apache.jena.query.Dataset; import org.apache.jena.query.DatasetFactory; import org.apache.jena.query.ParameterizedSparqlString; @@ -39,8 +38,6 @@ import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.rdf.model.RDFNode; import org.apache.jena.rdf.model.Resource; -import org.apache.jena.riot.Lang; -import org.apache.jena.riot.RDFDataMgr; import org.apache.jena.riot.system.JenaTitanium; import org.apache.jena.vocabulary.OWL; import org.apache.jena.vocabulary.RDF; @@ -61,7 +58,7 @@ public FeatureManager() { } public String getFeatures(HashSet features, String urn) { - System.out.println("getFeatures : "+urn); + //System.out.println("getFeatures : "+urn); // features.forEach(d->{ // System.out.println("YAH : "+d); //}); @@ -135,7 +132,7 @@ public String getFeatures(HashSet features, String urn) { pss.setNsPrefix("rdfs", RDFS.getURI()); pss.setNsPrefix("rdf", RDF.uri); pss.setValues("selected", roc); - System.out.println("#3 "+pss.toString()); + //System.out.println("#3 "+pss.toString()); rs = QueryExecutionFactory.create(pss.toString(), ds).execSelect(); String lroc = ""; Model m = ModelFactory.createDefaultModel(); @@ -268,10 +265,6 @@ public String getFeatures(HashSet features, String urn) { } catch (JsonLdError ex) { Logger.getLogger(FeatureManager.class.getName()).log(Level.SEVERE, null, ex); } - String fea = HFrame.wow(hold); - //System.out.println("===================================================== Features ======================================"); - //System.out.println(fea); - //System.out.println("==========XXXXXXXXXXXXXXXXXX======================== Features ============XXXXXXXXXXXXX=============="); - return fea; + return HFrame.wow(hold); } } From 22a696ca498d70a5d11e2c6f214467c6f4bcd1be Mon Sep 17 00:00:00 2001 From: Erich Bremer Date: Fri, 2 Jun 2023 08:46:08 -0400 Subject: [PATCH 4/8] wip --- dependency-reduced-pom.xml | 4 +- nbactions-ingestjar.xml | 6 +- pom.xml | 7 +- .../halcyon/filesystem/FileMetaExtractor.java | 74 ++++++++----------- 4 files changed, 38 insertions(+), 53 deletions(-) diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index d03886ae..83f619ad 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -793,7 +793,7 @@ com.ebremer BeakGraph - 0.3.2 + 0.4.0 provided @@ -926,7 +926,7 @@ 1.3.0 17 0.2.2 - 0.3.2 + 0.4.0 UTF-8 2.11.1 18.0.2 diff --git a/nbactions-ingestjar.xml b/nbactions-ingestjar.xml index f72fe7b4..36353766 100644 --- a/nbactions-ingestjar.xml +++ b/nbactions-ingestjar.xml @@ -14,7 +14,7 @@ ${exec.vmArgs} -classpath %classpath ${exec.mainClass} ${exec.appArgs} java -Xmx30G -Xms30G --add-opens=java.base/java.nio=ALL-UNNAMED --enable-preview -Darrow.memory.debug.allocator=true - -v -src "D:\halcyon\features\src2" -dest "D:\halcyon\features\dest" + -v -src "D:\halcyon\features\src3" -dest "D:\halcyon\features\dest5" com.ebremer.halcyon.converters.Ingest @@ -33,7 +33,7 @@ java true -Xmx30G -Xms30G --add-opens=java.base/java.nio=ALL-UNNAMED --enable-preview -Darrow.memory.debug.allocator=true -agentlib:jdwp=transport=dt_socket,server=n,address=${jpda.address} - -v -src "D:\halcyon\features\src2" -dest "D:\halcyon\features\dest" + -v -src "D:\halcyon\features\src3" -dest "D:\halcyon\features\dest5" com.ebremer.halcyon.converters.Ingest @@ -51,7 +51,7 @@ ${exec.vmArgs} -classpath %classpath ${exec.mainClass} ${exec.appArgs} java -Xmx30G -Xms30G --add-opens=java.base/java.nio=ALL-UNNAMED --enable-preview -Darrow.memory.debug.allocator=true - -v -src "D:\halcyon\features\src2" -dest "D:\halcyon\features\dest" + -v -src "D:\halcyon\features\src3" -dest "D:\halcyon\features\dest5" com.ebremer.halcyon.converters.Ingest diff --git a/pom.xml b/pom.xml index 49c88a94..93b6b254 100644 --- a/pom.xml +++ b/pom.xml @@ -80,12 +80,12 @@ 0.2.2 2.11.1 2.0.1 - 1.3.0 + 1.3.2 5.29.2 4.7.4.Final 13.0.12.Final 4.2.21.Final - 0.3.2 + 0.4.0 @@ -317,11 +317,12 @@ org.springframework.boot spring-boot-starter-undertow + diff --git a/src/main/java/com/ebremer/halcyon/filesystem/FileMetaExtractor.java b/src/main/java/com/ebremer/halcyon/filesystem/FileMetaExtractor.java index e894ab52..3809c1d8 100644 --- a/src/main/java/com/ebremer/halcyon/filesystem/FileMetaExtractor.java +++ b/src/main/java/com/ebremer/halcyon/filesystem/FileMetaExtractor.java @@ -2,7 +2,6 @@ import com.ebremer.halcyon.HalcyonSettings; import com.ebremer.halcyon.datum.EB; -import com.ebremer.ns.HAL; import com.ebremer.ns.LOC; import com.ebremer.halcyon.utils.Hash; import com.ebremer.ns.EXIF; @@ -23,9 +22,6 @@ import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.rdf.model.Resource; -import org.apache.jena.riot.Lang; -import org.apache.jena.riot.RDFDataMgr; -import org.apache.jena.riot.RDFFormat; import org.apache.jena.update.UpdateAction; import org.apache.jena.update.UpdateFactory; import org.apache.jena.update.UpdateRequest; @@ -70,50 +66,38 @@ public Model getCoreMeta() { private void Process() { String f = file.toString(); String extension = f.substring(f.lastIndexOf(".")+1).toLowerCase(); - //CalculateMD5(); switch (extension) { case "zip": Model z; - try { - String fixb = EB.fix(file); - String fixe = EB.fix(file)+"/"; - System.out.println(fixb+" ----> "+fixe); - //ROCrate roc = new ROCrate(file); - //ROCrateReader roc = new ROCrateReader(fixb, file.toURI()); - ROCrateReader roc = new ROCrateReader(file.toURI()); - //z = roc.getManifest(fixe); - z = roc.getManifest(); - //System.out.println("LOADED : "+z.size()); - //System.out.println("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"); - //RDFDataMgr.write(System.out, z, RDFFormat.TURTLE_PRETTY); - //System.out.println("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"); - UpdateRequest update = UpdateFactory.create(); - ParameterizedSparqlString pss = new ParameterizedSparqlString(""" - delete {?s ?p ?o} - insert {?neo ?p ?o} - where { - ?s ?p ?o - } - """); - pss.setIri("s", fixe); - pss.setIri("neo", fixb); - update.add(pss.toString()); - pss = new ParameterizedSparqlString(""" - delete {?s ?p ?neo} - insert {?s ?p ?o} - where { - ?s ?p ?o - } - """); - pss.setIri("o", fixe); - pss.setIri("neo", fixb); - update.add(pss.toString()); - UpdateAction.execute(update, z); - m.add(z); - //System.out.println("Extracted ========================================================\n"); - //RDFDataMgr.write(System.out, m, Lang.TURTLE); - //System.out.println("Extracted END END END ============================================"); - // roc.close(); + String fixb = EB.fix(file); + String fixe = EB.fix(file)+"/"; + try (ROCrateReader roc = new ROCrateReader(file.toURI())) { + if (roc.hasManifest()) { + z = roc.getManifest(); + UpdateRequest update = UpdateFactory.create(); + ParameterizedSparqlString pss = new ParameterizedSparqlString(""" + delete {?s ?p ?o} + insert {?neo ?p ?o} + where { + ?s ?p ?o + } + """); + pss.setIri("s", fixe); + pss.setIri("neo", fixb); + update.add(pss.toString()); + pss = new ParameterizedSparqlString(""" + delete {?s ?p ?neo} + insert {?s ?p ?o} + where { + ?s ?p ?o + } + """); + pss.setIri("o", fixe); + pss.setIri("neo", fixb); + update.add(pss.toString()); + UpdateAction.execute(update, z); + m.add(z); + } } catch (IOException ex) { Logger.getLogger(FileMetaExtractor.class.getName()).log(Level.SEVERE, null, ex); } From f3549561a4186175f91ba3ac57c0f8358cbf946b Mon Sep 17 00:00:00 2001 From: Erich Bremer Date: Mon, 5 Jun 2023 17:05:26 -0400 Subject: [PATCH 5/8] bump Keycloak 18.0.2 -> 21.1.1 --- .gitignore | 2 + nbactions-server.xml | 6 +- nbactions.xml | 6 +- pom.xml | 351 +-- realm-export.json | 2282 ----------------- ...beddedSpringKeycloakAutoConfiguration.java | 14 - .../com/ebremer/halcyon/HalcyonSettings.java | 4 +- src/main/java/com/ebremer/halcyon/INIT.java | 2 +- .../halcyon/fuseki/shiro/JwtVerifier.java | 22 +- .../shiro/KeycloakPublicKeyFetcher.java | 18 +- .../ebremer/halcyon/gui/HalcyonSession.java | 3 +- .../halcyon/{imagebox => server}/Main.java | 41 +- .../server/controllers/MainController.java | 21 + .../ebremer/halcyon/server/keycloak/App.java | 78 + .../halcyon/server/keycloak/Config.java | 89 + .../server/keycloak/RequestFilter.java | 59 + .../server/keycloak/ServerProperties.java | 6 + .../providers/JsonProviderFactory.java | 34 + .../providers/SimplePlatformProvider.java | 75 + .../server/resteasy/Resteasy4Provider.java | 32 + .../ebremer/halcyon/wicket/AccountPage.java | 3 +- .../com/ebremer/halcyon/wicket/MenuPanel.java | 1 + ...itional-spring-configuration-metadata.json | 22 + .../resources/META-INF/keycloak-server.json | 330 +++ .../resources/META-INF/keycloak-themes.json | 10 + .../org.keycloak.common.util.ResteasyProvider | 1 + .../org.keycloak.config.ConfigProviderFactory | 1 + .../org.keycloak.platform.PlatformProvider | 1 + src/main/resources/META-INF/spring.factories | 2 - src/main/resources/application.yml | 181 +- src/main/resources/infinispan.xml | 46 - src/main/resources/jgroups.xml | 61 - src/main/resources/jgroups_dns.xml | 62 - src/main/resources/keycloak - Copy.json | 10 + src/main/resources/keycloak-realm-config.json | 1240 ++++----- src/main/resources/keycloak.json | 6 +- src/main/resources/profile.properties | 16 - src/main/resources/theme/README.md | 24 + .../theme/providers-only/login/login.ftl | 25 + .../login/resources/css/login.css | 595 +++++ .../img/feedback-error-arrow-down.png | Bin 0 -> 513 bytes .../resources/img/feedback-error-sign.png | Bin 0 -> 343 bytes .../img/feedback-success-arrow-down.png | Bin 0 -> 678 bytes .../resources/img/feedback-success-sign.png | Bin 0 -> 410 bytes .../img/feedback-warning-arrow-down.png | Bin 0 -> 513 bytes .../resources/img/feedback-warning-sign.png | Bin 0 -> 646 bytes .../login/resources/img/keycloak-bg.png | Bin 0 -> 81862 bytes .../resources/img/keycloak-logo-text.png | Bin 0 -> 19994 bytes .../login/resources/img/keycloak-logo.png | Bin 0 -> 5281 bytes .../providers-only/login/theme.properties | 161 ++ 50 files changed, 2433 insertions(+), 3510 deletions(-) delete mode 100644 realm-export.json delete mode 100644 src/main/java/com/ebremer/halcyon/EmbeddedSpringKeycloakAutoConfiguration.java rename src/main/java/com/ebremer/halcyon/{imagebox => server}/Main.java (96%) create mode 100644 src/main/java/com/ebremer/halcyon/server/controllers/MainController.java create mode 100644 src/main/java/com/ebremer/halcyon/server/keycloak/App.java create mode 100644 src/main/java/com/ebremer/halcyon/server/keycloak/Config.java create mode 100644 src/main/java/com/ebremer/halcyon/server/keycloak/RequestFilter.java create mode 100644 src/main/java/com/ebremer/halcyon/server/keycloak/ServerProperties.java create mode 100644 src/main/java/com/ebremer/halcyon/server/keycloak/providers/JsonProviderFactory.java create mode 100644 src/main/java/com/ebremer/halcyon/server/keycloak/providers/SimplePlatformProvider.java create mode 100644 src/main/java/com/ebremer/halcyon/server/resteasy/Resteasy4Provider.java create mode 100644 src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 src/main/resources/META-INF/keycloak-server.json create mode 100644 src/main/resources/META-INF/keycloak-themes.json create mode 100644 src/main/resources/META-INF/services/org.keycloak.common.util.ResteasyProvider create mode 100644 src/main/resources/META-INF/services/org.keycloak.config.ConfigProviderFactory create mode 100644 src/main/resources/META-INF/services/org.keycloak.platform.PlatformProvider delete mode 100644 src/main/resources/META-INF/spring.factories delete mode 100644 src/main/resources/infinispan.xml delete mode 100644 src/main/resources/jgroups.xml delete mode 100644 src/main/resources/jgroups_dns.xml create mode 100644 src/main/resources/keycloak - Copy.json delete mode 100644 src/main/resources/profile.properties create mode 100644 src/main/resources/theme/README.md create mode 100644 src/main/resources/theme/providers-only/login/login.ftl create mode 100644 src/main/resources/theme/providers-only/login/resources/css/login.css create mode 100644 src/main/resources/theme/providers-only/login/resources/img/feedback-error-arrow-down.png create mode 100644 src/main/resources/theme/providers-only/login/resources/img/feedback-error-sign.png create mode 100644 src/main/resources/theme/providers-only/login/resources/img/feedback-success-arrow-down.png create mode 100644 src/main/resources/theme/providers-only/login/resources/img/feedback-success-sign.png create mode 100644 src/main/resources/theme/providers-only/login/resources/img/feedback-warning-arrow-down.png create mode 100644 src/main/resources/theme/providers-only/login/resources/img/feedback-warning-sign.png create mode 100644 src/main/resources/theme/providers-only/login/resources/img/keycloak-bg.png create mode 100644 src/main/resources/theme/providers-only/login/resources/img/keycloak-logo-text.png create mode 100644 src/main/resources/theme/providers-only/login/resources/img/keycloak-logo.png create mode 100644 src/main/resources/theme/providers-only/login/theme.properties diff --git a/.gitignore b/.gitignore index 7731dd66..d1118175 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Halcyon specific files /*.ttl /keycloak.json +/keycloak-realm-config.json # Compiled class file *.class @@ -35,4 +36,5 @@ hs_err_pid* target/* data/* dist/* +tdb2/* diff --git a/nbactions-server.xml b/nbactions-server.xml index 72f751ad..5237b5b8 100644 --- a/nbactions-server.xml +++ b/nbactions-server.xml @@ -15,7 +15,7 @@ java -Xmx45G -Xms45G --add-opens java.base/java.nio=ALL-UNNAMED --enable-preview - com.ebremer.halcyon.imagebox.Main + com.ebremer.halcyon.server.Main @@ -34,7 +34,7 @@ true -Xmx45G -Xms45G --add-opens java.base/java.nio=ALL-UNNAMED --enable-preview -agentlib:jdwp=transport=dt_socket,server=n,address=${jpda.address} - com.ebremer.halcyon.imagebox.Main + com.ebremer.halcyon.server.Main @@ -51,7 +51,7 @@ java -Xmx45G -Xms45G --add-opens java.base/java.nio=ALL-UNNAMED --enable-preview - com.ebremer.halcyon.imagebox.Main + com.ebremer.halcyon.server.Main diff --git a/nbactions.xml b/nbactions.xml index 29e56ce4..7a2acc89 100644 --- a/nbactions.xml +++ b/nbactions.xml @@ -15,7 +15,7 @@ java -Xmx50G -Xms50G --add-opens=java.base/java.nio=ALL-UNNAMED - is.halcyon.imagebox.Main + com.ebremer.halcyon.imagebox.Main @@ -34,7 +34,7 @@ true -Xmx50G -Xms50G --add-opens=java.base/java.nio=ALL-UNNAMED -agentlib:jdwp=transport=dt_socket,server=n,address=${jpda.address} - is.halcyon.imagebox.Main + com.ebremer.halcyon.imagebox.Main @@ -52,7 +52,7 @@ java -Xmx50G -Xms50G --add-opens=java.base/java.nio=ALL-UNNAMED - is.halcyon.imagebox.Main + com.ebremer.halcyon.imagebox.Main diff --git a/pom.xml b/pom.xml index 93b6b254..9e82fd9c 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.ebremer Halcyon - 0.4.2 + 0.5.0 jar Halcyon A whole slide image annotation, management, and visualization system @@ -74,7 +74,6 @@ 17 4.8.0 9.13.0 - 18.0.2 8.0.0 6.13.0 0.2.2 @@ -82,90 +81,23 @@ 2.0.1 1.3.2 5.29.2 - 4.7.4.Final - 13.0.12.Final - 4.2.21.Final 0.4.0 + 21.1.1 + 14.0.8.Final + 4.7.7.Final + 1.10.3 + 4.20.0 + 2.0 org.springframework.boot spring-boot-starter-parent - 2.6.13 + 2.7.11 - - org.jboss.xnio - xnio-api - 3.8.7.Final - - - org.keycloak - keycloak-core - ${keycloak.ver} - - - org.keycloak - keycloak-services - ${keycloak.ver} - - - org.keycloak - keycloak-saml-core - ${keycloak.ver} - - - org.keycloak - keycloak-server-api - ${keycloak.ver} - - - org.keycloak - keycloak-ldap-federation - ${keycloak.ver} - - - org.keycloak - keycloak-kerberos-federation - ${keycloak.ver} - - - org.keycloak - keycloak-authz-policy-common - ${keycloak.ver} - - - org.keycloak - keycloak-model-infinispan - ${keycloak.ver} - - - org.keycloak - keycloak-js-adapter - ${keycloak.ver} - - - org.keycloak - keycloak-model-spi - ${keycloak.ver} - - - org.keycloak - keycloak-server-spi - ${keycloak.ver} - - - org.keycloak - keycloak-themes - ${keycloak.ver} - - - org.jgroups - jgroups - 4.2.21.Final - @@ -256,7 +188,7 @@ org.springframework.boot spring-boot-maven-plugin - com.ebremer.halcyon.imagebox.Main + com.ebremer.halcyon.server.Main @@ -303,6 +235,14 @@ + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-data-jpa + org.springframework.boot spring-boot-starter-web @@ -317,12 +257,104 @@ org.springframework.boot spring-boot-starter-undertow - + com.h2database + h2 + runtime + + + io.micrometer + micrometer-registry-prometheus + ${micrometer.version} + + + org.projectlombok + lombok + true + + + org.jboss.resteasy + resteasy-jackson2-provider + ${resteasy.version} + + + org.keycloak + keycloak-dependencies-server-all + ${keycloak.version} + pom + + + org.slf4j + slf4j-log4j12 + + + log4j + log4j + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.keycloak + keycloak-crypto-default + ${keycloak.version} + + + org.keycloak + keycloak-admin-ui + ${keycloak.version} + + + * + * + + + + + org.keycloak + keycloak-rest-admin-ui-ext + ${keycloak.version} + + + org.liquibase + liquibase-core + ${liquibase.version} + + + org.yaml + snakeyaml + + + org.apache.commons + commons-text + + + + + org.yaml + snakeyaml + ${snakeyaml.version} + + + org.jboss.resteasy + resteasy-client + ${resteasy.version} + + + org.keycloak + keycloak-servlet-filter-adapter + ${keycloak.version} + jar + + + org.mitre.dsmiley.httpproxy + smiley-http-proxy-servlet + 1.12.1 + @@ -626,30 +658,10 @@ - - com.github.thomasdarimont.embedded-spring-boot-keycloak-server - embedded-keycloak-server-spring-boot-starter - ${embeddedkeycloak.ver} - - - log4j-api - log4j - - - log4j-to-slf4j - log4j - - - jakarta.json jakarta.json-api - 2.1.0 - - - org.glassfish - jakarta.json - 2.0.1 + 2.1.1 org.eclipse.parsson @@ -659,7 +671,7 @@ com.apicatalog titanium-json-ld - 1.3.0 + ${titanium-json-ld.ver} jar @@ -745,12 +757,6 @@ jena-fuseki-main ${jena.version} - com.github.davidmoten @@ -764,108 +770,6 @@ ${bioformats.ver} compile - - org.keycloak - keycloak-servlet-filter-adapter - ${keycloak.ver} - jar - - - org.keycloak - keycloak-model-jpa - ${keycloak.ver} - jar - - - org.mitre.dsmiley.httpproxy - smiley-http-proxy-servlet - 1.12.1 - - - org.jboss.resteasy - resteasy-servlet-initializer - ${resteasy.version} - - - org.jboss.resteasy - resteasy-jackson2-provider - ${resteasy.version} - - - org.jboss.resteasy - resteasy-jaxb-provider - ${resteasy.version} - - - org.jboss.resteasy - resteasy-multipart-provider - ${resteasy.version} - - - org.jboss.resteasy - resteasy-client - ${resteasy.version} - - - org.infinispan - infinispan-query-dsl - - - org.jgroups - jgroups - - - io.netty - * - - - - - com.ebremer - BeakGraph - ${beakgraph.version} - - org.apache.shiro @@ -877,14 +781,6 @@ shiro-web 1.11.0 - com.jcabi jcabi-aspects @@ -901,7 +797,7 @@ io.jsonwebtoken jjwt-impl 0.11.5 - runtime + io.jsonwebtoken @@ -909,19 +805,18 @@ 0.11.5 runtime - com.beust jcommander 1.82 + + com.ebremer + BeakGraph + ${beakgraph.version} + + spring-release diff --git a/realm-export.json b/realm-export.json deleted file mode 100644 index 50974d3a..00000000 --- a/realm-export.json +++ /dev/null @@ -1,2282 +0,0 @@ -{ - "id": "master", - "realm": "master", - "displayName": "Halcyon", - "displayNameHtml": "H A L C Y O N", - "notBefore": 1680204939, - "defaultSignatureAlgorithm": "RS256", - "revokeRefreshToken": false, - "refreshTokenMaxReuse": 0, - "accessTokenLifespan": 60, - "accessTokenLifespanForImplicitFlow": 900, - "ssoSessionIdleTimeout": 86400, - "ssoSessionMaxLifespan": 604800, - "ssoSessionIdleTimeoutRememberMe": 0, - "ssoSessionMaxLifespanRememberMe": 0, - "offlineSessionIdleTimeout": 2592000, - "offlineSessionMaxLifespanEnabled": false, - "offlineSessionMaxLifespan": 5184000, - "clientSessionIdleTimeout": 0, - "clientSessionMaxLifespan": 0, - "clientOfflineSessionIdleTimeout": 0, - "clientOfflineSessionMaxLifespan": 0, - "accessCodeLifespan": 60, - "accessCodeLifespanUserAction": 300, - "accessCodeLifespanLogin": 1800, - "actionTokenGeneratedByAdminLifespan": 43200, - "actionTokenGeneratedByUserLifespan": 300, - "oauth2DeviceCodeLifespan": 600, - "oauth2DevicePollingInterval": 5, - "enabled": true, - "sslRequired": "none", - "registrationAllowed": false, - "registrationEmailAsUsername": true, - "rememberMe": false, - "verifyEmail": false, - "loginWithEmailAllowed": true, - "duplicateEmailsAllowed": false, - "resetPasswordAllowed": false, - "editUsernameAllowed": false, - "bruteForceProtected": false, - "permanentLockout": false, - "maxFailureWaitSeconds": 900, - "minimumQuickLoginWaitSeconds": 60, - "waitIncrementSeconds": 60, - "quickLoginCheckMilliSeconds": 1000, - "maxDeltaTimeSeconds": 43200, - "failureFactor": 30, - "roles": { - "realm": [ - { - "id": "db43a81e-55f0-495f-bb10-b2897886e6c1", - "name": "default-roles-master", - "description": "${role_default-roles}", - "composite": true, - "composites": { - "realm": [ - "offline_access", - "uma_authorization" - ], - "client": { - "account": [ - "manage-account", - "view-profile" - ] - } - }, - "clientRole": false, - "containerId": "master", - "attributes": {} - }, - { - "id": "ba948bb5-7632-4efc-bf3f-840474d00fd5", - "name": "create-realm", - "description": "${role_create-realm}", - "composite": false, - "clientRole": false, - "containerId": "master", - "attributes": {} - }, - { - "id": "a7d3d99d-e84c-4116-a334-c9608bc76d85", - "name": "offline_access", - "description": "${role_offline-access}", - "composite": false, - "clientRole": false, - "containerId": "master", - "attributes": {} - }, - { - "id": "9eb710dd-4d98-40e0-ad73-162102401e4b", - "name": "admin", - "description": "${role_admin}", - "composite": true, - "composites": { - "realm": [ - "create-realm" - ], - "client": { - "master-realm": [ - "view-clients", - "manage-events", - "view-realm", - "query-clients", - "manage-realm", - "manage-identity-providers", - "view-identity-providers", - "manage-users", - "impersonation", - "view-authorization", - "query-realms", - "view-events", - "manage-authorization", - "query-groups", - "create-client", - "view-users", - "manage-clients", - "query-users" - ] - } - }, - "clientRole": false, - "containerId": "master", - "attributes": {} - }, - { - "id": "1d1d6634-162d-4e1f-9120-d31b7adae4d3", - "name": "uma_authorization", - "description": "${role_uma_authorization}", - "composite": false, - "clientRole": false, - "containerId": "master", - "attributes": {} - } - ], - "client": { - "security-admin-console": [], - "admin-cli": [], - "account-console": [], - "broker": [ - { - "id": "ccc1d2f3-9a90-47dc-8821-939ac7aa9917", - "name": "read-token", - "description": "${role_read-token}", - "composite": false, - "clientRole": true, - "containerId": "add69182-4588-4087-a1e6-0d9a59d8d81c", - "attributes": {} - } - ], - "master-realm": [ - { - "id": "4d681622-fb1b-448c-a14f-757885ae177c", - "name": "view-clients", - "description": "${role_view-clients}", - "composite": true, - "composites": { - "client": { - "master-realm": [ - "query-clients" - ] - } - }, - "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", - "attributes": {} - }, - { - "id": "18d8b8d6-4b9c-4e62-8a9f-7cbb74b59e3c", - "name": "manage-events", - "description": "${role_manage-events}", - "composite": false, - "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", - "attributes": {} - }, - { - "id": "04ecb1b3-b44b-44d5-bcff-62b19a285e6c", - "name": "view-realm", - "description": "${role_view-realm}", - "composite": false, - "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", - "attributes": {} - }, - { - "id": "97c45eba-c008-4a4a-800d-6d36f3f17b5c", - "name": "query-clients", - "description": "${role_query-clients}", - "composite": false, - "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", - "attributes": {} - }, - { - "id": "8790557a-5269-4177-b8d7-2961e64e9e3c", - "name": "manage-realm", - "description": "${role_manage-realm}", - "composite": false, - "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", - "attributes": {} - }, - { - "id": "559dc49f-eed9-4d70-a12b-88ce32745933", - "name": "manage-identity-providers", - "description": "${role_manage-identity-providers}", - "composite": false, - "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", - "attributes": {} - }, - { - "id": "46074624-f2e9-4e7f-b3b2-04636bfb93fb", - "name": "view-identity-providers", - "description": "${role_view-identity-providers}", - "composite": false, - "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", - "attributes": {} - }, - { - "id": "21ea52fd-3c7d-460c-b3ce-c0360caf7c52", - "name": "manage-users", - "description": "${role_manage-users}", - "composite": false, - "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", - "attributes": {} - }, - { - "id": "791dcc5f-9aae-41fa-a36b-b3c7bcb175a6", - "name": "impersonation", - "description": "${role_impersonation}", - "composite": false, - "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", - "attributes": {} - }, - { - "id": "aad4a9a5-00d5-4de3-894f-e9b5ad99bc28", - "name": "query-realms", - "description": "${role_query-realms}", - "composite": false, - "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", - "attributes": {} - }, - { - "id": "54b94dcf-b2ac-4a3d-97f8-9b50f8326fc6", - "name": "view-authorization", - "description": "${role_view-authorization}", - "composite": false, - "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", - "attributes": {} - }, - { - "id": "dfdd2271-3739-41d7-b088-8954bf0b91ed", - "name": "view-events", - "description": "${role_view-events}", - "composite": false, - "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", - "attributes": {} - }, - { - "id": "cbc59c22-d07b-4606-8406-c75b63b32413", - "name": "manage-authorization", - "description": "${role_manage-authorization}", - "composite": false, - "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", - "attributes": {} - }, - { - "id": "d1374727-f085-41a1-8ae8-f675f5c0fcf5", - "name": "query-groups", - "description": "${role_query-groups}", - "composite": false, - "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", - "attributes": {} - }, - { - "id": "1afc8117-cf6c-4e35-8427-ba40fae6431d", - "name": "create-client", - "description": "${role_create-client}", - "composite": false, - "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", - "attributes": {} - }, - { - "id": "87ca302d-9010-402a-a4d2-1d31c0e19613", - "name": "view-users", - "description": "${role_view-users}", - "composite": true, - "composites": { - "client": { - "master-realm": [ - "query-groups", - "query-users" - ] - } - }, - "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", - "attributes": {} - }, - { - "id": "28977c95-a4c1-4400-a828-36c626026298", - "name": "manage-clients", - "description": "${role_manage-clients}", - "composite": false, - "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", - "attributes": {} - }, - { - "id": "bc60194d-1c23-4a94-bb55-738d7389faab", - "name": "query-users", - "description": "${role_query-users}", - "composite": false, - "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", - "attributes": {} - } - ], - "account": [ - { - "id": "d1c18f80-771f-4df2-ade9-d70e75e32ecf", - "name": "manage-account", - "description": "${role_manage-account}", - "composite": true, - "composites": { - "client": { - "account": [ - "manage-account-links" - ] - } - }, - "clientRole": true, - "containerId": "fa64a2d4-66d2-4ff6-af84-d59ede0320c0", - "attributes": {} - }, - { - "id": "e057f536-3ca1-4c91-bc28-4a2f77bcedcb", - "name": "manage-consent", - "description": "${role_manage-consent}", - "composite": true, - "composites": { - "client": { - "account": [ - "view-consent" - ] - } - }, - "clientRole": true, - "containerId": "fa64a2d4-66d2-4ff6-af84-d59ede0320c0", - "attributes": {} - }, - { - "id": "7d6c2511-1e91-41d3-bcd0-51f079dca2ee", - "name": "delete-account", - "description": "${role_delete-account}", - "composite": false, - "clientRole": true, - "containerId": "fa64a2d4-66d2-4ff6-af84-d59ede0320c0", - "attributes": {} - }, - { - "id": "69bff3e2-eae4-4b03-a400-89a1f99b4094", - "name": "view-applications", - "description": "${role_view-applications}", - "composite": false, - "clientRole": true, - "containerId": "fa64a2d4-66d2-4ff6-af84-d59ede0320c0", - "attributes": {} - }, - { - "id": "75f420a7-e7b3-48a0-a1bd-08948be9867c", - "name": "view-consent", - "description": "${role_view-consent}", - "composite": false, - "clientRole": true, - "containerId": "fa64a2d4-66d2-4ff6-af84-d59ede0320c0", - "attributes": {} - }, - { - "id": "d003da96-499c-400b-9d95-c6d3b29f0f76", - "name": "manage-account-links", - "description": "${role_manage-account-links}", - "composite": false, - "clientRole": true, - "containerId": "fa64a2d4-66d2-4ff6-af84-d59ede0320c0", - "attributes": {} - }, - { - "id": "532cc3ac-1590-474c-a303-1738cd33ecaa", - "name": "view-profile", - "description": "${role_view-profile}", - "composite": false, - "clientRole": true, - "containerId": "fa64a2d4-66d2-4ff6-af84-d59ede0320c0", - "attributes": {} - } - ] - } - }, - "groups": [ - { - "id": "2c20a6b5-6551-4491-8ba8-727306bc46eb", - "name": "Bremer Lab", - "path": "/Bremer Lab", - "attributes": {}, - "realmRoles": [], - "clientRoles": {}, - "subGroups": [] - }, - { - "id": "90679840-5e71-4256-afde-cdb708e6c428", - "name": "admin", - "path": "/admin", - "attributes": {}, - "realmRoles": [], - "clientRoles": {}, - "subGroups": [] - } - ], - "defaultRole": { - "id": "db43a81e-55f0-495f-bb10-b2897886e6c1", - "name": "default-roles-master", - "description": "${role_default-roles}", - "composite": true, - "clientRole": false, - "containerId": "master" - }, - "requiredCredentials": [ - "password" - ], - "otpPolicyType": "totp", - "otpPolicyAlgorithm": "HmacSHA1", - "otpPolicyInitialCounter": 0, - "otpPolicyDigits": 6, - "otpPolicyLookAheadWindow": 1, - "otpPolicyPeriod": 30, - "otpSupportedApplications": [ - "FreeOTP", - "Google Authenticator" - ], - "webAuthnPolicyRpEntityName": "keycloak", - "webAuthnPolicySignatureAlgorithms": [ - "ES256" - ], - "webAuthnPolicyRpId": "", - "webAuthnPolicyAttestationConveyancePreference": "not specified", - "webAuthnPolicyAuthenticatorAttachment": "not specified", - "webAuthnPolicyRequireResidentKey": "not specified", - "webAuthnPolicyUserVerificationRequirement": "not specified", - "webAuthnPolicyCreateTimeout": 0, - "webAuthnPolicyAvoidSameAuthenticatorRegister": false, - "webAuthnPolicyAcceptableAaguids": [], - "webAuthnPolicyPasswordlessRpEntityName": "keycloak", - "webAuthnPolicyPasswordlessSignatureAlgorithms": [ - "ES256" - ], - "webAuthnPolicyPasswordlessRpId": "", - "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", - "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", - "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", - "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", - "webAuthnPolicyPasswordlessCreateTimeout": 0, - "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, - "webAuthnPolicyPasswordlessAcceptableAaguids": [], - "scopeMappings": [ - { - "clientScope": "offline_access", - "roles": [ - "offline_access" - ] - } - ], - "clientScopeMappings": { - "account": [ - { - "client": "account-console", - "roles": [ - "manage-account" - ] - } - ] - }, - "clients": [ - { - "id": "fa64a2d4-66d2-4ff6-af84-d59ede0320c0", - "clientId": "account", - "name": "${client_account}", - "rootUrl": "${authBaseUrl}", - "baseUrl": "/realms/master/account/", - "surrogateAuthRequired": false, - "enabled": true, - "alwaysDisplayInConsole": false, - "clientAuthenticatorType": "client-secret", - "redirectUris": [ - "*" - ], - "webOrigins": [], - "notBefore": 0, - "bearerOnly": false, - "consentRequired": false, - "standardFlowEnabled": true, - "implicitFlowEnabled": false, - "directAccessGrantsEnabled": true, - "serviceAccountsEnabled": false, - "publicClient": true, - "frontchannelLogout": false, - "protocol": "openid-connect", - "attributes": { - "saml.multivalued.roles": "false", - "saml.force.post.binding": "false", - "post.logout.redirect.uris": "+", - "frontchannel.logout.session.required": "false", - "oauth2.device.authorization.grant.enabled": "false", - "backchannel.logout.revoke.offline.tokens": "false", - "saml.server.signature.keyinfo.ext": "false", - "use.refresh.tokens": "true", - "oidc.ciba.grant.enabled": "false", - "backchannel.logout.session.required": "false", - "client_credentials.use_refresh_token": "false", - "saml.client.signature": "false", - "require.pushed.authorization.requests": "false", - "saml.allow.ecp.flow": "false", - "saml.assertion.signature": "false", - "id.token.as.detached.signature": "false", - "saml.encrypt": "false", - "saml.server.signature": "false", - "exclude.session.state.from.auth.response": "false", - "saml.artifact.binding": "false", - "saml_force_name_id_format": "false", - "tls.client.certificate.bound.access.tokens": "false", - "acr.loa.map": "{}", - "saml.authnstatement": "false", - "display.on.consent.screen": "false", - "token.response.type.bearer.lower-case": "false", - "saml.onetimeuse.condition": "false" - }, - "authenticationFlowBindingOverrides": {}, - "fullScopeAllowed": true, - "nodeReRegistrationTimeout": 0, - "protocolMappers": [ - { - "id": "7537de68-443f-43bc-94e4-46164093aed7", - "name": "email", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": false, - "config": { - "userinfo.token.claim": "true", - "user.attribute": "email", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "email", - "jsonType.label": "String" - } - }, - { - "id": "097b836d-74c8-4c32-a550-035ae515c2f4", - "name": "webid", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "userinfo.token.claim": "true", - "multivalued": "false", - "user.attribute": "webid", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "webid", - "jsonType.label": "String" - } - }, - { - "id": "ccca6b2b-0b2d-48db-b290-15da39215c45", - "name": "memberOf", - "protocol": "openid-connect", - "protocolMapper": "oidc-group-membership-mapper", - "consentRequired": false, - "config": { - "full.path": "true", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "HalcyonGroups", - "userinfo.token.claim": "true" - } - }, - { - "id": "adeac42c-ce72-47f2-8ab9-50d9c674fe0a", - "name": "username", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": false, - "config": { - "userinfo.token.claim": "true", - "user.attribute": "username", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "preferred_username", - "jsonType.label": "String" - } - }, - { - "id": "62eab4b3-358a-423e-a898-9cfa660c2d1a", - "name": "groupwebid", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "aggregate.attrs": "true", - "userinfo.token.claim": "true", - "multivalued": "true", - "user.attribute": "groupwebid", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "groupwebid", - "jsonType.label": "String" - } - }, - { - "id": "8c58c860-8cf7-4a6f-89e9-f98f1745d9da", - "name": "client roles", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-client-role-mapper", - "consentRequired": false, - "config": { - "user.attribute": "foo", - "access.token.claim": "true", - "claim.name": "resource_access.${client_id}.roles", - "jsonType.label": "String", - "multivalued": "true" - } - }, - { - "id": "43549103-9c75-40ae-938c-a690f7a26bce", - "name": "groups", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-realm-role-mapper", - "consentRequired": false, - "config": { - "multivalued": "true", - "userinfo.token.claim": "true", - "user.attribute": "foo", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "groups", - "jsonType.label": "String" - } - } - ], - "defaultClientScopes": [ - "web-origins", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] - }, - { - "id": "43443219-b1ba-4bda-8295-4f0d21bf030a", - "clientId": "account-console", - "name": "${client_account-console}", - "rootUrl": "${authBaseUrl}", - "baseUrl": "/realms/master/account/", - "surrogateAuthRequired": false, - "enabled": true, - "alwaysDisplayInConsole": false, - "clientAuthenticatorType": "client-secret", - "redirectUris": [ - "/realms/master/account/*" - ], - "webOrigins": [], - "notBefore": 0, - "bearerOnly": false, - "consentRequired": false, - "standardFlowEnabled": true, - "implicitFlowEnabled": false, - "directAccessGrantsEnabled": false, - "serviceAccountsEnabled": false, - "publicClient": true, - "frontchannelLogout": false, - "protocol": "openid-connect", - "attributes": { - "post.logout.redirect.uris": "+", - "pkce.code.challenge.method": "S256" - }, - "authenticationFlowBindingOverrides": {}, - "fullScopeAllowed": false, - "nodeReRegistrationTimeout": 0, - "protocolMappers": [ - { - "id": "6ebe150a-a9e3-41e8-816e-b25a853ccaed", - "name": "audience resolve", - "protocol": "openid-connect", - "protocolMapper": "oidc-audience-resolve-mapper", - "consentRequired": false, - "config": {} - } - ], - "defaultClientScopes": [ - "web-origins", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] - }, - { - "id": "ae6950e9-7a85-4970-8cdd-4c6b2e4b8d6f", - "clientId": "admin-cli", - "name": "${client_admin-cli}", - "surrogateAuthRequired": false, - "enabled": true, - "alwaysDisplayInConsole": false, - "clientAuthenticatorType": "client-secret", - "redirectUris": [], - "webOrigins": [], - "notBefore": 0, - "bearerOnly": false, - "consentRequired": false, - "standardFlowEnabled": false, - "implicitFlowEnabled": false, - "directAccessGrantsEnabled": true, - "serviceAccountsEnabled": false, - "publicClient": true, - "frontchannelLogout": false, - "protocol": "openid-connect", - "attributes": {}, - "authenticationFlowBindingOverrides": {}, - "fullScopeAllowed": false, - "nodeReRegistrationTimeout": 0, - "defaultClientScopes": [ - "web-origins", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] - }, - { - "id": "add69182-4588-4087-a1e6-0d9a59d8d81c", - "clientId": "broker", - "name": "${client_broker}", - "surrogateAuthRequired": false, - "enabled": true, - "alwaysDisplayInConsole": false, - "clientAuthenticatorType": "client-secret", - "redirectUris": [], - "webOrigins": [], - "notBefore": 0, - "bearerOnly": true, - "consentRequired": false, - "standardFlowEnabled": true, - "implicitFlowEnabled": false, - "directAccessGrantsEnabled": false, - "serviceAccountsEnabled": false, - "publicClient": false, - "frontchannelLogout": false, - "protocol": "openid-connect", - "attributes": {}, - "authenticationFlowBindingOverrides": {}, - "fullScopeAllowed": false, - "nodeReRegistrationTimeout": 0, - "defaultClientScopes": [ - "web-origins", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] - }, - { - "id": "efa03508-c135-4dc8-a471-eed108cdfc6c", - "clientId": "master-realm", - "name": "master Realm", - "surrogateAuthRequired": false, - "enabled": true, - "alwaysDisplayInConsole": false, - "clientAuthenticatorType": "client-secret", - "redirectUris": [], - "webOrigins": [], - "notBefore": 0, - "bearerOnly": true, - "consentRequired": false, - "standardFlowEnabled": true, - "implicitFlowEnabled": false, - "directAccessGrantsEnabled": false, - "serviceAccountsEnabled": false, - "publicClient": false, - "frontchannelLogout": false, - "protocol": "openid-connect", - "attributes": {}, - "authenticationFlowBindingOverrides": {}, - "fullScopeAllowed": false, - "nodeReRegistrationTimeout": 0, - "defaultClientScopes": [ - "web-origins", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] - }, - { - "id": "f3b244a4-2280-4125-ac81-4abc7caebf57", - "clientId": "security-admin-console", - "name": "${client_security-admin-console}", - "rootUrl": "${authAdminUrl}", - "baseUrl": "/admin/master/console/", - "surrogateAuthRequired": false, - "enabled": true, - "alwaysDisplayInConsole": false, - "clientAuthenticatorType": "client-secret", - "redirectUris": [ - "/admin/master/console/*" - ], - "webOrigins": [ - "+" - ], - "notBefore": 0, - "bearerOnly": false, - "consentRequired": false, - "standardFlowEnabled": true, - "implicitFlowEnabled": false, - "directAccessGrantsEnabled": false, - "serviceAccountsEnabled": false, - "publicClient": true, - "frontchannelLogout": false, - "protocol": "openid-connect", - "attributes": { - "post.logout.redirect.uris": "+", - "pkce.code.challenge.method": "S256" - }, - "authenticationFlowBindingOverrides": {}, - "fullScopeAllowed": false, - "nodeReRegistrationTimeout": 0, - "protocolMappers": [ - { - "id": "7f0699b0-db56-403c-b0c3-d239e4d7cbf9", - "name": "locale", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "userinfo.token.claim": "true", - "user.attribute": "locale", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "locale", - "jsonType.label": "String" - } - } - ], - "defaultClientScopes": [ - "web-origins", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] - } - ], - "clientScopes": [ - { - "id": "77536af6-5352-42cd-aff0-9f0ed0c2e636", - "name": "address", - "description": "OpenID Connect built-in scope: address", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "true", - "display.on.consent.screen": "true", - "consent.screen.text": "${addressScopeConsentText}" - }, - "protocolMappers": [ - { - "id": "8dcab95e-fa00-414c-8c3f-7927764af6e5", - "name": "address", - "protocol": "openid-connect", - "protocolMapper": "oidc-address-mapper", - "consentRequired": false, - "config": { - "user.attribute.formatted": "formatted", - "user.attribute.country": "country", - "user.attribute.postal_code": "postal_code", - "userinfo.token.claim": "true", - "user.attribute.street": "street", - "id.token.claim": "true", - "user.attribute.region": "region", - "access.token.claim": "true", - "user.attribute.locality": "locality" - } - } - ] - }, - { - "id": "43cd72f4-541a-4ce3-8657-615cd05d675c", - "name": "acr", - "description": "OpenID Connect scope for add acr (authentication context class reference) to the token", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "false", - "display.on.consent.screen": "false" - }, - "protocolMappers": [ - { - "id": "fc2a9442-5c90-4fad-b577-245b23800f74", - "name": "acr loa level", - "protocol": "openid-connect", - "protocolMapper": "oidc-acr-mapper", - "consentRequired": false, - "config": { - "id.token.claim": "true", - "access.token.claim": "true" - } - } - ] - }, - { - "id": "99e38304-3ee1-4c5a-8925-4506f74696ee", - "name": "offline_access", - "description": "OpenID Connect built-in scope: offline_access", - "protocol": "openid-connect", - "attributes": { - "consent.screen.text": "${offlineAccessScopeConsentText}", - "display.on.consent.screen": "true" - } - }, - { - "id": "395b3d90-81c3-4838-a824-24074d9a085d", - "name": "microprofile-jwt", - "description": "Microprofile - JWT built-in scope", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "true", - "display.on.consent.screen": "false" - }, - "protocolMappers": [ - { - "id": "d48a6085-1b80-4fd9-8190-e6da48378e2d", - "name": "upn", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": false, - "config": { - "userinfo.token.claim": "true", - "user.attribute": "username", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "upn", - "jsonType.label": "String" - } - }, - { - "id": "91e33380-5d98-4b46-b394-9b59e372a494", - "name": "groups", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-realm-role-mapper", - "consentRequired": false, - "config": { - "multivalued": "true", - "userinfo.token.claim": "true", - "user.attribute": "foo", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "groups", - "jsonType.label": "String" - } - } - ] - }, - { - "id": "e260ddc8-9ab5-4cfb-86cc-863b4cb59117", - "name": "email", - "description": "OpenID Connect built-in scope: email", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "true", - "display.on.consent.screen": "true", - "consent.screen.text": "${emailScopeConsentText}" - }, - "protocolMappers": [ - { - "id": "21b7539e-791b-4b1e-895f-edaab639c5ef", - "name": "email verified", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": false, - "config": { - "userinfo.token.claim": "true", - "user.attribute": "emailVerified", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "email_verified", - "jsonType.label": "boolean" - } - }, - { - "id": "211d42d9-f436-4b6a-a5e3-931507746247", - "name": "email", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": false, - "config": { - "userinfo.token.claim": "true", - "user.attribute": "email", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "email", - "jsonType.label": "String" - } - } - ] - }, - { - "id": "bd3f2e46-be00-4352-a704-4acb72823ca2", - "name": "profile", - "description": "OpenID Connect built-in scope: profile", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "true", - "display.on.consent.screen": "true", - "consent.screen.text": "${profileScopeConsentText}" - }, - "protocolMappers": [ - { - "id": "6246d22e-e8f4-4c1a-b8c7-85d967bc440a", - "name": "website", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "userinfo.token.claim": "true", - "user.attribute": "website", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "website", - "jsonType.label": "String" - } - }, - { - "id": "9385e7ca-45ad-4c4b-a56a-8217aead6e16", - "name": "middle name", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "userinfo.token.claim": "true", - "user.attribute": "middleName", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "middle_name", - "jsonType.label": "String" - } - }, - { - "id": "a2be1cec-f7c0-4b9f-b3a6-b94bdd8174e9", - "name": "locale", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "userinfo.token.claim": "true", - "user.attribute": "locale", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "locale", - "jsonType.label": "String" - } - }, - { - "id": "71ad7a22-c923-4822-8930-0a2342c0195b", - "name": "gender", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "userinfo.token.claim": "true", - "user.attribute": "gender", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "gender", - "jsonType.label": "String" - } - }, - { - "id": "220b5b4e-6c86-404b-92d8-ccc92075b0ec", - "name": "nickname", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "userinfo.token.claim": "true", - "user.attribute": "nickname", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "nickname", - "jsonType.label": "String" - } - }, - { - "id": "f5d0f9ee-906f-432b-8169-5c23601f4dd2", - "name": "username", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": false, - "config": { - "userinfo.token.claim": "true", - "user.attribute": "username", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "preferred_username", - "jsonType.label": "String" - } - }, - { - "id": "19962845-92c4-4ef5-b5e5-780c53fd8181", - "name": "family name", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": false, - "config": { - "userinfo.token.claim": "true", - "user.attribute": "lastName", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "family_name", - "jsonType.label": "String" - } - }, - { - "id": "54fe8868-1ac4-4906-bb96-a0431f11f801", - "name": "birthdate", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "userinfo.token.claim": "true", - "user.attribute": "birthdate", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "birthdate", - "jsonType.label": "String" - } - }, - { - "id": "cf5d88d9-c70e-47d9-9eeb-b0d05bee4df3", - "name": "full name", - "protocol": "openid-connect", - "protocolMapper": "oidc-full-name-mapper", - "consentRequired": false, - "config": { - "id.token.claim": "true", - "access.token.claim": "true", - "userinfo.token.claim": "true" - } - }, - { - "id": "b0fc93e7-ccd2-44c0-bf7b-7d3db935f980", - "name": "zoneinfo", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "userinfo.token.claim": "true", - "user.attribute": "zoneinfo", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "zoneinfo", - "jsonType.label": "String" - } - }, - { - "id": "77f20845-32dd-4651-9c5c-07e8acbdfac0", - "name": "picture", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "userinfo.token.claim": "true", - "user.attribute": "picture", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "picture", - "jsonType.label": "String" - } - }, - { - "id": "e9a0cdc2-43bd-4734-b455-0d2e11698de8", - "name": "profile", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "userinfo.token.claim": "true", - "user.attribute": "profile", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "profile", - "jsonType.label": "String" - } - }, - { - "id": "35cf7657-3fa4-4569-871c-2060a11c5e4b", - "name": "updated at", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "userinfo.token.claim": "true", - "user.attribute": "updatedAt", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "updated_at", - "jsonType.label": "String" - } - }, - { - "id": "3d697e57-2bd7-4667-a904-d6452354e0ea", - "name": "given name", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": false, - "config": { - "userinfo.token.claim": "true", - "user.attribute": "firstName", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "given_name", - "jsonType.label": "String" - } - } - ] - }, - { - "id": "258a3c9d-abdd-4880-b169-3f90bcb59609", - "name": "web-origins", - "description": "OpenID Connect scope for add allowed web origins to the access token", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "false", - "display.on.consent.screen": "false", - "consent.screen.text": "" - }, - "protocolMappers": [ - { - "id": "5e55df72-1128-423c-bb7e-efd59b303992", - "name": "allowed web origins", - "protocol": "openid-connect", - "protocolMapper": "oidc-allowed-origins-mapper", - "consentRequired": false, - "config": {} - } - ] - }, - { - "id": "e2519dd7-181b-4061-96e4-51ad793c21a0", - "name": "role_list", - "description": "SAML role list", - "protocol": "saml", - "attributes": { - "consent.screen.text": "${samlRoleListScopeConsentText}", - "display.on.consent.screen": "true" - }, - "protocolMappers": [ - { - "id": "5d6a5eb2-de50-47de-886b-c285723f9928", - "name": "role list", - "protocol": "saml", - "protocolMapper": "saml-role-list-mapper", - "consentRequired": false, - "config": { - "single": "false", - "attribute.nameformat": "Basic", - "attribute.name": "Role" - } - } - ] - }, - { - "id": "925f1f40-cc22-4829-afae-7765ac2cdb14", - "name": "phone", - "description": "OpenID Connect built-in scope: phone", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "true", - "display.on.consent.screen": "true", - "consent.screen.text": "${phoneScopeConsentText}" - }, - "protocolMappers": [ - { - "id": "8e0f8453-a375-493f-8d96-57a8154c052d", - "name": "phone number verified", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "userinfo.token.claim": "true", - "user.attribute": "phoneNumberVerified", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "phone_number_verified", - "jsonType.label": "boolean" - } - }, - { - "id": "8efae8d2-77a1-46c4-8de9-51a6781fb8f7", - "name": "phone number", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "userinfo.token.claim": "true", - "user.attribute": "phoneNumber", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "phone_number", - "jsonType.label": "String" - } - } - ] - }, - { - "id": "0b27e77a-3487-4a0a-91bc-9d5501e1b431", - "name": "roles", - "description": "OpenID Connect scope for add user roles to the access token", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "false", - "display.on.consent.screen": "true", - "consent.screen.text": "${rolesScopeConsentText}" - }, - "protocolMappers": [ - { - "id": "f02997a9-12dc-490d-9af7-2aa217874359", - "name": "audience resolve", - "protocol": "openid-connect", - "protocolMapper": "oidc-audience-resolve-mapper", - "consentRequired": false, - "config": {} - }, - { - "id": "af14fbf1-81f4-4e38-9dcd-8a328dc48619", - "name": "realm roles", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-realm-role-mapper", - "consentRequired": false, - "config": { - "user.attribute": "foo", - "access.token.claim": "true", - "claim.name": "realm_access.roles", - "jsonType.label": "String", - "multivalued": "true" - } - }, - { - "id": "b8166372-96a6-4d9c-b47c-30bc29fef4a8", - "name": "client roles", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-client-role-mapper", - "consentRequired": false, - "config": { - "user.attribute": "foo", - "access.token.claim": "true", - "claim.name": "resource_access.${client_id}.roles", - "jsonType.label": "String", - "multivalued": "true" - } - } - ] - } - ], - "defaultDefaultClientScopes": [ - "role_list", - "profile", - "email", - "roles", - "web-origins", - "acr" - ], - "defaultOptionalClientScopes": [ - "offline_access", - "address", - "phone", - "microprofile-jwt" - ], - "browserSecurityHeaders": { - "contentSecurityPolicyReportOnly": "", - "xContentTypeOptions": "nosniff", - "xRobotsTag": "none", - "xFrameOptions": "SAMEORIGIN", - "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", - "xXSSProtection": "1; mode=block", - "strictTransportSecurity": "max-age=31536000; includeSubDomains" - }, - "smtpServer": {}, - "eventsEnabled": false, - "eventsListeners": [ - "jboss-logging" - ], - "enabledEventTypes": [], - "adminEventsEnabled": false, - "adminEventsDetailsEnabled": false, - "identityProviders": [], - "identityProviderMappers": [], - "components": { - "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ - { - "id": "86c28e25-74c1-476b-8723-6d0f799b15df", - "name": "Full Scope Disabled", - "providerId": "scope", - "subType": "anonymous", - "subComponents": {}, - "config": {} - }, - { - "id": "3cb73dd9-c955-4fba-9bc5-abb6ccd29bb6", - "name": "Allowed Client Scopes", - "providerId": "allowed-client-templates", - "subType": "anonymous", - "subComponents": {}, - "config": { - "allow-default-scopes": [ - "true" - ] - } - }, - { - "id": "3e7e1b97-82c5-4b1a-9c5a-ef0657799252", - "name": "Allowed Protocol Mapper Types", - "providerId": "allowed-protocol-mappers", - "subType": "authenticated", - "subComponents": {}, - "config": { - "allowed-protocol-mapper-types": [ - "saml-user-attribute-mapper", - "oidc-full-name-mapper", - "oidc-usermodel-attribute-mapper", - "saml-role-list-mapper", - "saml-user-property-mapper", - "oidc-address-mapper", - "oidc-sha256-pairwise-sub-mapper", - "oidc-usermodel-property-mapper" - ] - } - }, - { - "id": "d6a971eb-ad45-4bfd-a8c5-cd426c593768", - "name": "Consent Required", - "providerId": "consent-required", - "subType": "anonymous", - "subComponents": {}, - "config": {} - }, - { - "id": "ed09d054-e0b1-48cb-ab73-0119b21a8b3a", - "name": "Max Clients Limit", - "providerId": "max-clients", - "subType": "anonymous", - "subComponents": {}, - "config": { - "max-clients": [ - "200" - ] - } - }, - { - "id": "568cebd6-bc86-4f14-ae91-112a34176e32", - "name": "Allowed Client Scopes", - "providerId": "allowed-client-templates", - "subType": "authenticated", - "subComponents": {}, - "config": { - "allow-default-scopes": [ - "true" - ] - } - }, - { - "id": "1a1c22b5-4be6-417b-b5cf-4d2b0b2ee24d", - "name": "Allowed Protocol Mapper Types", - "providerId": "allowed-protocol-mappers", - "subType": "anonymous", - "subComponents": {}, - "config": { - "allowed-protocol-mapper-types": [ - "oidc-usermodel-attribute-mapper", - "saml-role-list-mapper", - "oidc-sha256-pairwise-sub-mapper", - "oidc-address-mapper", - "saml-user-property-mapper", - "oidc-usermodel-property-mapper", - "saml-user-attribute-mapper", - "oidc-full-name-mapper" - ] - } - }, - { - "id": "82aaab32-c500-45bd-8c74-def1d2012ba1", - "name": "Trusted Hosts", - "providerId": "trusted-hosts", - "subType": "anonymous", - "subComponents": {}, - "config": { - "host-sending-registration-request-must-match": [ - "true" - ], - "client-uris-must-match": [ - "true" - ] - } - } - ], - "org.keycloak.keys.KeyProvider": [ - { - "id": "1a5101b2-1068-4587-8a00-3f19ba2a82f7", - "name": "rsa-generated", - "providerId": "rsa-generated", - "subComponents": {}, - "config": { - "priority": [ - "100" - ] - } - }, - { - "id": "c8d75e7a-1a73-4080-be95-9062291310cb", - "name": "aes-generated", - "providerId": "aes-generated", - "subComponents": {}, - "config": { - "priority": [ - "100" - ] - } - }, - { - "id": "c752ea78-9018-4357-a720-587aefc6054f", - "name": "hmac-generated", - "providerId": "hmac-generated", - "subComponents": {}, - "config": { - "priority": [ - "100" - ], - "algorithm": [ - "HS256" - ] - } - } - ] - }, - "internationalizationEnabled": false, - "supportedLocales": [], - "authenticationFlows": [ - { - "id": "5ed3d9a5-2ad0-44c0-a623-9a481c60ce27", - "alias": "Account verification options", - "description": "Method with which to verity the existing account", - "providerId": "basic-flow", - "topLevel": false, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "idp-email-verification", - "authenticatorFlow": false, - "requirement": "ALTERNATIVE", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticatorFlow": true, - "requirement": "ALTERNATIVE", - "priority": 20, - "autheticatorFlow": true, - "flowAlias": "Verify Existing Account by Re-authentication", - "userSetupAllowed": false - } - ] - }, - { - "id": "54bc8205-ee68-4a6f-b639-0944a37a3c75", - "alias": "Authentication Options", - "description": "Authentication options.", - "providerId": "basic-flow", - "topLevel": false, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "basic-auth", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "basic-auth-otp", - "authenticatorFlow": false, - "requirement": "DISABLED", - "priority": 20, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "auth-spnego", - "authenticatorFlow": false, - "requirement": "DISABLED", - "priority": 30, - "autheticatorFlow": false, - "userSetupAllowed": false - } - ] - }, - { - "id": "d04905ec-09af-4e95-b4bd-9ed4ce963364", - "alias": "Browser - Conditional OTP", - "description": "Flow to determine if the OTP is required for the authentication", - "providerId": "basic-flow", - "topLevel": false, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "conditional-user-configured", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "auth-otp-form", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 20, - "autheticatorFlow": false, - "userSetupAllowed": false - } - ] - }, - { - "id": "dc6e420c-52d5-4221-8320-4d1604434053", - "alias": "Direct Grant - Conditional OTP", - "description": "Flow to determine if the OTP is required for the authentication", - "providerId": "basic-flow", - "topLevel": false, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "conditional-user-configured", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "direct-grant-validate-otp", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 20, - "autheticatorFlow": false, - "userSetupAllowed": false - } - ] - }, - { - "id": "1ead146e-8539-4c31-8df0-3e65caeee5d0", - "alias": "First broker login - Conditional OTP", - "description": "Flow to determine if the OTP is required for the authentication", - "providerId": "basic-flow", - "topLevel": false, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "conditional-user-configured", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "auth-otp-form", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 20, - "autheticatorFlow": false, - "userSetupAllowed": false - } - ] - }, - { - "id": "8d58fe02-e3f6-4227-930d-a88b72aaa44e", - "alias": "Handle Existing Account", - "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", - "providerId": "basic-flow", - "topLevel": false, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "idp-confirm-link", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticatorFlow": true, - "requirement": "REQUIRED", - "priority": 20, - "autheticatorFlow": true, - "flowAlias": "Account verification options", - "userSetupAllowed": false - } - ] - }, - { - "id": "f5e8fd4a-86d8-4bfc-b36f-7c5e927fd935", - "alias": "Reset - Conditional OTP", - "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", - "providerId": "basic-flow", - "topLevel": false, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "conditional-user-configured", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "reset-otp", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 20, - "autheticatorFlow": false, - "userSetupAllowed": false - } - ] - }, - { - "id": "4a25a163-1ab8-416f-a8ba-c23d889f9f6f", - "alias": "User creation or linking", - "description": "Flow for the existing/non-existing user alternatives", - "providerId": "basic-flow", - "topLevel": false, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticatorConfig": "create unique user config", - "authenticator": "idp-create-user-if-unique", - "authenticatorFlow": false, - "requirement": "ALTERNATIVE", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticatorFlow": true, - "requirement": "ALTERNATIVE", - "priority": 20, - "autheticatorFlow": true, - "flowAlias": "Handle Existing Account", - "userSetupAllowed": false - } - ] - }, - { - "id": "acc44799-a775-433e-b20f-0e5473df1f2a", - "alias": "Verify Existing Account by Re-authentication", - "description": "Reauthentication of existing account", - "providerId": "basic-flow", - "topLevel": false, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "idp-username-password-form", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticatorFlow": true, - "requirement": "CONDITIONAL", - "priority": 20, - "autheticatorFlow": true, - "flowAlias": "First broker login - Conditional OTP", - "userSetupAllowed": false - } - ] - }, - { - "id": "b5c47f32-f572-4a59-bbd9-95cfa242b680", - "alias": "browser", - "description": "browser based authentication", - "providerId": "basic-flow", - "topLevel": true, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "auth-cookie", - "authenticatorFlow": false, - "requirement": "ALTERNATIVE", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "auth-spnego", - "authenticatorFlow": false, - "requirement": "DISABLED", - "priority": 20, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "identity-provider-redirector", - "authenticatorFlow": false, - "requirement": "ALTERNATIVE", - "priority": 25, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticatorFlow": true, - "requirement": "ALTERNATIVE", - "priority": 30, - "autheticatorFlow": true, - "flowAlias": "forms", - "userSetupAllowed": false - } - ] - }, - { - "id": "1b0f26df-6272-43fd-8645-f8f3235733c9", - "alias": "clients", - "description": "Base authentication for clients", - "providerId": "client-flow", - "topLevel": true, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "client-secret", - "authenticatorFlow": false, - "requirement": "ALTERNATIVE", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "client-jwt", - "authenticatorFlow": false, - "requirement": "ALTERNATIVE", - "priority": 20, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "client-secret-jwt", - "authenticatorFlow": false, - "requirement": "ALTERNATIVE", - "priority": 30, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "client-x509", - "authenticatorFlow": false, - "requirement": "ALTERNATIVE", - "priority": 40, - "autheticatorFlow": false, - "userSetupAllowed": false - } - ] - }, - { - "id": "54cedac9-a26e-4042-af0b-e489bbe20028", - "alias": "direct grant", - "description": "OpenID Connect Resource Owner Grant", - "providerId": "basic-flow", - "topLevel": true, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "direct-grant-validate-username", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "direct-grant-validate-password", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 20, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticatorFlow": true, - "requirement": "CONDITIONAL", - "priority": 30, - "autheticatorFlow": true, - "flowAlias": "Direct Grant - Conditional OTP", - "userSetupAllowed": false - } - ] - }, - { - "id": "fc8a4081-14cf-441b-b32e-b777a6c099c3", - "alias": "docker auth", - "description": "Used by Docker clients to authenticate against the IDP", - "providerId": "basic-flow", - "topLevel": true, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "docker-http-basic-authenticator", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - } - ] - }, - { - "id": "0622b1f0-60fe-47c9-bdc0-46a07f046d89", - "alias": "first broker login", - "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", - "providerId": "basic-flow", - "topLevel": true, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticatorConfig": "review profile config", - "authenticator": "idp-review-profile", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticatorFlow": true, - "requirement": "REQUIRED", - "priority": 20, - "autheticatorFlow": true, - "flowAlias": "User creation or linking", - "userSetupAllowed": false - } - ] - }, - { - "id": "7e270b16-443d-4245-8acf-3efeff61bb20", - "alias": "forms", - "description": "Username, password, otp and other auth forms.", - "providerId": "basic-flow", - "topLevel": false, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "auth-username-password-form", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticatorFlow": true, - "requirement": "CONDITIONAL", - "priority": 20, - "autheticatorFlow": true, - "flowAlias": "Browser - Conditional OTP", - "userSetupAllowed": false - } - ] - }, - { - "id": "7b53ff6a-c58b-4502-8cd8-2c1ac07b6559", - "alias": "http challenge", - "description": "An authentication flow based on challenge-response HTTP Authentication Schemes", - "providerId": "basic-flow", - "topLevel": true, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "no-cookie-redirect", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticatorFlow": true, - "requirement": "REQUIRED", - "priority": 20, - "autheticatorFlow": true, - "flowAlias": "Authentication Options", - "userSetupAllowed": false - } - ] - }, - { - "id": "36dcec4a-2aa0-4034-a71e-4121ad489d4f", - "alias": "registration", - "description": "registration flow", - "providerId": "basic-flow", - "topLevel": true, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "registration-page-form", - "authenticatorFlow": true, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": true, - "flowAlias": "registration form", - "userSetupAllowed": false - } - ] - }, - { - "id": "7953629c-bbb1-450e-a8bf-0c8230c86f35", - "alias": "registration form", - "description": "registration form", - "providerId": "form-flow", - "topLevel": false, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "registration-user-creation", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 20, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "registration-profile-action", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 40, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "registration-password-action", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 50, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "registration-recaptcha-action", - "authenticatorFlow": false, - "requirement": "DISABLED", - "priority": 60, - "autheticatorFlow": false, - "userSetupAllowed": false - } - ] - }, - { - "id": "682e128e-b00c-46d1-b5e0-0f2e83c70e0c", - "alias": "reset credentials", - "description": "Reset credentials for a user if they forgot their password or something", - "providerId": "basic-flow", - "topLevel": true, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "reset-credentials-choose-user", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "reset-credential-email", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 20, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "reset-password", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 30, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticatorFlow": true, - "requirement": "CONDITIONAL", - "priority": 40, - "autheticatorFlow": true, - "flowAlias": "Reset - Conditional OTP", - "userSetupAllowed": false - } - ] - }, - { - "id": "85504ca7-485f-40c9-b093-c286fc8ae9cf", - "alias": "saml ecp", - "description": "SAML ECP Profile Authentication Flow", - "providerId": "basic-flow", - "topLevel": true, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "http-basic-authenticator", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - } - ] - } - ], - "authenticatorConfig": [ - { - "id": "9942228c-2408-4783-b209-d739b7b5f144", - "alias": "create unique user config", - "config": { - "require.password.update.after.registration": "false" - } - }, - { - "id": "c90c245d-e7e4-4ba2-89bd-40db9898824b", - "alias": "review profile config", - "config": { - "update.profile.on.first.login": "missing" - } - } - ], - "requiredActions": [ - { - "alias": "CONFIGURE_TOTP", - "name": "Configure OTP", - "providerId": "CONFIGURE_TOTP", - "enabled": true, - "defaultAction": false, - "priority": 10, - "config": {} - }, - { - "alias": "terms_and_conditions", - "name": "Terms and Conditions", - "providerId": "terms_and_conditions", - "enabled": false, - "defaultAction": false, - "priority": 20, - "config": {} - }, - { - "alias": "UPDATE_PASSWORD", - "name": "Update Password", - "providerId": "UPDATE_PASSWORD", - "enabled": true, - "defaultAction": false, - "priority": 30, - "config": {} - }, - { - "alias": "UPDATE_PROFILE", - "name": "Update Profile", - "providerId": "UPDATE_PROFILE", - "enabled": true, - "defaultAction": false, - "priority": 40, - "config": {} - }, - { - "alias": "VERIFY_EMAIL", - "name": "Verify Email", - "providerId": "VERIFY_EMAIL", - "enabled": true, - "defaultAction": false, - "priority": 50, - "config": {} - }, - { - "alias": "delete_account", - "name": "Delete Account", - "providerId": "delete_account", - "enabled": false, - "defaultAction": false, - "priority": 60, - "config": {} - }, - { - "alias": "update_user_locale", - "name": "Update User Locale", - "providerId": "update_user_locale", - "enabled": true, - "defaultAction": false, - "priority": 1000, - "config": {} - } - ], - "browserFlow": "browser", - "registrationFlow": "registration", - "directGrantFlow": "direct grant", - "resetCredentialsFlow": "reset credentials", - "clientAuthenticationFlow": "clients", - "dockerAuthenticationFlow": "docker auth", - "attributes": { - "cibaBackchannelTokenDeliveryMode": "poll", - "cibaExpiresIn": "120", - "cibaAuthRequestedUserHint": "login_hint", - "oauth2DeviceCodeLifespan": "600", - "clientOfflineSessionMaxLifespan": "0", - "oauth2DevicePollingInterval": "5", - "clientSessionIdleTimeout": "0", - "userProfileEnabled": "false", - "parRequestUriLifespan": "60", - "clientSessionMaxLifespan": "0", - "clientOfflineSessionIdleTimeout": "0", - "cibaInterval": "5" - }, - "keycloakVersion": "18.0.2", - "userManagedAccessAllowed": true, - "clientProfiles": { - "profiles": [] - }, - "clientPolicies": { - "policies": [] - } -} \ No newline at end of file diff --git a/src/main/java/com/ebremer/halcyon/EmbeddedSpringKeycloakAutoConfiguration.java b/src/main/java/com/ebremer/halcyon/EmbeddedSpringKeycloakAutoConfiguration.java deleted file mode 100644 index 90e90ac4..00000000 --- a/src/main/java/com/ebremer/halcyon/EmbeddedSpringKeycloakAutoConfiguration.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.ebremer.halcyon; - -import com.github.thomasdarimont.keycloak.embedded.EmbeddedKeycloakConfig; -import com.github.thomasdarimont.keycloak.embedded.KeycloakCustomProperties; -import com.github.thomasdarimont.keycloak.embedded.KeycloakProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; - -@Configuration -@EnableConfigurationProperties({KeycloakProperties.class, KeycloakCustomProperties.class}) -@ComponentScan(basePackageClasses = EmbeddedKeycloakConfig.class) -public class EmbeddedSpringKeycloakAutoConfiguration { -} \ No newline at end of file diff --git a/src/main/java/com/ebremer/halcyon/HalcyonSettings.java b/src/main/java/com/ebremer/halcyon/HalcyonSettings.java index 599cfcae..cfda5ce5 100644 --- a/src/main/java/com/ebremer/halcyon/HalcyonSettings.java +++ b/src/main/java/com/ebremer/halcyon/HalcyonSettings.java @@ -54,13 +54,13 @@ public final class HalcyonSettings { private final HashMap mappings; private final String Realm = "master"; - public static final String realm = "master"; + public static final String realm = "Halcyon"; public static final int DEFAULTHTTPPORT = 8888; public static final int DEFAULTHTTPSPORT = 9999; public static final int DEFAULTSPARQLPORT = 8887; public static final String DEFAULTHOSTNAME = "http://localhost"; public static final String DEFAULTHOSTIP = "0.0.0.0"; - public static final String VERSION = "0.4.2"; + public static final String VERSION = "0.5.0"; public static Resource HALCYONAGENT = ResourceFactory.createResource(HAL.NS+"/VERSION/"+VERSION); private HalcyonSettings() { diff --git a/src/main/java/com/ebremer/halcyon/INIT.java b/src/main/java/com/ebremer/halcyon/INIT.java index 496abc17..2af5cb2d 100644 --- a/src/main/java/com/ebremer/halcyon/INIT.java +++ b/src/main/java/com/ebremer/halcyon/INIT.java @@ -45,7 +45,7 @@ public Model getDefaultSettings() { m.setNsPrefix("", HAL.NS); m.createResource("http://localhost") .addProperty(RDF.type, HAL.HalcyonSettingsFile) - .addProperty(HAL.RDFStoreLocation, "TDB2") + .addProperty(HAL.RDFStoreLocation, "tdb2") .addProperty(HAL.HostName, "http://localhost:"+HalcyonSettings.DEFAULTHTTPPORT) .addProperty(HAL.HostIP, "0.0.0.0") //not fully implemented yet .addLiteral(HAL.HTTPPort, HalcyonSettings.DEFAULTHTTPPORT) diff --git a/src/main/java/com/ebremer/halcyon/fuseki/shiro/JwtVerifier.java b/src/main/java/com/ebremer/halcyon/fuseki/shiro/JwtVerifier.java index c3279327..3f8c3236 100644 --- a/src/main/java/com/ebremer/halcyon/fuseki/shiro/JwtVerifier.java +++ b/src/main/java/com/ebremer/halcyon/fuseki/shiro/JwtVerifier.java @@ -1,8 +1,9 @@ package com.ebremer.halcyon.fuseki.shiro; import io.jsonwebtoken.Claims; +import io.jsonwebtoken.impl.DefaultClaims; import io.jsonwebtoken.Jws; -import io.jsonwebtoken.JwtParserBuilder; +import io.jsonwebtoken.JwtParser; import io.jsonwebtoken.Jwts; import java.security.PublicKey; @@ -14,17 +15,24 @@ public JwtVerifier(PublicKey publicKey) { } public Claims verify(String token) { - JwtParserBuilder b = Jwts.parserBuilder(); - Jws claimx; try { - claimx = b + JwtParser p = Jwts.parserBuilder() .setAllowedClockSkewSeconds(86400) .setSigningKey(publicKey) - .build().parseClaimsJws(token); + .build(); + Jws claimx = p.parseClaimsJws(token); return claimx.getBody(); } catch (io.jsonwebtoken.ExpiredJwtException ex) { System.out.println("ARRRGGGG JWT expired!!!!"); + } catch (io.jsonwebtoken.security.SignatureException ex) { + System.out.println(ex.getMessage()); } - return null; + return new DefaultClaims(); } -} \ No newline at end of file + + public static void main(String[] args) { + String token ="eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ6RkN0a1BiYW14S093RDBDa3M4SUJsSFZZelc3dVFTWkhpRWVTeGRYTENJIn0.eyJleHAiOjE2ODYwMDE0ODgsImlhdCI6MTY4NTk5NDI4OCwianRpIjoiMDE5ODk1ZjMtMGU2Yy00ZTI0LTgzYjAtOWYxYTQ1MmQ4MTlkIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4ODg4L2F1dGgvcmVhbG1zL0hhbGN5b24iLCJzdWIiOiJmNDg0NTlmMy0yMTY1LTQ3ZjEtYjA3MC1lZmVhMjljZTgxNDAiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJhY2NvdW50Iiwic2Vzc2lvbl9zdGF0ZSI6IjE1ZDA5MzIxLWZlZmUtNGNmYi05MzdjLWE2MzM5MWY5MTBlZCIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1oYWxjeW9uIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsInNpZCI6IjE1ZDA5MzIxLWZlZmUtNGNmYi05MzdjLWE2MzM5MWY5MTBlZCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZ3JvdXBzIjpbImRlZmF1bHQtcm9sZXMtaGFsY3lvbiIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXSwiSGFsY3lvbkdyb3VwcyI6WyIvRXZlcnlvbmUiLCIvYWRtaW4iXSwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWRtaW4iLCJnaXZlbl9uYW1lIjoiIiwiZmFtaWx5X25hbWUiOiIifQ.i_djD-ki1P_-G3bucE1y8qlvDGZjppbuPcN_QMjD9jU1VBzclBUJrWbQppNjCfSg0profLfqEtMCw8IQ42h54WPxPePlJOe_U9vmnoRJtQ2bWB4yDVLeHv58T0UpjorOWDUBahVEpeFEy-X9Bkd1hsO1joNS-MHNbT1QRzzQX56Im1i2FuCq_atcvI1yIjaZzW7fpCZy6lorfCg2ij795MrTlgDr6oas3whbDwzZ_irw_EOKy7onGO-feXWqOTOENpfJwC_78I91v8obxeLsnZHvvWg0ZEPxDlPNkfs-KwZAa-V8dEuU2HehJHkKfMc81HFyH54xG6fKbAFl1mF0Zw"; + JwtVerifier v = new JwtVerifier(KeycloakPublicKeyFetcher.getKeycloakPublicKeyFetcher().getPublicKey()); + v.verify(token); + } +} diff --git a/src/main/java/com/ebremer/halcyon/fuseki/shiro/KeycloakPublicKeyFetcher.java b/src/main/java/com/ebremer/halcyon/fuseki/shiro/KeycloakPublicKeyFetcher.java index 4dddb565..25d1f4d7 100644 --- a/src/main/java/com/ebremer/halcyon/fuseki/shiro/KeycloakPublicKeyFetcher.java +++ b/src/main/java/com/ebremer/halcyon/fuseki/shiro/KeycloakPublicKeyFetcher.java @@ -22,11 +22,11 @@ public class KeycloakPublicKeyFetcher { private static KeycloakPublicKeyFetcher kpkf = null; - private final String oidcConfigurationUrl = "http://localhost:"+HalcyonSettings.getSettings().GetHTTPPort() + "/auth/realms/master/protocol/openid-connect/certs"; + private final String oidcConfigurationUrl; private static PublicKey publicKey = null; private KeycloakPublicKeyFetcher() { - + oidcConfigurationUrl = "http://localhost:"+HalcyonSettings.getSettings().GetHTTPPort() + "/auth/realms/"+HalcyonSettings.realm+"/protocol/openid-connect/certs"; } public PublicKey getPublicKey() { @@ -51,27 +51,29 @@ public static KeycloakPublicKeyFetcher getKeycloakPublicKeyFetcher() { private PublicKey fetchPublicKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { URL url = new URL(oidcConfigurationUrl); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.setRequestProperty("Accept", "application/json"); - if (connection.getResponseCode() != 200) { throw new IOException("Failed to fetch public key from Keycloak: " + connection.getResponseMessage()); } - try (InputStream inputStream = connection.getInputStream()) { String oidcConfiguration = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); JsonObject configJson = Json.createReader(new StringReader(oidcConfiguration)).readObject(); JsonArray keysArray = configJson.getJsonArray("keys"); + int keyn = 0; + for (int f=0; f{ Resource gg = qs.getResource("s"); - Response rr = client.target(s.getProxyHostName()+"/auth/admin/realms/"+HalcyonSettings.realm+"/groups/"+gg.getURI().substring(9)+"/members").request().get(); + String url = s.getProxyHostName()+"/auth/admin/realms/"+HalcyonSettings.realm+"/groups/"+gg.getURI().substring(9)+"/members"; + Response rr = client.target(url).request().get(); if (rr.getStatus()==200) { String json2 = rr.readEntity(String.class); JsonReader jr = Json.createReader(new StringReader(json2)); diff --git a/src/main/java/com/ebremer/halcyon/imagebox/Main.java b/src/main/java/com/ebremer/halcyon/server/Main.java similarity index 96% rename from src/main/java/com/ebremer/halcyon/imagebox/Main.java rename to src/main/java/com/ebremer/halcyon/server/Main.java index 5935f2ba..24603724 100644 --- a/src/main/java/com/ebremer/halcyon/imagebox/Main.java +++ b/src/main/java/com/ebremer/halcyon/server/Main.java @@ -1,4 +1,4 @@ -package com.ebremer.halcyon.imagebox; +package com.ebremer.halcyon.server; import com.ebremer.halcyon.keycloak.HALKeycloakOIDCFilter; import com.ebremer.halcyon.HalcyonSettings; @@ -6,6 +6,9 @@ import com.ebremer.halcyon.fuseki.HalcyonProxyServlet; import com.ebremer.halcyon.datum.SessionsManager; import com.ebremer.halcyon.gui.HalcyonApplication; +import com.ebremer.halcyon.imagebox.FeatureServer; +import com.ebremer.halcyon.imagebox.ImageServer; +import com.ebremer.halcyon.server.keycloak.ServerProperties; import io.undertow.UndertowOptions; import java.io.InputStream; import java.nio.file.Files; @@ -31,6 +34,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean; @@ -43,7 +47,9 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @SpringBootApplication(exclude = LiquibaseAutoConfiguration.class) +@EnableConfigurationProperties(ServerProperties.class) public class Main { + private final HalcyonSettings settings = HalcyonSettings.getSettings(); private static final SessionIdMapper sessionidmapper = SessionsManager.getSessionsManager().getSessionIdMapper(); @@ -60,7 +66,7 @@ public ServletRegistrationBean proxyServletRegistrationBean() { bean.addInitParameter(ProxyServlet.P_HANDLEREDIRECTS, "true"); return bean; } - + @Bean public UndertowServletWebServerFactory embeddedServletContainerFactory() { System.out.println("Configuring Undertow Web Engine..."); @@ -91,7 +97,7 @@ public UndertowServletWebServerFactory embeddedServletContainerFactory() { } return factory; } - + @Bean ServletRegistrationBean iboxServletRegistration () { System.out.println("iboxServletRegistration order: "+Ordered.LOWEST_PRECEDENCE); @@ -117,18 +123,7 @@ public FilterRegistrationBean KeycloakOIDCFilterFilterReg registration.setEnabled(true); return registration; } - - /* - @Bean - public FilterRegistrationBean headerAddingFilter() { - FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); - registrationBean.setFilter(new JwtInterceptor()); - registrationBean.addUrlPatterns("/*"); - registrationBean.setOrder(1); - return registrationBean; - } - */ - + @Bean public FilterRegistrationBean wicketFilterRegistration(){ HalcyonApplication hal = new HalcyonApplication(); @@ -143,7 +138,7 @@ public FilterRegistrationBean wicketFilterRegistration(){ registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD); return registration; } - + @Bean ServletRegistrationBean HalcyonServletRegistration () { ServletRegistrationBean srb = new ServletRegistrationBean(); @@ -170,7 +165,7 @@ public void addResourceHandlers(ResourceHandlerRegistry registry) { } } } - + private final String keyStorePassword = "changeit"; private final String serverKeystore = "cacerts"; private final String serverTruststore = "cacerts"; @@ -216,3 +211,15 @@ public static void main(String[] args) throws NoSuchAlgorithmException { System.out.println("===================== Welcome to Halcyon!"); } } + + + /* + @Bean + public FilterRegistrationBean headerAddingFilter() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new JwtInterceptor()); + registrationBean.addUrlPatterns("/*"); + registrationBean.setOrder(1); + return registrationBean; + } + */ \ No newline at end of file diff --git a/src/main/java/com/ebremer/halcyon/server/controllers/MainController.java b/src/main/java/com/ebremer/halcyon/server/controllers/MainController.java new file mode 100644 index 00000000..87cf51a1 --- /dev/null +++ b/src/main/java/com/ebremer/halcyon/server/controllers/MainController.java @@ -0,0 +1,21 @@ +package com.ebremer.halcyon.server.controllers; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.view.RedirectView; + +@ConditionalOnProperty(prefix = "keycloak.server", name = "context-redirect", havingValue = "true") +@RestController +public class MainController { + + @Value("${keycloak.server.context-path}") + private String contextPath; + + @GetMapping + public RedirectView root() { + return new RedirectView(contextPath); + } + +} diff --git a/src/main/java/com/ebremer/halcyon/server/keycloak/App.java b/src/main/java/com/ebremer/halcyon/server/keycloak/App.java new file mode 100644 index 00000000..b3f66b87 --- /dev/null +++ b/src/main/java/com/ebremer/halcyon/server/keycloak/App.java @@ -0,0 +1,78 @@ +package com.ebremer.halcyon.server.keycloak; + +import java.util.NoSuchElementException; + +import org.keycloak.Config; +import org.keycloak.exportimport.ExportImportManager; +import org.keycloak.models.KeycloakSession; +import org.keycloak.services.managers.ApplianceBootstrap; +import org.keycloak.services.resources.KeycloakApplication; +import org.keycloak.services.util.JsonConfigProviderFactory; + +import com.ebremer.halcyon.server.keycloak.providers.JsonProviderFactory; +import java.io.File; +import java.io.IOException; + +import lombok.extern.slf4j.Slf4j; +import org.keycloak.exportimport.ExportImportConfig; +import static org.keycloak.services.resources.KeycloakApplication.getSessionFactory; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; + +@Slf4j +public class App extends KeycloakApplication { + + static ServerProperties properties; + + @Override + protected void loadConfig() { + JsonConfigProviderFactory factory = new JsonProviderFactory(); + Config.init(factory.create().orElseThrow(() -> new NoSuchElementException("No value present"))); + } + + @Override + protected ExportImportManager bootstrap() { + final ExportImportManager exportImportManager = super.bootstrap(); + createMasterRealmAdminUser(); + tryImportRealm(); + return exportImportManager; + } + + private void createMasterRealmAdminUser() { + KeycloakSession session = getSessionFactory().create(); + ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session); + try { + session.getTransactionManager().begin(); + applianceBootstrap.createMasterRealmUser(properties.username(), properties.password()); + session.getTransactionManager().commit(); + } catch (Exception ex) { + log.warn("Couldn't create keycloak master admin user: {}", ex.getMessage()); + session.getTransactionManager().rollback(); + } + session.close(); + } + + private void tryImportRealm() { + Resource importLocation = new FileSystemResource("keycloak-realm-config.json"); + if (!importLocation.exists()) { + log.info("Could not find keycloak import file {}", importLocation); + return; + } + File file; + try { + file = importLocation.getFile(); + } catch (IOException e) { + log.error("Could not read keycloak import file {}", importLocation, e); + return; + } + log.info("Starting Keycloak realm configuration import from location: {}", importLocation); + try (KeycloakSession session = getSessionFactory().create()) { + ExportImportConfig.setAction("import"); + ExportImportConfig.setProvider("singleFile"); + ExportImportConfig.setFile(file.getAbsolutePath()); + ExportImportManager manager = new ExportImportManager(session); + manager.runImport(); + } + log.info("Keycloak realm configuration import finished."); + } +} diff --git a/src/main/java/com/ebremer/halcyon/server/keycloak/Config.java b/src/main/java/com/ebremer/halcyon/server/keycloak/Config.java new file mode 100644 index 00000000..f822fd36 --- /dev/null +++ b/src/main/java/com/ebremer/halcyon/server/keycloak/Config.java @@ -0,0 +1,89 @@ +package com.ebremer.halcyon.server.keycloak; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import javax.naming.CompositeName; +import javax.naming.InitialContext; +import javax.naming.Name; +import javax.naming.NameParser; +import javax.naming.NamingException; +import javax.naming.spi.NamingManager; +import javax.sql.DataSource; +import org.jboss.resteasy.plugins.server.servlet.HttpServlet30Dispatcher; +import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters; +import org.keycloak.platform.Platform; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import com.ebremer.halcyon.server.keycloak.providers.SimplePlatformProvider; +import lombok.RequiredArgsConstructor; + +@Configuration +@RequiredArgsConstructor +public class Config { + private final ServerProperties properties; + private final DataSource dataSource; + + @Bean + ServletRegistrationBean keycloakJaxRsApplication() throws Exception { + mockJndiEnvironment(); + App.properties = properties; + final var servlet = new ServletRegistrationBean(new HttpServlet30Dispatcher()); + servlet.addInitParameter("javax.ws.rs.Application", App.class.getName()); + servlet.addInitParameter(ResteasyContextParameters.RESTEASY_SERVLET_MAPPING_PREFIX, properties.contextPath()); + servlet.addInitParameter(ResteasyContextParameters.RESTEASY_USE_CONTAINER_FORM_PARAMS, "true"); + servlet.addUrlMappings(properties.contextPath() + "/*"); + servlet.setLoadOnStartup(1); + servlet.setAsyncSupported(true); + return servlet; + } + + @Bean + FilterRegistrationBean keycloakSessionManagement() { + final var filter = new FilterRegistrationBean(); + filter.setName("Keycloak Session Management"); + filter.setFilter(new RequestFilter()); + filter.addUrlPatterns(properties.contextPath() + "/*"); + return filter; + } + + private void mockJndiEnvironment() throws NamingException { + NamingManager.setInitialContextFactoryBuilder((env) -> (environment) -> new InitialContext() { + @Override + public Object lookup(Name name) { + return lookup(name.toString()); + } + + @Override + public Object lookup(String name) { + if ("spring/datasource".equals(name)) { + return dataSource; + } else if (name.startsWith("java:jboss/ee/concurrency/executor/")) { + return fixedThreadPool(); + } + return null; + } + + @Override + public NameParser getNameParser(String name) { + return CompositeName::new; + } + + @Override + public void close() {} + }); + } + + @Bean("fixedThreadPool") + ExecutorService fixedThreadPool() { + return Executors.newFixedThreadPool(5); + } + + @Bean + @ConditionalOnMissingBean(name = "springBootPlatform") + protected SimplePlatformProvider springBootPlatform() { + return (SimplePlatformProvider) Platform.getPlatform(); + } +} diff --git a/src/main/java/com/ebremer/halcyon/server/keycloak/RequestFilter.java b/src/main/java/com/ebremer/halcyon/server/keycloak/RequestFilter.java new file mode 100644 index 00000000..f0668c98 --- /dev/null +++ b/src/main/java/com/ebremer/halcyon/server/keycloak/RequestFilter.java @@ -0,0 +1,59 @@ +package com.ebremer.halcyon.server.keycloak; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; + +import org.keycloak.common.ClientConnection; +import org.keycloak.services.filters.AbstractRequestFilter; + +public class RequestFilter extends AbstractRequestFilter implements Filter { + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws UnsupportedEncodingException { + servletRequest.setCharacterEncoding(StandardCharsets.UTF_8.name()); + final var clientConnection = createConnection((HttpServletRequest) servletRequest); + filter(clientConnection, (session) -> { + try { + filterChain.doFilter(servletRequest, servletResponse); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + private ClientConnection createConnection(HttpServletRequest request) { + return new ClientConnection() { + @Override + public String getRemoteAddr() { + return request.getRemoteAddr(); + } + + @Override + public String getRemoteHost() { + return request.getRemoteHost(); + } + + @Override + public int getRemotePort() { + return request.getRemotePort(); + } + + @Override + public String getLocalAddr() { + return request.getLocalAddr(); + } + + @Override + public int getLocalPort() { + return request.getLocalPort(); + } + }; + } +} diff --git a/src/main/java/com/ebremer/halcyon/server/keycloak/ServerProperties.java b/src/main/java/com/ebremer/halcyon/server/keycloak/ServerProperties.java new file mode 100644 index 00000000..1bc7f848 --- /dev/null +++ b/src/main/java/com/ebremer/halcyon/server/keycloak/ServerProperties.java @@ -0,0 +1,6 @@ +package com.ebremer.halcyon.server.keycloak; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "keycloak.server") +public record ServerProperties(String contextPath, String username, String password) {} diff --git a/src/main/java/com/ebremer/halcyon/server/keycloak/providers/JsonProviderFactory.java b/src/main/java/com/ebremer/halcyon/server/keycloak/providers/JsonProviderFactory.java new file mode 100644 index 00000000..4e90f3a8 --- /dev/null +++ b/src/main/java/com/ebremer/halcyon/server/keycloak/providers/JsonProviderFactory.java @@ -0,0 +1,34 @@ +package com.ebremer.halcyon.server.keycloak.providers; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import javax.servlet.ServletContext; +import org.keycloak.common.util.Resteasy; +import org.keycloak.common.util.SystemEnvProperties; +import org.keycloak.services.util.JsonConfigProviderFactory; +import org.keycloak.util.JsonSerialization; + +public class JsonProviderFactory extends JsonConfigProviderFactory { + + public static final String SERVER_CONTEXT_CONFIG_PROPERTY_OVERRIDES = "keycloak.server.context.config.property-overrides"; + + @Override + protected Properties getProperties() { + return new SystemEnvProperties(getPropertyOverrides()); + } + + private Map getPropertyOverrides() { + final var context = Resteasy.getContextData(ServletContext.class); + final var propertyOverridesMap = new HashMap(); + final var propertyOverrides = context.getInitParameter(SERVER_CONTEXT_CONFIG_PROPERTY_OVERRIDES); + try { + if (context.getInitParameter(SERVER_CONTEXT_CONFIG_PROPERTY_OVERRIDES) != null) { + final var jsonObj = JsonSerialization.mapper.readTree(propertyOverrides); + jsonObj.fields().forEachRemaining(e -> propertyOverridesMap.put(e.getKey(), e.getValue().asText())); + } + } catch (IOException e) {} + return propertyOverridesMap; + } +} diff --git a/src/main/java/com/ebremer/halcyon/server/keycloak/providers/SimplePlatformProvider.java b/src/main/java/com/ebremer/halcyon/server/keycloak/providers/SimplePlatformProvider.java new file mode 100644 index 00000000..83bf40f1 --- /dev/null +++ b/src/main/java/com/ebremer/halcyon/server/keycloak/providers/SimplePlatformProvider.java @@ -0,0 +1,75 @@ +package com.ebremer.halcyon.server.keycloak.providers; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import org.keycloak.Config.Scope; +import org.keycloak.common.Profile; +import org.keycloak.common.profile.PropertiesFileProfileConfigResolver; +import org.keycloak.common.profile.PropertiesProfileConfigResolver; +import org.keycloak.platform.PlatformProvider; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SimplePlatformProvider implements PlatformProvider { + + private File tmpDir; + + public SimplePlatformProvider() { + Profile.configure( + new PropertiesProfileConfigResolver(System.getProperties()), + new PropertiesFileProfileConfigResolver()); + } + + @Override + public String name() { + return "springboot-keycloak-server"; + } + + @Override + public void onStartup(Runnable startupHook) { + startupHook.run(); + } + + @Override + public void onShutdown(Runnable shutdownHook) { + } + + @Override + public void exit(Throwable cause) { + throw new RuntimeException(cause); + } + + @Override + public File getTmpDirectory() { + if (tmpDir == null) { + final var projectBuildDir = System.getProperty("project.build.directory"); + File tmpDir; + if (projectBuildDir != null) { + tmpDir = new File(projectBuildDir, "server-tmp"); + tmpDir.mkdir(); + } else { + try { + tmpDir = Files.createTempDirectory("keycloak-server-").toFile(); + tmpDir.deleteOnExit(); + } catch (IOException ioe) { + throw new RuntimeException("Could not create temporary directory", ioe); + } + } + if (tmpDir.isDirectory()) { + this.tmpDir = tmpDir; + log.info("Using server tmp directory: {}", tmpDir.getAbsolutePath()); + } else { + throw new RuntimeException("Directory " + tmpDir + " was not created and does not exists"); + } + } + return tmpDir; + } + + @Override + public ClassLoader getScriptEngineClassLoader(Scope scriptProviderConfig) { + return null; + } + +} \ No newline at end of file diff --git a/src/main/java/com/ebremer/halcyon/server/resteasy/Resteasy4Provider.java b/src/main/java/com/ebremer/halcyon/server/resteasy/Resteasy4Provider.java new file mode 100644 index 00000000..756bb71c --- /dev/null +++ b/src/main/java/com/ebremer/halcyon/server/resteasy/Resteasy4Provider.java @@ -0,0 +1,32 @@ +package com.ebremer.halcyon.server.resteasy; + +import org.jboss.resteasy.core.ResteasyContext; +import org.jboss.resteasy.spi.ResteasyProviderFactory; +import org.keycloak.common.util.ResteasyProvider; + +@SuppressWarnings({"unchecked", "rawtypes"}) +public class Resteasy4Provider implements ResteasyProvider { + + @Override + public R getContextData(Class type) { + return ResteasyProviderFactory.getInstance().getContextData(type); + } + + @Override + public void pushDefaultContextObject(Class type, Object instance) { + ResteasyProviderFactory.getInstance() + .getContextData(org.jboss.resteasy.spi.Dispatcher.class) + .getDefaultContextObjects() + .put(type, instance); + } + + @Override + public void pushContext(Class type, Object instance) { + ResteasyContext.pushContext(type, instance); + } + + @Override + public void clearContextData() { + ResteasyContext.clearContextData(); + } +} diff --git a/src/main/java/com/ebremer/halcyon/wicket/AccountPage.java b/src/main/java/com/ebremer/halcyon/wicket/AccountPage.java index e902fe89..f02d9755 100644 --- a/src/main/java/com/ebremer/halcyon/wicket/AccountPage.java +++ b/src/main/java/com/ebremer/halcyon/wicket/AccountPage.java @@ -1,5 +1,6 @@ package com.ebremer.halcyon.wicket; +import com.ebremer.halcyon.HalcyonSettings; import org.apache.wicket.markup.ComponentTag; import org.apache.wicket.markup.html.WebMarkupContainer; @@ -14,7 +15,7 @@ public AccountPage() { @Override public void onComponentTag(ComponentTag tag) { super.onComponentTag(tag); - tag.put("src", "/auth/realms/Halcyon/account"); + tag.put("src", "/auth/realms/"+HalcyonSettings.realm+"/account"); } }); diff --git a/src/main/java/com/ebremer/halcyon/wicket/MenuPanel.java b/src/main/java/com/ebremer/halcyon/wicket/MenuPanel.java index b4d6e03d..96343dbd 100644 --- a/src/main/java/com/ebremer/halcyon/wicket/MenuPanel.java +++ b/src/main/java/com/ebremer/halcyon/wicket/MenuPanel.java @@ -62,6 +62,7 @@ public void onClick() { revisionhistory.setVisible(true); login.setVisible(false); logout.setVisible(true); + //account.setVisible(true); sparql.setVisible(true); hp.getGroups().forEach(k->{ System.out.println("GROUP : "+k); diff --git a/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 00000000..b1f1df27 --- /dev/null +++ b/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,22 @@ +{"properties": [ + { + "name": "keycloak.server.context-path", + "type": "java.lang.String", + "description": "Context path for Keycloak's server" + }, + { + "name": "keycloak.server.username", + "type": "java.lang.String", + "description": "Master administrator user name" + }, + { + "name": "keycloak.server.password", + "type": "java.lang.String", + "description": "Master administrator password" + }, + { + "name": "keycloak.server.context-redirect", + "type": "java.lang.String", + "description": "Defines if the / redirects or not to /context'" + } +]} \ No newline at end of file diff --git a/src/main/resources/META-INF/keycloak-server.json b/src/main/resources/META-INF/keycloak-server.json new file mode 100644 index 00000000..b33c938f --- /dev/null +++ b/src/main/resources/META-INF/keycloak-server.json @@ -0,0 +1,330 @@ +{ + + "hostname": { + "provider": "${keycloak.hostname.provider:}", + + "default": { + "frontendUrl": "${keycloak.frontendUrl:}", + "adminUrl": "${keycloak.adminUrl:}", + "forceBackendUrlToFrontendUrl": "${keycloak.hostname.default.forceBackendUrlToFrontendUrl:}" + } + }, + + "eventsStore": { + "provider": "${keycloak.eventsStore.provider:jpa}", + "jpa": { + "max-detail-length": "${keycloak.eventsStore.maxDetailLength:1000}" + }, + "map": { + "storage-admin-events": { + "provider": "${keycloak.adminEventsStore.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" + }, + "storage-auth-events": { + "provider": "${keycloak.authEventsStore.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" + } + } + }, + + "deploymentState": { + "provider": "${keycloak.deploymentState.provider:jpa}", + "map": { + "resourcesVersionSeed": "1JZ379bzyOCFA" + } + }, + + "globalLock": { + "provider": "${keycloak.globalLock.provider:dblock}", + "map": { + "storage": { + "provider": "${keycloak.lock.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" + } + } + }, + + "realm": { + "provider": "${keycloak.realm.provider:jpa}", + "map": { + "storage": { + "provider": "${keycloak.realm.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" + } + } + }, + + "client": { + "provider": "${keycloak.client.provider:jpa}", + "map": { + "storage": { + "provider": "${keycloak.client.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" + } + } + }, + + "clientScope": { + "provider": "${keycloak.clientScope.provider:jpa}", + "map": { + "storage": { + "provider": "${keycloak.clientScope.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" + } + } + }, + + "group": { + "provider": "${keycloak.group.provider:jpa}", + "map": { + "storage": { + "provider": "${keycloak.group.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" + } + } + }, + + "role": { + "provider": "${keycloak.role.provider:jpa}", + "map": { + "storage": { + "provider": "${keycloak.role.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" + } + } + }, + + "authenticationSessions": { + "provider": "${keycloak.authSession.provider:infinispan}", + "map": { + "storage": { + "provider": "${keycloak.authSession.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" + } + }, + "infinispan": { + "authSessionsLimit": "${keycloak.authSessions.limit:300}" + } + }, + + "userSessions": { + "provider": "${keycloak.userSession.provider:infinispan}", + "map": { + "storage": { + "provider": "${keycloak.userSession.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" + } + } + }, + + "loginFailure": { + "provider": "${keycloak.loginFailure.provider:infinispan}", + "map": { + "storage": { + "provider": "${keycloak.loginFailure.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" + } + } + }, + + "singleUseObject": { + "provider": "${keycloak.singleUseObject.provider:infinispan}", + "map": { + "storage": { + "provider": "${keycloak.singleUseObject.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" + } + } + }, + + "publicKeyStorage": { + "provider": "${keycloak.publicKeyStorage.provider:infinispan}", + "map": { + "storage": { + "provider": "${keycloak.publicKeyStorage.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" + } + } + }, + + "mapStorage": { + "provider": "${keycloak.mapStorage.provider:}", + "concurrenthashmap": { + "dir": "${project.build.directory:target/map}", + "keyType.single-use-objects": "string", + "keyType.realms": "string", + "keyType.authz-resource-servers": "string" + }, + "jpa": { + "url": "${keycloak.map.storage.connectionsJpa.url:}", + "user": "${keycloak.map.storage.connectionsJpa.user:}", + "password": "${keycloak.map.storage.connectionsJpa.password:}", + "driver": "org.postgresql.Driver", + "driverDialect": "org.keycloak.models.map.storage.jpa.hibernate.dialect.JsonbPostgreSQL95Dialect", + "showSql": "${keycloak.map.storage.connectionsJpa,showSql:false}" + }, + "ldap-map-storage": { + "vendor": "other", + "usernameLDAPAttribute": "uid", + "rdnLDAPAttribute": "uid", + "uuidLDAPAttribute": "entryUUID", + "userObjectClasses": "inetOrgPerson, organizationalPerson", + "connectionUrl": "${keycloak.map.storage.ldap.connectionUrl:}", + "usersDn": "ou=People,dc=keycloak,dc=org", + "bindDn": "${keycloak.map.storage.ldap.bindDn:}", + "bindCredential": "${keycloak.map.storage.ldap.bindCredential:}", + "roles.realm.dn": "ou=RealmRoles,dc=keycloak,dc=org", + "roles.common.dn": "dc=keycloak,dc=org", + "roles.client.dn": "ou={0},dc=keycloak,dc=org", + "membership.ldap.attribute": "member", + "role.name.ldap.attribute": "cn", + "role.object.classes": "groupOfNames", + "role.attributes": "ou", + "mode": "LDAP_ONLY", + "use.realm.roles.mapping": "true", + "connectionPooling": "true" + }, + "file": { + "dir": "${keycloak.group.map.storage.provider.directory:target/file}" + } + }, + + "user": { + "provider": "${keycloak.user.provider:jpa}", + "map": { + "storage": { + "provider": "${keycloak.user.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" + } + } + }, + + "userFederatedStorage": { + "provider": "${keycloak.userFederatedStorage.provider:}" + }, + + "userSessionPersister": { + "provider": "${keycloak.userSessionPersister.provider:}" + }, + + "authorizationPersister": { + "provider": "${keycloak.authorization.provider:jpa}", + "map": { + "storage": { + "provider": "${keycloak.authorization.map.storage.provider,keycloak.mapStorage.provider.default:concurrenthashmap}" + } + } + }, + + "theme": { + "staticMaxAge": "${keycloak.theme.staticMaxAge:}", + "cacheTemplates": "${keycloak.theme.cacheTemplates:}", + "cacheThemes": "${keycloak.theme.cacheThemes:}", + "folder": { + "dir": "${keycloak.theme.dir}" + } + }, + + "connectionsJpa": { + "default": { + "url": "${keycloak.connectionsJpa.url:jdbc:h2:file:./data/keycloak;DB_CLOSE_ON_EXIT=FALSE}", + "driver": "${keycloak.connectionsJpa.driver:org.h2.Driver}", + "driverDialect": "${keycloak.connectionsJpa.driverDialect:}", + "user": "${keycloak.connectionsJpa.user:sa}", + "password": "${keycloak.connectionsJpa.password:}", + "showSql": "${keycloak.connectionsJpa.showSql:}", + "formatSql": "${keycloak.connectionsJpa.formatSql:}", + "globalStatsInterval": "${keycloak.connectionsJpa.globalStatsInterval:}" + } + }, + + "realmCache": { + "default" : { + "enabled": "${keycloak.realmCache.enabled:true}" + } + }, + + "userCache": { + "default" : { + "enabled": "${keycloak.userCache.enabled:true}" + }, + "mem": { + "maxSize": 20000 + } + }, + + "publicKeyCache": { + "default" : { + "enabled": "${keycloak.publicKeyCache.enabled:true}" + } + }, + + "authorizationCache": { + "default": { + "enabled": "${keycloak.authorizationCache.enabled:true}" + } + }, + + "connectionsInfinispan": { + "default": { + "jgroupsUdpMcastAddr": "${keycloak.connectionsInfinispan.jgroupsUdpMcastAddr:234.56.78.90}", + "nodeName": "${keycloak.connectionsInfinispan.nodeName,jboss.node.name:}", + "siteName": "${keycloak.connectionsInfinispan.siteName,jboss.site.name:}", + "clustered": "${keycloak.connectionsInfinispan.clustered:}", + "async": "${keycloak.connectionsInfinispan.async:}", + "sessionsOwners": "${keycloak.connectionsInfinispan.sessionsOwners:}", + "l1Lifespan": "${keycloak.connectionsInfinispan.l1Lifespan:}", + "remoteStoreEnabled": "${keycloak.connectionsInfinispan.remoteStoreEnabled:}", + "remoteStoreHost": "${keycloak.connectionsInfinispan.remoteStoreServer:}", + "remoteStorePort": "${keycloak.connectionsInfinispan.remoteStorePort:}", + "hotrodProtocolVersion": "${keycloak.connectionsInfinispan.hotrodProtocolVersion}", + "embedded": "${keycloak.connectionsInfinispan.embedded:true}" + } + }, + + "connectionsHotRod": { + "default": { + "embedded": "${keycloak.connectionsHotRod.embedded:true}", + "embeddedPort": "${keycloak.connectionsHotRod.embeddedPort:11444}", + "enableSecurity": "${keycloak.connectionsHotRod.enableSecurity:false}", + "port": "${keycloak.connectionsHotRod.port:11444}", + "host": "${keycloak.connectionsHotRod.host:localhost}", + "configureRemoteCaches": "${keycloak.connectionsHotRod.configureRemoteCaches:false}", + "username": "${keycloak.connectionsHotRod.username:admin}", + "password": "${keycloak.connectionsHotRod.password:admin}", + "reindexCaches": "${keycloak.connectionsHotRod.reindexCaches:}" + } + }, + "scripting": { + }, + + "jta-lookup": { + "provider": "${keycloak.jta.lookup.provider:}" + }, + + "login-protocol": { + "openid-connect": { + "legacy-logout-redirect-uri": "${keycloak.oidc.legacyLogoutRedirectUri:false}" + }, + "saml": { + "knownProtocols": [ + "http=${auth.server.http.port}", + "https=${auth.server.https.port}" + ] + } + }, + + "userProfile": { + "provider": "${keycloak.userProfile.provider:declarative-user-profile}", + "declarative-user-profile": { + "read-only-attributes": [ "deniedFoo", "deniedBar*", "deniedSome/thing", "deniedsome*thing" ], + "admin-read-only-attributes": [ "deniedSomeAdmin" ] + } + }, + + "x509cert-lookup": { + "provider": "${keycloak.x509cert.lookup.provider:}", + "haproxy": { + "sslClientCert": "x-ssl-client-cert", + "sslCertChainPrefix": "x-ssl-client-cert-chain", + "certificateChainLength": 1 + }, + "apache": { + "sslClientCert": "x-ssl-client-cert", + "sslCertChainPrefix": "x-ssl-client-cert-chain", + "certificateChainLength": 1 + }, + "nginx": { + "sslClientCert": "x-ssl-client-cert", + "sslCertChainPrefix": "x-ssl-client-cert-chain", + "certificateChainLength": 1 + } + } + +} diff --git a/src/main/resources/META-INF/keycloak-themes.json b/src/main/resources/META-INF/keycloak-themes.json new file mode 100644 index 00000000..08bb9399 --- /dev/null +++ b/src/main/resources/META-INF/keycloak-themes.json @@ -0,0 +1,10 @@ +{ + "themes": [ + { + "name": "providers-only", + "types": [ + "login" + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/META-INF/services/org.keycloak.common.util.ResteasyProvider b/src/main/resources/META-INF/services/org.keycloak.common.util.ResteasyProvider new file mode 100644 index 00000000..965db7ae --- /dev/null +++ b/src/main/resources/META-INF/services/org.keycloak.common.util.ResteasyProvider @@ -0,0 +1 @@ +com.ebremer.halcyon.server.resteasy.Resteasy4Provider \ No newline at end of file diff --git a/src/main/resources/META-INF/services/org.keycloak.config.ConfigProviderFactory b/src/main/resources/META-INF/services/org.keycloak.config.ConfigProviderFactory new file mode 100644 index 00000000..b6135a43 --- /dev/null +++ b/src/main/resources/META-INF/services/org.keycloak.config.ConfigProviderFactory @@ -0,0 +1 @@ +com.ebremer.halcyon.server.keycloak.providers.JsonProviderFactory \ No newline at end of file diff --git a/src/main/resources/META-INF/services/org.keycloak.platform.PlatformProvider b/src/main/resources/META-INF/services/org.keycloak.platform.PlatformProvider new file mode 100644 index 00000000..a07af0c9 --- /dev/null +++ b/src/main/resources/META-INF/services/org.keycloak.platform.PlatformProvider @@ -0,0 +1 @@ +com.ebremer.halcyon.server.keycloak.providers.SimplePlatformProvider \ No newline at end of file diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories deleted file mode 100644 index 5e7ab019..00000000 --- a/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,2 +0,0 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ - com.ebremer.halcyon.EmbeddedSpringKeycloakAutoConfiguration diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 6a819120..75a0db96 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,156 +1,37 @@ spring: + h2: + console: + enabled: true + path: /h2 datasource: username: sa - url: jdbc:h2:./data/keycloak;DB_CLOSE_ON_EXIT=FALSE - hikari: - maximum-pool-size: 25 - minimum-idle: 1 - -server: - forward-headers-strategy: native - port: 8888 - servlet: - context-path: "/" + password: + url: jdbc:h2:file:./data/keycloak;DB_CLOSE_ON_EXIT=FALSE + driver-class-name: org.h2.Driver + jpa: + database-platform: org.hibernate.dialect.H2Dialect + hibernate: + ddl-auto: update + +keycloak: + server: + context-path: /auth + username: admin + password: admin + context-redirect: true + + migration: + importProvider: singleFile + importLocation: keycloak-realm-config.json logging: level: - org.jgroups: INFO - org.infinispan: INFO - org.keycloak: INFO - org.keycloak.services.managers.DefaultBruteForceProtector: DEBUG - org.keycloak.services.scheduled.ClusterAwareScheduledTaskRunner: DEBUG - org.keycloak.services.managers.UserSessionManager: DEBUG - org.keycloak.timer.basic: DEBUG - org.apache.wicket.Application: INFO - com.ebremer.halcyon.wicket.ListImages: INFO - com.ebremer.halcyon.gui.HalcyonApplication: INFO - -keycloak: - custom: - server: - keycloak-path: "/auth" - - adminUser: - username: admin - password: admin - create-admin-user-enabled: true -# -# migration: -# importProvider: singleFile -# importLocation: keycloak-realm-config.json - # -Dkeycloak.migration.strategy=OVERWRITE_EXISTING - # -Dkeycloak.migration.strategy=IGNORE_EXISTING -# hostname: -# provider: "default" -# default: -# frontendUrl: "" -# adminUrl: "" -# forceBackendUrlToFrontendUrl: false -# -# admin: -# realm: "master" -# -# eventsStore: -# provider: "jpa" -# -# eventsListener: -# "jboss-logging": -# "success-level": "info" -# "error-level": "warn" -# -# realm: -# provider: "jpa" -# -# user: -# provider: "jpa" -# -# userFederatedStorage: -# provider: "jpa" -# -# userSessionPersister: -# provider: "jpa" -# -# authorizationPersister: -# provider: "jpa" -# -# userCache: -# default: -# enabled: true -# -# timer: -# provider: "basic" -# -# theme: -# staticMaxAge: "2592000" -# cacheTemplates: true -# cacheThemes: true -# folder: -# dir: "" -# -# scheduled: -# interval: 900 -# -# connectionsHttpClient: -# default: {} -# -# connectionsJpa: -# provider: "default" -# default: -# dataSource: "spring/datasource" -# initializeEmpty: true -# migrationStrategy: "update" -# showSql: false -# formatSql: true -# globalStatsInterval: -1 -# -# realmCache: -# default: -# enabled: true -# -# connectionsInfinispan: -# default: -# jgroupsUdpMcastAddr: "234.56.78.90" -# nodeName: "localhost" -# siteName: "" -# clustered: fase -# async: false -# sessionsOwners: 1 -# l1Lifespan: 600000 -# remoteStoreEnabled: false -# remoteStoreHost: "localhost" -# remoteStorePort: 11222 -# hotrodProtocolVersion: "" -# -# scripting: {} -# -# "jta-lookup": -# provider: "jboss" -# jboss: -# enabled: true -# -# "login-protocol": -# "saml": -# "knownProtocols": ["http=${server.port}", "https=${server.port}"] -# -# "x509cert-lookup": -# provider: "default" -# default: -# enabled: true -# -# haproxy: -# enabled: true -# sslClientCert: "x-ssl-client-cert" -# sslCertChainPrefix: "x-ssl-client-cert-chain" -# certificateChainLength: 1 -# -# apache: -# enabled: true -# sslClientCert: "x-ssl-client-cert" -# sslCertChainPrefix: "x-ssl-client-cert-chain" -# certificateChainLength: 1 -# -# nginx: -# enabled: true -# sslClientCert: "x-ssl-client-cert" -# sslCertChainPrefix: "x-ssl-client-cert-chain" -# certificateChainLength: 1 + root: ERROR + #org.jgroups: TRACE + #org.infinispan: INFO + #org.keycloak: DEBUG + #org.keycloak.services.managers.DefaultBruteForceProtector: WARN + #org.keycloak.services.scheduled.ClusterAwareScheduledTaskRunner: ERROR + #org.keycloak.services.managers.UserSessionManager: ERROR + #org.keycloak.timer.basic: ERROR + #org.apache.wicket.Application: ERROR diff --git a/src/main/resources/infinispan.xml b/src/main/resources/infinispan.xml deleted file mode 100644 index bb7d35e7..00000000 --- a/src/main/resources/infinispan.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/jgroups.xml b/src/main/resources/jgroups.xml deleted file mode 100644 index 34982a85..00000000 --- a/src/main/resources/jgroups.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/jgroups_dns.xml b/src/main/resources/jgroups_dns.xml deleted file mode 100644 index 5f65d0db..00000000 --- a/src/main/resources/jgroups_dns.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/keycloak - Copy.json b/src/main/resources/keycloak - Copy.json new file mode 100644 index 00000000..1670e250 --- /dev/null +++ b/src/main/resources/keycloak - Copy.json @@ -0,0 +1,10 @@ +{ + "realm": "master", + "auth-server-url": "http://localhost:8888/auth/", + "ssl-required": "external", + "resource": "account", + "public-client": true, + "verify-token-audience": true, + "use-resource-role-mappings": true, + "confidential-port": 0 +} \ No newline at end of file diff --git a/src/main/resources/keycloak-realm-config.json b/src/main/resources/keycloak-realm-config.json index d5cb38ee..faaabe7a 100644 --- a/src/main/resources/keycloak-realm-config.json +++ b/src/main/resources/keycloak-realm-config.json @@ -1,16 +1,16 @@ { - "id": "master", - "realm": "master", - "displayName": "Halcyon", + "id": "d09e261f-309c-4d66-bf0e-674ce19772fd", + "realm": "Halcyon", + "displayName": "H A L C Y O N", "displayNameHtml": "H A L C Y O N", "notBefore": 0, "defaultSignatureAlgorithm": "RS256", "revokeRefreshToken": false, "refreshTokenMaxReuse": 0, - "accessTokenLifespan": 60, + "accessTokenLifespan": 7200, "accessTokenLifespanForImplicitFlow": 900, - "ssoSessionIdleTimeout": 86400, - "ssoSessionMaxLifespan": 604800, + "ssoSessionIdleTimeout": 7200, + "ssoSessionMaxLifespan": 36000, "ssoSessionIdleTimeoutRememberMe": 0, "ssoSessionMaxLifespanRememberMe": 0, "offlineSessionIdleTimeout": 2592000, @@ -26,11 +26,11 @@ "actionTokenGeneratedByAdminLifespan": 43200, "actionTokenGeneratedByUserLifespan": 300, "oauth2DeviceCodeLifespan": 600, - "oauth2DevicePollingInterval": 600, + "oauth2DevicePollingInterval": 5, "enabled": true, - "sslRequired": "none", + "sslRequired": "external", "registrationAllowed": false, - "registrationEmailAsUsername": true, + "registrationEmailAsUsername": false, "rememberMe": false, "verifyEmail": false, "loginWithEmailAllowed": true, @@ -45,31 +45,11 @@ "quickLoginCheckMilliSeconds": 1000, "maxDeltaTimeSeconds": 43200, "failureFactor": 30, - "users": [ - { - "username": "admin", - "firstName": "firstName", - "lastName": "lastName", - "enabled": true, - "groups": [ - "/admin" - ], - "credentials": [ - { - "type": "password", - "value": "admin" - } - ], - "realmRoles": [ - "admin" - ] - } - ], "roles": { "realm": [ { - "id": "db43a81e-55f0-495f-bb10-b2897886e6c1", - "name": "default-roles-master", + "id": "28262476-5ea7-4b58-979e-99e356636c2c", + "name": "default-roles-halcyon", "description": "${role_default-roles}", "composite": true, "composites": { @@ -85,270 +65,294 @@ } }, "clientRole": false, - "containerId": "master", + "containerId": "d09e261f-309c-4d66-bf0e-674ce19772fd", "attributes": {} }, { - "id": "ba948bb5-7632-4efc-bf3f-840474d00fd5", - "name": "create-realm", - "description": "${role_create-realm}", + "id": "e269ed61-5559-44b5-baf4-61c0e698fa4c", + "name": "uma_authorization", + "description": "${role_uma_authorization}", "composite": false, "clientRole": false, - "containerId": "master", + "containerId": "d09e261f-309c-4d66-bf0e-674ce19772fd", "attributes": {} }, { - "id": "a7d3d99d-e84c-4116-a334-c9608bc76d85", + "id": "3d32afc4-f3f1-4372-9a69-173c0324a9f8", "name": "offline_access", "description": "${role_offline-access}", "composite": false, "clientRole": false, - "containerId": "master", - "attributes": {} - }, - { - "id": "9eb710dd-4d98-40e0-ad73-162102401e4b", - "name": "admin", - "description": "${role_admin}", - "composite": true, - "composites": { - "realm": [ - "create-realm" - ], - "client": { - "master-realm": [ - "view-clients", - "manage-events", - "view-realm", - "query-clients", - "manage-realm", - "manage-identity-providers", - "view-identity-providers", - "manage-users", - "impersonation", - "view-authorization", - "query-realms", - "view-events", - "manage-authorization", - "query-groups", - "create-client", - "view-users", - "manage-clients", - "query-users" - ] - } - }, - "clientRole": false, - "containerId": "master", - "attributes": {} - }, - { - "id": "1d1d6634-162d-4e1f-9120-d31b7adae4d3", - "name": "uma_authorization", - "description": "${role_uma_authorization}", - "composite": false, - "clientRole": false, - "containerId": "master", + "containerId": "d09e261f-309c-4d66-bf0e-674ce19772fd", "attributes": {} } ], "client": { - "security-admin-console": [], - "admin-cli": [], - "account-console": [], - "broker": [ - { - "id": "ccc1d2f3-9a90-47dc-8821-939ac7aa9917", - "name": "read-token", - "description": "${role_read-token}", - "composite": false, - "clientRole": true, - "containerId": "add69182-4588-4087-a1e6-0d9a59d8d81c", - "attributes": {} - } - ], - "master-realm": [ - { - "id": "4d681622-fb1b-448c-a14f-757885ae177c", - "name": "view-clients", - "description": "${role_view-clients}", - "composite": true, - "composites": { - "client": { - "master-realm": [ - "query-clients" - ] - } - }, - "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", - "attributes": {} - }, + "realm-management": [ { - "id": "18d8b8d6-4b9c-4e62-8a9f-7cbb74b59e3c", - "name": "manage-events", - "description": "${role_manage-events}", + "id": "c0ed3c3b-f0b6-4382-8720-016b0120fd16", + "name": "manage-clients", + "description": "${role_manage-clients}", "composite": false, "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", + "containerId": "9ae99474-46f8-46f1-8d03-66b5e3e707ea", "attributes": {} }, { - "id": "04ecb1b3-b44b-44d5-bcff-62b19a285e6c", - "name": "view-realm", - "description": "${role_view-realm}", + "id": "bae0c095-2198-48d2-a8ef-bceec8c6c3ab", + "name": "query-groups", + "description": "${role_query-groups}", "composite": false, "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", + "containerId": "9ae99474-46f8-46f1-8d03-66b5e3e707ea", "attributes": {} }, { - "id": "97c45eba-c008-4a4a-800d-6d36f3f17b5c", - "name": "query-clients", - "description": "${role_query-clients}", + "id": "b41eb93c-c510-482f-9860-f087cb876060", + "name": "create-client", + "description": "${role_create-client}", "composite": false, "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", + "containerId": "9ae99474-46f8-46f1-8d03-66b5e3e707ea", "attributes": {} }, { - "id": "8790557a-5269-4177-b8d7-2961e64e9e3c", - "name": "manage-realm", - "description": "${role_manage-realm}", + "id": "fdd41ebb-eb2c-4521-a15b-5815f475a9d3", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", "composite": false, "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", + "containerId": "9ae99474-46f8-46f1-8d03-66b5e3e707ea", "attributes": {} }, { - "id": "559dc49f-eed9-4d70-a12b-88ce32745933", - "name": "manage-identity-providers", - "description": "${role_manage-identity-providers}", + "id": "0ecb18a7-b1d4-47da-9c7a-429b6f0f2cc6", + "name": "manage-users", + "description": "${role_manage-users}", "composite": false, "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", + "containerId": "9ae99474-46f8-46f1-8d03-66b5e3e707ea", "attributes": {} }, { - "id": "46074624-f2e9-4e7f-b3b2-04636bfb93fb", + "id": "a050d11f-1fc4-4a1d-9da1-78d8955d3191", "name": "view-identity-providers", "description": "${role_view-identity-providers}", "composite": false, "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", + "containerId": "9ae99474-46f8-46f1-8d03-66b5e3e707ea", "attributes": {} }, { - "id": "21ea52fd-3c7d-460c-b3ce-c0360caf7c52", - "name": "manage-users", - "description": "${role_manage-users}", + "id": "7e8a3690-2876-427d-8475-c900d260b318", + "name": "query-clients", + "description": "${role_query-clients}", "composite": false, "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", + "containerId": "9ae99474-46f8-46f1-8d03-66b5e3e707ea", "attributes": {} }, { - "id": "791dcc5f-9aae-41fa-a36b-b3c7bcb175a6", - "name": "impersonation", - "description": "${role_impersonation}", + "id": "027e6a07-d8b3-49fe-aa25-515fb304b084", + "name": "view-realm", + "description": "${role_view-realm}", "composite": false, "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", + "containerId": "9ae99474-46f8-46f1-8d03-66b5e3e707ea", "attributes": {} }, { - "id": "aad4a9a5-00d5-4de3-894f-e9b5ad99bc28", - "name": "query-realms", - "description": "${role_query-realms}", - "composite": false, + "id": "fb24dcfc-b731-493d-a1bb-3506093bacb8", + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "manage-clients", + "query-groups", + "manage-users", + "view-identity-providers", + "create-client", + "manage-identity-providers", + "view-realm", + "query-clients", + "view-authorization", + "view-clients", + "manage-realm", + "query-realms", + "impersonation", + "view-users", + "view-events", + "manage-authorization", + "query-users", + "manage-events" + ] + } + }, "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", + "containerId": "9ae99474-46f8-46f1-8d03-66b5e3e707ea", "attributes": {} }, { - "id": "54b94dcf-b2ac-4a3d-97f8-9b50f8326fc6", + "id": "a4fe0ecd-8961-4ea7-b7c7-ccd2a77669bf", "name": "view-authorization", "description": "${role_view-authorization}", "composite": false, "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", + "containerId": "9ae99474-46f8-46f1-8d03-66b5e3e707ea", "attributes": {} }, { - "id": "dfdd2271-3739-41d7-b088-8954bf0b91ed", - "name": "view-events", - "description": "${role_view-events}", - "composite": false, + "id": "57198c07-8861-4517-8781-c95bbed06220", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-clients" + ] + } + }, "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", + "containerId": "9ae99474-46f8-46f1-8d03-66b5e3e707ea", "attributes": {} }, { - "id": "cbc59c22-d07b-4606-8406-c75b63b32413", - "name": "manage-authorization", - "description": "${role_manage-authorization}", + "id": "e690a244-db59-4c02-b259-a674d7df0f14", + "name": "manage-realm", + "description": "${role_manage-realm}", "composite": false, "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", + "containerId": "9ae99474-46f8-46f1-8d03-66b5e3e707ea", "attributes": {} }, { - "id": "d1374727-f085-41a1-8ae8-f675f5c0fcf5", - "name": "query-groups", - "description": "${role_query-groups}", + "id": "ddf18ccc-4d44-4e58-83bb-69ee28699bc2", + "name": "query-realms", + "description": "${role_query-realms}", "composite": false, "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", + "containerId": "9ae99474-46f8-46f1-8d03-66b5e3e707ea", "attributes": {} }, { - "id": "1afc8117-cf6c-4e35-8427-ba40fae6431d", - "name": "create-client", - "description": "${role_create-client}", + "id": "fcf57ea8-cd92-4544-a35b-94a8145d5171", + "name": "impersonation", + "description": "${role_impersonation}", "composite": false, "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", + "containerId": "9ae99474-46f8-46f1-8d03-66b5e3e707ea", "attributes": {} }, { - "id": "87ca302d-9010-402a-a4d2-1d31c0e19613", + "id": "2e18d2a6-fb8a-4e67-bb45-cc7951261d3a", "name": "view-users", "description": "${role_view-users}", "composite": true, "composites": { "client": { - "master-realm": [ + "realm-management": [ "query-groups", "query-users" ] } }, "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", + "containerId": "9ae99474-46f8-46f1-8d03-66b5e3e707ea", "attributes": {} }, { - "id": "28977c95-a4c1-4400-a828-36c626026298", - "name": "manage-clients", - "description": "${role_manage-clients}", + "id": "e0da81ef-8b9a-4e46-b8c4-0b89f7fcd8f3", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "9ae99474-46f8-46f1-8d03-66b5e3e707ea", + "attributes": {} + }, + { + "id": "dd5f40bb-6c19-4906-95bf-6e3246d7025c", + "name": "manage-authorization", + "description": "${role_manage-authorization}", "composite": false, "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", + "containerId": "9ae99474-46f8-46f1-8d03-66b5e3e707ea", "attributes": {} }, { - "id": "bc60194d-1c23-4a94-bb55-738d7389faab", + "id": "284ff3a5-9a4e-4827-a4d5-608c15f7f8fd", "name": "query-users", "description": "${role_query-users}", "composite": false, "clientRole": true, - "containerId": "efa03508-c135-4dc8-a471-eed108cdfc6c", + "containerId": "9ae99474-46f8-46f1-8d03-66b5e3e707ea", + "attributes": {} + }, + { + "id": "80ee2d80-11e1-418d-9a2c-8a7c936a151e", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "9ae99474-46f8-46f1-8d03-66b5e3e707ea", + "attributes": {} + } + ], + "security-admin-console": [], + "admin-cli": [], + "account-console": [], + "broker": [ + { + "id": "9150537f-ba0c-45ff-be36-4c1f624718f9", + "name": "read-token", + "description": "${role_read-token}", + "composite": false, + "clientRole": true, + "containerId": "a40be18f-9079-4610-b891-d342751f8359", "attributes": {} } ], "account": [ { - "id": "d1c18f80-771f-4df2-ade9-d70e75e32ecf", + "id": "23910ab3-5f23-420e-9420-937aeff8d30f", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "a456dc69-03b4-4949-8a54-2a637ad605c8", + "attributes": {} + }, + { + "id": "08c35da8-f26a-4119-aa2a-a5f8678f0b4c", + "name": "view-groups", + "description": "${role_view-groups}", + "composite": false, + "clientRole": true, + "containerId": "a456dc69-03b4-4949-8a54-2a637ad605c8", + "attributes": {} + }, + { + "id": "2227b291-238b-410b-9cbd-786c18428087", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "a456dc69-03b4-4949-8a54-2a637ad605c8", + "attributes": {} + }, + { + "id": "81820609-f0ec-4961-abb5-dbfbf040a50b", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "a456dc69-03b4-4949-8a54-2a637ad605c8", + "attributes": {} + }, + { + "id": "01067080-352e-4de4-b1e3-d4b417a6d19d", "name": "manage-account", "description": "${role_manage-account}", "composite": true, @@ -360,11 +364,11 @@ } }, "clientRole": true, - "containerId": "fa64a2d4-66d2-4ff6-af84-d59ede0320c0", + "containerId": "a456dc69-03b4-4949-8a54-2a637ad605c8", "attributes": {} }, { - "id": "e057f536-3ca1-4c91-bc28-4a2f77bcedcb", + "id": "0a3ea1fa-15fe-4cf2-8ef3-0326aeb948d4", "name": "manage-consent", "description": "${role_manage-consent}", "composite": true, @@ -376,52 +380,25 @@ } }, "clientRole": true, - "containerId": "fa64a2d4-66d2-4ff6-af84-d59ede0320c0", + "containerId": "a456dc69-03b4-4949-8a54-2a637ad605c8", "attributes": {} }, { - "id": "7d6c2511-1e91-41d3-bcd0-51f079dca2ee", - "name": "delete-account", - "description": "${role_delete-account}", - "composite": false, - "clientRole": true, - "containerId": "fa64a2d4-66d2-4ff6-af84-d59ede0320c0", - "attributes": {} - }, - { - "id": "69bff3e2-eae4-4b03-a400-89a1f99b4094", - "name": "view-applications", - "description": "${role_view-applications}", - "composite": false, - "clientRole": true, - "containerId": "fa64a2d4-66d2-4ff6-af84-d59ede0320c0", - "attributes": {} - }, - { - "id": "75f420a7-e7b3-48a0-a1bd-08948be9867c", - "name": "view-consent", - "description": "${role_view-consent}", + "id": "208be599-dcfa-45aa-a5cc-fbe1b2359ff7", + "name": "view-profile", + "description": "${role_view-profile}", "composite": false, "clientRole": true, - "containerId": "fa64a2d4-66d2-4ff6-af84-d59ede0320c0", + "containerId": "a456dc69-03b4-4949-8a54-2a637ad605c8", "attributes": {} }, { - "id": "d003da96-499c-400b-9d95-c6d3b29f0f76", + "id": "3c6b3873-fc2d-463a-89cb-a3984878ca4b", "name": "manage-account-links", "description": "${role_manage-account-links}", "composite": false, "clientRole": true, - "containerId": "fa64a2d4-66d2-4ff6-af84-d59ede0320c0", - "attributes": {} - }, - { - "id": "532cc3ac-1590-474c-a303-1738cd33ecaa", - "name": "view-profile", - "description": "${role_view-profile}", - "composite": false, - "clientRole": true, - "containerId": "fa64a2d4-66d2-4ff6-af84-d59ede0320c0", + "containerId": "a456dc69-03b4-4949-8a54-2a637ad605c8", "attributes": {} } ] @@ -429,7 +406,16 @@ }, "groups": [ { - "id": "90679840-5e71-4256-afde-cdb708e6c428", + "id": "dc743602-ed76-4ba4-aa03-8f146f7c677a", + "name": "Authenticated Users", + "path": "/Authenticated Users", + "attributes": {}, + "realmRoles": [], + "clientRoles": {}, + "subGroups": [] + }, + { + "id": "ff6804de-e92b-491f-8ab0-50619e62e8ef", "name": "admin", "path": "/admin", "attributes": {}, @@ -439,13 +425,16 @@ } ], "defaultRole": { - "id": "db43a81e-55f0-495f-bb10-b2897886e6c1", - "name": "default-roles-master", + "id": "28262476-5ea7-4b58-979e-99e356636c2c", + "name": "default-roles-halcyon", "description": "${role_default-roles}", "composite": true, "clientRole": false, - "containerId": "master" + "containerId": "d09e261f-309c-4d66-bf0e-674ce19772fd" }, + "defaultGroups": [ + "/Authenticated Users" + ], "requiredCredentials": [ "password" ], @@ -455,9 +444,11 @@ "otpPolicyDigits": 6, "otpPolicyLookAheadWindow": 1, "otpPolicyPeriod": 30, + "otpPolicyCodeReusable": false, "otpSupportedApplications": [ - "FreeOTP", - "Google Authenticator" + "totpAppFreeOTPName", + "totpAppMicrosoftAuthenticatorName", + "totpAppGoogleName" ], "webAuthnPolicyRpEntityName": "keycloak", "webAuthnPolicySignatureAlgorithms": [ @@ -496,24 +487,28 @@ { "client": "account-console", "roles": [ - "manage-account" + "manage-account", + "view-groups" ] } ] }, "clients": [ { - "id": "fa64a2d4-66d2-4ff6-af84-d59ede0320c0", + "id": "a456dc69-03b4-4949-8a54-2a637ad605c8", "clientId": "account", "name": "${client_account}", + "description": "", "rootUrl": "${authBaseUrl}", - "baseUrl": "/realms/master/account/", + "adminUrl": "", + "baseUrl": "/realms/Halcyon/account/", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "redirectUris": [ - "*" + "*", + "/realms/Halcyon/account/*" ], "webOrigins": [], "notBefore": 0, @@ -527,69 +522,20 @@ "frontchannelLogout": false, "protocol": "openid-connect", "attributes": { - "saml.multivalued.roles": "false", - "saml.force.post.binding": "false", - "oauth2.device.authorization.grant.enabled": "false", - "backchannel.logout.revoke.offline.tokens": "false", - "saml.server.signature.keyinfo.ext": "false", - "use.refresh.tokens": "true", "oidc.ciba.grant.enabled": "false", - "backchannel.logout.session.required": "false", - "client_credentials.use_refresh_token": "false", - "saml.client.signature": "false", - "require.pushed.authorization.requests": "false", - "saml.assertion.signature": "false", - "id.token.as.detached.signature": "false", - "saml.encrypt": "false", - "saml.server.signature": "false", - "exclude.session.state.from.auth.response": "false", - "saml.artifact.binding": "false", - "saml_force_name_id_format": "false", - "tls.client.certificate.bound.access.tokens": "false", - "acr.loa.map": "{}", - "saml.authnstatement": "false", + "backchannel.logout.session.required": "true", + "post.logout.redirect.uris": "+", + "oauth2.device.authorization.grant.enabled": "false", "display.on.consent.screen": "false", - "token.response.type.bearer.lower-case": "false", - "saml.onetimeuse.condition": "false" + "backchannel.logout.revoke.offline.tokens": "false" }, "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": true, "nodeReRegistrationTimeout": 0, "protocolMappers": [ { - "id": "7537de68-443f-43bc-94e4-46164093aed7", - "name": "email", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": false, - "config": { - "userinfo.token.claim": "true", - "user.attribute": "email", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "email", - "jsonType.label": "String" - } - }, - { - "id": "097b836d-74c8-4c32-a550-035ae515c2f4", - "name": "webid", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "userinfo.token.claim": "true", - "multivalued": "false", - "user.attribute": "webid", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "webid", - "jsonType.label": "String" - } - }, - { - "id": "ccca6b2b-0b2d-48db-b290-15da39215c45", - "name": "memberOf", + "id": "bf4da703-ad95-4ed4-a9b0-e6d77adea94d", + "name": "HalcyonGroups", "protocol": "openid-connect", "protocolMapper": "oidc-group-membership-mapper", "consentRequired": false, @@ -602,59 +548,59 @@ } }, { - "id": "adeac42c-ce72-47f2-8ab9-50d9c674fe0a", - "name": "username", + "id": "e6804aad-d77b-4e31-a0dd-fbb54853d877", + "name": "family name", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-property-mapper", "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "username", + "user.attribute": "lastName", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "preferred_username", + "claim.name": "family_name", "jsonType.label": "String" } }, { - "id": "62eab4b3-358a-423e-a898-9cfa660c2d1a", - "name": "groupwebid", + "id": "9035a6d3-adb7-41e4-8027-d7f85a540ae8", + "name": "picture", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, "config": { - "aggregate.attrs": "true", "userinfo.token.claim": "true", - "multivalued": "true", - "user.attribute": "groupwebid", + "user.attribute": "picture", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "groupwebid", + "claim.name": "picture", "jsonType.label": "String" } }, { - "id": "8c58c860-8cf7-4a6f-89e9-f98f1745d9da", - "name": "client roles", + "id": "996875b1-f0c8-49f5-b78a-8d16eacf0e7c", + "name": "given name", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-client-role-mapper", + "protocolMapper": "oidc-usermodel-property-mapper", "consentRequired": false, "config": { - "user.attribute": "foo", + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "resource_access.${client_id}.roles", - "jsonType.label": "String", - "multivalued": "true" + "claim.name": "given_name", + "jsonType.label": "String" } }, { - "id": "43549103-9c75-40ae-938c-a690f7a26bce", + "id": "afe30e1c-3427-4ca7-b243-6b4aac8a9563", "name": "groups", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-realm-role-mapper", "consentRequired": false, "config": { "multivalued": "true", + "userinfo.token.claim": "true", "user.attribute": "foo", "id.token.claim": "true", "access.token.claim": "true", @@ -665,6 +611,7 @@ ], "defaultClientScopes": [ "web-origins", + "acr", "profile", "roles", "email" @@ -677,17 +624,17 @@ ] }, { - "id": "43443219-b1ba-4bda-8295-4f0d21bf030a", + "id": "e2fadf7c-acfa-4032-8fbb-2bdc2198270c", "clientId": "account-console", "name": "${client_account-console}", "rootUrl": "${authBaseUrl}", - "baseUrl": "/realms/master/account/", + "baseUrl": "/realms/Halcyon/account/", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "redirectUris": [ - "/realms/master/account/*" + "/realms/Halcyon/account/*" ], "webOrigins": [], "notBefore": 0, @@ -701,6 +648,7 @@ "frontchannelLogout": false, "protocol": "openid-connect", "attributes": { + "post.logout.redirect.uris": "+", "pkce.code.challenge.method": "S256" }, "authenticationFlowBindingOverrides": {}, @@ -708,7 +656,7 @@ "nodeReRegistrationTimeout": 0, "protocolMappers": [ { - "id": "6ebe150a-a9e3-41e8-816e-b25a853ccaed", + "id": "0b2718d6-8f74-4287-9f0b-b3b59fe901e3", "name": "audience resolve", "protocol": "openid-connect", "protocolMapper": "oidc-audience-resolve-mapper", @@ -718,6 +666,7 @@ ], "defaultClientScopes": [ "web-origins", + "acr", "profile", "roles", "email" @@ -730,7 +679,7 @@ ] }, { - "id": "ae6950e9-7a85-4970-8cdd-4c6b2e4b8d6f", + "id": "89114b1a-805c-4a4e-89b8-9deca1eee5a8", "clientId": "admin-cli", "name": "${client_admin-cli}", "surrogateAuthRequired": false, @@ -749,12 +698,15 @@ "publicClient": true, "frontchannelLogout": false, "protocol": "openid-connect", - "attributes": {}, + "attributes": { + "post.logout.redirect.uris": "+" + }, "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, "defaultClientScopes": [ "web-origins", + "acr", "profile", "roles", "email" @@ -767,7 +719,7 @@ ] }, { - "id": "add69182-4588-4087-a1e6-0d9a59d8d81c", + "id": "a40be18f-9079-4610-b891-d342751f8359", "clientId": "broker", "name": "${client_broker}", "surrogateAuthRequired": false, @@ -786,12 +738,15 @@ "publicClient": false, "frontchannelLogout": false, "protocol": "openid-connect", - "attributes": {}, + "attributes": { + "post.logout.redirect.uris": "+" + }, "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, "defaultClientScopes": [ "web-origins", + "acr", "profile", "roles", "email" @@ -804,9 +759,9 @@ ] }, { - "id": "efa03508-c135-4dc8-a471-eed108cdfc6c", - "clientId": "master-realm", - "name": "master Realm", + "id": "9ae99474-46f8-46f1-8d03-66b5e3e707ea", + "clientId": "realm-management", + "name": "${client_realm-management}", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, @@ -822,12 +777,16 @@ "serviceAccountsEnabled": false, "publicClient": false, "frontchannelLogout": false, - "attributes": {}, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, "defaultClientScopes": [ "web-origins", + "acr", "profile", "roles", "email" @@ -840,17 +799,17 @@ ] }, { - "id": "f3b244a4-2280-4125-ac81-4abc7caebf57", + "id": "bf6ea7d2-74ea-417c-96cb-0f34c92c6a01", "clientId": "security-admin-console", "name": "${client_security-admin-console}", "rootUrl": "${authAdminUrl}", - "baseUrl": "/admin/master/console/", + "baseUrl": "/admin/Halcyon/console/", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "redirectUris": [ - "/admin/master/console/*" + "/admin/Halcyon/console/*" ], "webOrigins": [ "+" @@ -866,6 +825,7 @@ "frontchannelLogout": false, "protocol": "openid-connect", "attributes": { + "post.logout.redirect.uris": "+", "pkce.code.challenge.method": "S256" }, "authenticationFlowBindingOverrides": {}, @@ -873,7 +833,7 @@ "nodeReRegistrationTimeout": 0, "protocolMappers": [ { - "id": "7f0699b0-db56-403c-b0c3-d239e4d7cbf9", + "id": "4f2a6ae0-640b-4118-b8ae-fc11244735b7", "name": "locale", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", @@ -890,6 +850,7 @@ ], "defaultClientScopes": [ "web-origins", + "acr", "profile", "roles", "email" @@ -904,234 +865,195 @@ ], "clientScopes": [ { - "id": "77536af6-5352-42cd-aff0-9f0ed0c2e636", - "name": "address", - "description": "OpenID Connect built-in scope: address", + "id": "3c651006-a679-4282-97fc-e8d3141fceac", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", "protocol": "openid-connect", "attributes": { "include.in.token.scope": "true", "display.on.consent.screen": "true", - "consent.screen.text": "${addressScopeConsentText}" + "consent.screen.text": "${profileScopeConsentText}" }, "protocolMappers": [ { - "id": "8dcab95e-fa00-414c-8c3f-7927764af6e5", - "name": "address", + "id": "ead5f69d-3e92-4442-be41-f706dac64aa9", + "name": "gender", "protocol": "openid-connect", - "protocolMapper": "oidc-address-mapper", + "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, "config": { - "user.attribute.formatted": "formatted", - "user.attribute.country": "country", - "user.attribute.postal_code": "postal_code", "userinfo.token.claim": "true", - "user.attribute.street": "street", + "user.attribute": "gender", "id.token.claim": "true", - "user.attribute.region": "region", "access.token.claim": "true", - "user.attribute.locality": "locality" + "claim.name": "gender", + "jsonType.label": "String" } - } - ] - }, - { - "id": "99e38304-3ee1-4c5a-8925-4506f74696ee", - "name": "offline_access", - "description": "OpenID Connect built-in scope: offline_access", - "protocol": "openid-connect", - "attributes": { - "consent.screen.text": "${offlineAccessScopeConsentText}", - "display.on.consent.screen": "true" - } - }, - { - "id": "395b3d90-81c3-4838-a824-24074d9a085d", - "name": "microprofile-jwt", - "description": "Microprofile - JWT built-in scope", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "true", - "display.on.consent.screen": "false" - }, - "protocolMappers": [ + }, { - "id": "d48a6085-1b80-4fd9-8190-e6da48378e2d", - "name": "upn", + "id": "1fd32f69-94a0-472b-a107-8e5ed90231fc", + "name": "locale", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", + "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "username", + "user.attribute": "locale", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "upn", + "claim.name": "locale", "jsonType.label": "String" } }, { - "id": "91e33380-5d98-4b46-b394-9b59e372a494", - "name": "groups", + "id": "106c227b-4a2b-479a-bf54-7847d134d8d7", + "name": "given name", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-realm-role-mapper", + "protocolMapper": "oidc-usermodel-property-mapper", "consentRequired": false, "config": { - "multivalued": "true", - "user.attribute": "foo", + "userinfo.token.claim": "true", + "user.attribute": "firstName", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "groups", + "claim.name": "given_name", "jsonType.label": "String" } - } - ] - }, - { - "id": "e260ddc8-9ab5-4cfb-86cc-863b4cb59117", - "name": "email", - "description": "OpenID Connect built-in scope: email", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "true", - "display.on.consent.screen": "true", - "consent.screen.text": "${emailScopeConsentText}" - }, - "protocolMappers": [ + }, { - "id": "21b7539e-791b-4b1e-895f-edaab639c5ef", - "name": "email verified", + "id": "b5679740-6851-4f4a-8635-843d9e51552d", + "name": "profile", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", + "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "emailVerified", + "user.attribute": "profile", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "email_verified", - "jsonType.label": "boolean" + "claim.name": "profile", + "jsonType.label": "String" } }, { - "id": "211d42d9-f436-4b6a-a5e3-931507746247", - "name": "email", + "id": "a2a41c29-b159-4810-bcd6-fd6fbcc4079b", + "name": "updated at", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", + "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "email", + "user.attribute": "updatedAt", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "email", - "jsonType.label": "String" + "claim.name": "updated_at", + "jsonType.label": "long" } - } - ] - }, - { - "id": "bd3f2e46-be00-4352-a704-4acb72823ca2", - "name": "profile", - "description": "OpenID Connect built-in scope: profile", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "true", - "display.on.consent.screen": "true", - "consent.screen.text": "${profileScopeConsentText}" - }, - "protocolMappers": [ + }, { - "id": "6246d22e-e8f4-4c1a-b8c7-85d967bc440a", - "name": "website", + "id": "2f4e1996-2da1-4194-8d96-7a7e4a2a0ebd", + "name": "nickname", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "website", + "user.attribute": "nickname", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "website", + "claim.name": "nickname", "jsonType.label": "String" } }, { - "id": "9385e7ca-45ad-4c4b-a56a-8217aead6e16", - "name": "middle name", + "id": "57782fab-3525-4bb4-9718-98ca6b445dd8", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "c64cf00e-6116-4171-992e-ee17e7029c7b", + "name": "zoneinfo", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "middleName", + "user.attribute": "zoneinfo", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "middle_name", + "claim.name": "zoneinfo", "jsonType.label": "String" } }, { - "id": "a2be1cec-f7c0-4b9f-b3a6-b94bdd8174e9", - "name": "locale", + "id": "43fce4b2-3381-4787-8ae1-8e9eff591232", + "name": "middle name", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "locale", + "user.attribute": "middleName", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "locale", + "claim.name": "middle_name", "jsonType.label": "String" } }, { - "id": "71ad7a22-c923-4822-8930-0a2342c0195b", - "name": "gender", + "id": "fe590308-5b86-419b-a7f1-493c1afd7b2d", + "name": "username", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", + "protocolMapper": "oidc-usermodel-property-mapper", "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "gender", + "user.attribute": "username", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "gender", + "claim.name": "preferred_username", "jsonType.label": "String" } }, { - "id": "220b5b4e-6c86-404b-92d8-ccc92075b0ec", - "name": "nickname", + "id": "287f4787-b853-4712-9bef-5a59f6a970fa", + "name": "website", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "nickname", + "user.attribute": "website", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "nickname", + "claim.name": "website", "jsonType.label": "String" } }, { - "id": "f5d0f9ee-906f-432b-8169-5c23601f4dd2", - "name": "username", + "id": "f8286cb8-3b9c-4b03-a488-b66d4344b9e9", + "name": "picture", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", + "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "username", + "user.attribute": "picture", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "preferred_username", + "claim.name": "picture", "jsonType.label": "String" } }, { - "id": "19962845-92c4-4ef5-b5e5-780c53fd8181", + "id": "61813008-3e19-453c-a25b-8f59b7625905", "name": "family name", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-property-mapper", @@ -1146,7 +1068,7 @@ } }, { - "id": "54fe8868-1ac4-4906-bb96-a0431f11f801", + "id": "cd455592-7282-47ff-99e4-74c8eb7fdf32", "name": "birthdate", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", @@ -1159,98 +1081,162 @@ "claim.name": "birthdate", "jsonType.label": "String" } - }, - { - "id": "cf5d88d9-c70e-47d9-9eeb-b0d05bee4df3", - "name": "full name", - "protocol": "openid-connect", - "protocolMapper": "oidc-full-name-mapper", - "consentRequired": false, - "config": { - "id.token.claim": "true", - "access.token.claim": "true", - "userinfo.token.claim": "true" - } - }, + } + ] + }, + { + "id": "7d5f159a-12c9-4627-b312-30833cc73e99", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${phoneScopeConsentText}" + }, + "protocolMappers": [ { - "id": "b0fc93e7-ccd2-44c0-bf7b-7d3db935f980", - "name": "zoneinfo", + "id": "d7e77c6e-f4c0-4c8a-bb45-6d192d862e3e", + "name": "phone number verified", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "zoneinfo", + "user.attribute": "phoneNumberVerified", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "zoneinfo", - "jsonType.label": "String" + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" } }, { - "id": "77f20845-32dd-4651-9c5c-07e8acbdfac0", - "name": "picture", + "id": "cfe381ac-0147-41a7-97f0-845342413ee7", + "name": "phone number", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "picture", + "user.attribute": "phoneNumber", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "picture", + "claim.name": "phone_number", "jsonType.label": "String" } - }, + } + ] + }, + { + "id": "8c1113c8-bccf-434f-89b8-b8ca5c554984", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${emailScopeConsentText}" + }, + "protocolMappers": [ { - "id": "e9a0cdc2-43bd-4734-b455-0d2e11698de8", - "name": "profile", + "id": "ce5023e6-3f85-4fb8-9c83-3e2649e4b627", + "name": "email", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", + "protocolMapper": "oidc-usermodel-property-mapper", "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "profile", + "user.attribute": "email", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "profile", + "claim.name": "email", "jsonType.label": "String" } }, { - "id": "35cf7657-3fa4-4569-871c-2060a11c5e4b", - "name": "updated at", + "id": "38a61b0c-28dd-4a22-b2ec-6114ef5b415e", + "name": "email verified", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", + "protocolMapper": "oidc-usermodel-property-mapper", "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "updatedAt", + "user.attribute": "emailVerified", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "updated_at", - "jsonType.label": "String" + "claim.name": "email_verified", + "jsonType.label": "boolean" } - }, + } + ] + }, + { + "id": "1b42c259-044b-481e-9f9b-d67fd6bdcc0e", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ { - "id": "3d697e57-2bd7-4667-a904-d6452354e0ea", - "name": "given name", + "id": "76ee88a3-96c2-45fe-906b-d78dbacd8ea2", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "d2e45b1c-930e-47de-af18-874326ada247", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "236846f6-7fe0-4c8c-a278-a14692a4ce28", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${addressScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "ba5fe467-32ff-40bf-83bb-acfadd5a005a", + "name": "address", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", + "protocolMapper": "oidc-address-mapper", "consentRequired": false, "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "user.attribute.postal_code": "postal_code", "userinfo.token.claim": "true", - "user.attribute": "firstName", + "user.attribute.street": "street", "id.token.claim": "true", + "user.attribute.region": "region", "access.token.claim": "true", - "claim.name": "given_name", - "jsonType.label": "String" + "user.attribute.locality": "locality" } } ] }, { - "id": "258a3c9d-abdd-4880-b169-3f90bcb59609", + "id": "994faf8a-c227-40c1-bcb0-f682a4930aac", "name": "web-origins", "description": "OpenID Connect scope for add allowed web origins to the access token", "protocol": "openid-connect", @@ -1261,7 +1247,7 @@ }, "protocolMappers": [ { - "id": "5e55df72-1128-423c-bb7e-efd59b303992", + "id": "7b660a19-2040-47d2-92e3-37d7cc2e8abd", "name": "allowed web origins", "protocol": "openid-connect", "protocolMapper": "oidc-allowed-origins-mapper", @@ -1271,74 +1257,74 @@ ] }, { - "id": "e2519dd7-181b-4061-96e4-51ad793c21a0", - "name": "role_list", - "description": "SAML role list", - "protocol": "saml", + "id": "9cd9d8cf-3ac8-43b4-b6c6-7bed10a9a38e", + "name": "acr", + "description": "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol": "openid-connect", "attributes": { - "consent.screen.text": "${samlRoleListScopeConsentText}", - "display.on.consent.screen": "true" + "include.in.token.scope": "false", + "display.on.consent.screen": "false" }, "protocolMappers": [ { - "id": "5d6a5eb2-de50-47de-886b-c285723f9928", - "name": "role list", - "protocol": "saml", - "protocolMapper": "saml-role-list-mapper", + "id": "426e6133-76e0-4ab8-aecf-4d6eed3ee321", + "name": "acr loa level", + "protocol": "openid-connect", + "protocolMapper": "oidc-acr-mapper", "consentRequired": false, "config": { - "single": "false", - "attribute.nameformat": "Basic", - "attribute.name": "Role" + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" } } ] }, { - "id": "925f1f40-cc22-4829-afae-7765ac2cdb14", - "name": "phone", - "description": "OpenID Connect built-in scope: phone", + "id": "622a2ff6-fc4a-4d30-aa24-c681bce1e7b1", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", "protocol": "openid-connect", "attributes": { "include.in.token.scope": "true", - "display.on.consent.screen": "true", - "consent.screen.text": "${phoneScopeConsentText}" + "display.on.consent.screen": "false" }, "protocolMappers": [ { - "id": "8e0f8453-a375-493f-8d96-57a8154c052d", - "name": "phone number verified", + "id": "bd72742f-2949-41ee-ac52-2cf04096e85e", + "name": "upn", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", + "protocolMapper": "oidc-usermodel-property-mapper", "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "phoneNumberVerified", + "user.attribute": "username", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "phone_number_verified", - "jsonType.label": "boolean" + "claim.name": "upn", + "jsonType.label": "String" } }, { - "id": "8efae8d2-77a1-46c4-8de9-51a6781fb8f7", - "name": "phone number", + "id": "c700deb6-719c-48a1-92ae-98ee30621b94", + "name": "groups", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", + "protocolMapper": "oidc-usermodel-realm-role-mapper", "consentRequired": false, "config": { + "multivalued": "true", "userinfo.token.claim": "true", - "user.attribute": "phoneNumber", + "user.attribute": "foo", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "phone_number", + "claim.name": "groups", "jsonType.label": "String" } } ] }, { - "id": "0b27e77a-3487-4a0a-91bc-9d5501e1b431", + "id": "8d668142-8737-43b6-bad2-cdbbd1e89b08", "name": "roles", "description": "OpenID Connect scope for add user roles to the access token", "protocol": "openid-connect", @@ -1349,7 +1335,7 @@ }, "protocolMappers": [ { - "id": "f02997a9-12dc-490d-9af7-2aa217874359", + "id": "56743226-ba5a-4d94-b9d5-9f1f9ec1ec3d", "name": "audience resolve", "protocol": "openid-connect", "protocolMapper": "oidc-audience-resolve-mapper", @@ -1357,29 +1343,29 @@ "config": {} }, { - "id": "af14fbf1-81f4-4e38-9dcd-8a328dc48619", - "name": "realm roles", + "id": "ad88ff33-092d-4e2c-9131-e3bfabb70839", + "name": "client roles", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-realm-role-mapper", + "protocolMapper": "oidc-usermodel-client-role-mapper", "consentRequired": false, "config": { "user.attribute": "foo", "access.token.claim": "true", - "claim.name": "realm_access.roles", + "claim.name": "resource_access.${client_id}.roles", "jsonType.label": "String", "multivalued": "true" } }, { - "id": "b8166372-96a6-4d9c-b47c-30bc29fef4a8", - "name": "client roles", + "id": "e36807c2-b55b-4a88-9d03-02fab369bd90", + "name": "realm roles", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-client-role-mapper", + "protocolMapper": "oidc-usermodel-realm-role-mapper", "consentRequired": false, "config": { "user.attribute": "foo", "access.token.claim": "true", - "claim.name": "resource_access.${client_id}.roles", + "claim.name": "realm_access.roles", "jsonType.label": "String", "multivalued": "true" } @@ -1392,7 +1378,8 @@ "profile", "email", "roles", - "web-origins" + "web-origins", + "acr" ], "defaultOptionalClientScopes": [ "offline_access", @@ -1422,15 +1409,26 @@ "components": { "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ { - "id": "86c28e25-74c1-476b-8723-6d0f799b15df", - "name": "Full Scope Disabled", - "providerId": "scope", - "subType": "anonymous", + "id": "7a2cb92d-8214-410e-9e9c-a6be6904c873", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", "subComponents": {}, - "config": {} + "config": { + "allowed-protocol-mapper-types": [ + "saml-user-attribute-mapper", + "saml-role-list-mapper", + "oidc-address-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-full-name-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-usermodel-property-mapper", + "saml-user-property-mapper" + ] + } }, { - "id": "3cb73dd9-c955-4fba-9bc5-abb6ccd29bb6", + "id": "6f143e03-d722-421e-b689-3471609ca8ac", "name": "Allowed Client Scopes", "providerId": "allowed-client-templates", "subType": "anonymous", @@ -1442,34 +1440,34 @@ } }, { - "id": "3e7e1b97-82c5-4b1a-9c5a-ef0657799252", - "name": "Allowed Protocol Mapper Types", - "providerId": "allowed-protocol-mappers", - "subType": "authenticated", + "id": "3e590349-137d-426d-8f2e-47cba18340e6", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", "subComponents": {}, "config": { - "allowed-protocol-mapper-types": [ - "saml-user-attribute-mapper", - "oidc-full-name-mapper", - "saml-role-list-mapper", - "oidc-usermodel-attribute-mapper", - "saml-user-property-mapper", - "oidc-usermodel-property-mapper", - "oidc-address-mapper", - "oidc-sha256-pairwise-sub-mapper" + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" ] } }, { - "id": "d6a971eb-ad45-4bfd-a8c5-cd426c593768", - "name": "Consent Required", - "providerId": "consent-required", - "subType": "anonymous", + "id": "2282a454-37db-4ba0-9e7e-f88edc35a34a", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", "subComponents": {}, - "config": {} + "config": { + "allow-default-scopes": [ + "true" + ] + } }, { - "id": "ed09d054-e0b1-48cb-ab73-0119b21a8b3a", + "id": "c848fba8-2a08-4c1c-afbc-1ab0774eb0b0", "name": "Max Clients Limit", "providerId": "max-clients", "subType": "anonymous", @@ -1481,57 +1479,54 @@ } }, { - "id": "568cebd6-bc86-4f14-ae91-112a34176e32", - "name": "Allowed Client Scopes", - "providerId": "allowed-client-templates", - "subType": "authenticated", + "id": "67c1f4ed-c40e-4edc-a08a-f97026d8af1a", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", "subComponents": {}, - "config": { - "allow-default-scopes": [ - "true" - ] - } + "config": {} }, { - "id": "1a1c22b5-4be6-417b-b5cf-4d2b0b2ee24d", + "id": "b95df822-2d21-4d26-8914-6aa821c72527", "name": "Allowed Protocol Mapper Types", "providerId": "allowed-protocol-mappers", "subType": "anonymous", "subComponents": {}, "config": { "allowed-protocol-mapper-types": [ - "saml-user-property-mapper", - "oidc-usermodel-property-mapper", - "saml-role-list-mapper", "saml-user-attribute-mapper", + "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", - "oidc-sha256-pairwise-sub-mapper", + "saml-user-property-mapper", + "saml-role-list-mapper", + "oidc-usermodel-property-mapper", "oidc-address-mapper", - "oidc-full-name-mapper" + "oidc-sha256-pairwise-sub-mapper" ] } }, { - "id": "82aaab32-c500-45bd-8c74-def1d2012ba1", - "name": "Trusted Hosts", - "providerId": "trusted-hosts", + "id": "77169691-09dd-42ab-b2fb-61e55c2fade5", + "name": "Consent Required", + "providerId": "consent-required", "subType": "anonymous", "subComponents": {}, - "config": { - "host-sending-registration-request-must-match": [ - "true" - ], - "client-uris-must-match": [ - "true" - ] - } + "config": {} + } + ], + "org.keycloak.userprofile.UserProfileProvider": [ + { + "id": "d3cb083e-144d-4dc5-b16c-b65c57f2755b", + "providerId": "declarative-user-profile", + "subComponents": {}, + "config": {} } ], "org.keycloak.keys.KeyProvider": [ { - "id": "1a5101b2-1068-4587-8a00-3f19ba2a82f7", - "name": "rsa-generated", - "providerId": "rsa-generated", + "id": "dd87adbb-d86a-4a5e-a15e-3aa2d60b412a", + "name": "aes-generated", + "providerId": "aes-generated", "subComponents": {}, "config": { "priority": [ @@ -1540,27 +1535,41 @@ } }, { - "id": "c8d75e7a-1a73-4080-be95-9062291310cb", - "name": "aes-generated", - "providerId": "aes-generated", + "id": "97243c80-09d7-4f06-8477-c9e0b5245d77", + "name": "hmac-generated", + "providerId": "hmac-generated", "subComponents": {}, "config": { "priority": [ "100" + ], + "algorithm": [ + "HS256" ] } }, { - "id": "c752ea78-9018-4357-a720-587aefc6054f", - "name": "hmac-generated", - "providerId": "hmac-generated", + "id": "c93175cf-08be-40e5-a805-f1c7c5cff607", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "b0f7fc2a-b531-4d35-83e0-6ed2770520c0", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", "subComponents": {}, "config": { "priority": [ "100" ], "algorithm": [ - "HS256" + "RSA-OAEP" ] } } @@ -1570,7 +1579,7 @@ "supportedLocales": [], "authenticationFlows": [ { - "id": "e575425d-f4a2-4038-ae86-37fd6a6a26b9", + "id": "9c27dbd3-7ddd-41e5-bf9e-e6c549711982", "alias": "Account verification options", "description": "Method with which to verity the existing account", "providerId": "basic-flow", @@ -1596,7 +1605,7 @@ ] }, { - "id": "4c7fe8e3-2e6d-4acb-91d2-a08e82ce74b6", + "id": "b47e3b3d-548f-4a3b-8b85-7cb062d6758e", "alias": "Authentication Options", "description": "Authentication options.", "providerId": "basic-flow", @@ -1630,7 +1639,7 @@ ] }, { - "id": "0b7b9bba-fe5e-4147-9a5d-617f7c4ac2a8", + "id": "265dc545-ef9d-4395-85c2-6b7247a4cd0e", "alias": "Browser - Conditional OTP", "description": "Flow to determine if the OTP is required for the authentication", "providerId": "basic-flow", @@ -1656,7 +1665,7 @@ ] }, { - "id": "3516fb19-91fd-4ab6-ac86-c503e0f5c643", + "id": "a7282671-e77a-45f2-9d94-23f68c474628", "alias": "Direct Grant - Conditional OTP", "description": "Flow to determine if the OTP is required for the authentication", "providerId": "basic-flow", @@ -1682,7 +1691,7 @@ ] }, { - "id": "d39565fc-0f28-4da3-ad11-afa0f6128415", + "id": "d557424c-1011-4290-a116-8300919027b8", "alias": "First broker login - Conditional OTP", "description": "Flow to determine if the OTP is required for the authentication", "providerId": "basic-flow", @@ -1708,7 +1717,7 @@ ] }, { - "id": "afb39881-b370-42ed-849a-f2e16143b96a", + "id": "cbcdf297-d4e5-45ce-a826-2aa539d0e274", "alias": "Handle Existing Account", "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", "providerId": "basic-flow", @@ -1734,7 +1743,7 @@ ] }, { - "id": "335b15c9-41f1-481d-ab88-2496990fd5b5", + "id": "247bb564-5adc-4986-a902-3cc5f373251f", "alias": "Reset - Conditional OTP", "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", "providerId": "basic-flow", @@ -1760,7 +1769,7 @@ ] }, { - "id": "4b67d4e6-fddf-47fb-9050-2eab05fcb431", + "id": "ec9deaa5-61aa-4bcf-a12c-95bd8d578833", "alias": "User creation or linking", "description": "Flow for the existing/non-existing user alternatives", "providerId": "basic-flow", @@ -1787,7 +1796,7 @@ ] }, { - "id": "a6c4e237-3d71-4ee7-8f67-5d649bc67494", + "id": "4b075757-bc7f-401b-a128-b59092f7eb0e", "alias": "Verify Existing Account by Re-authentication", "description": "Reauthentication of existing account", "providerId": "basic-flow", @@ -1813,7 +1822,7 @@ ] }, { - "id": "107e15d5-c4d1-43f1-ba20-9d7b1fbff62b", + "id": "b08fefa6-61d0-41e7-b591-5cbf4481848f", "alias": "browser", "description": "browser based authentication", "providerId": "basic-flow", @@ -1855,7 +1864,7 @@ ] }, { - "id": "e76a4d60-b2ad-4270-a1d5-3936846577a0", + "id": "b93ad76d-bbf7-46e7-b032-4aecf8b951a7", "alias": "clients", "description": "Base authentication for clients", "providerId": "client-flow", @@ -1897,7 +1906,7 @@ ] }, { - "id": "465868da-b426-43ea-aadb-bd107257f322", + "id": "00ac8812-017e-4e0e-89af-c0c39fab3484", "alias": "direct grant", "description": "OpenID Connect Resource Owner Grant", "providerId": "basic-flow", @@ -1931,7 +1940,7 @@ ] }, { - "id": "c694eec6-0493-41d6-8e46-73e7d2739cb7", + "id": "3901e3e0-1237-4bdc-88b3-dc9e8edf8820", "alias": "docker auth", "description": "Used by Docker clients to authenticate against the IDP", "providerId": "basic-flow", @@ -1949,7 +1958,7 @@ ] }, { - "id": "1dddf501-6fca-4f26-a118-1a7db976c4b5", + "id": "6d5d442b-acc6-47f2-aa26-56b9521b57bd", "alias": "first broker login", "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", "providerId": "basic-flow", @@ -1976,7 +1985,7 @@ ] }, { - "id": "65e655e4-d0e9-4ef3-a0c4-bc0465d9fcaf", + "id": "9722f48b-ca66-42bf-b1dc-2f46b99141e8", "alias": "forms", "description": "Username, password, otp and other auth forms.", "providerId": "basic-flow", @@ -2002,7 +2011,7 @@ ] }, { - "id": "5c6271f3-77c7-4352-9041-68cc75cd2bfe", + "id": "8a481e27-d05d-4458-9307-a2d6ed790fdd", "alias": "http challenge", "description": "An authentication flow based on challenge-response HTTP Authentication Schemes", "providerId": "basic-flow", @@ -2028,7 +2037,7 @@ ] }, { - "id": "156abc82-64db-4a01-acb4-827cd2798be1", + "id": "334b5f2f-649a-43c5-88f7-8575c964c6bc", "alias": "registration", "description": "registration flow", "providerId": "basic-flow", @@ -2047,7 +2056,7 @@ ] }, { - "id": "6654fb49-a28c-48c1-b748-595eecca20ec", + "id": "41d99231-2a3f-43a2-afdc-c9652d381fc1", "alias": "registration form", "description": "registration form", "providerId": "form-flow", @@ -2089,7 +2098,7 @@ ] }, { - "id": "eed5ce41-fc76-4d97-b516-320151a807fe", + "id": "82b65253-5731-44a5-aab4-1c776f4034ab", "alias": "reset credentials", "description": "Reset credentials for a user if they forgot their password or something", "providerId": "basic-flow", @@ -2131,7 +2140,7 @@ ] }, { - "id": "3ae44d6e-5f48-4c93-b060-4b36e82c755f", + "id": "ae8645ca-9895-414d-a343-7d5c5db71218", "alias": "saml ecp", "description": "SAML ECP Profile Authentication Flow", "providerId": "basic-flow", @@ -2151,14 +2160,14 @@ ], "authenticatorConfig": [ { - "id": "0ceafd8b-ba90-4f08-ba05-8a5ba8f6a011", + "id": "588840ec-a07b-4347-8f59-65bb73410d0b", "alias": "create unique user config", "config": { "require.password.update.after.registration": "false" } }, { - "id": "c695b0f2-ae08-4d80-81f9-9ec5c31ef2a2", + "id": "bab12af2-fa6b-44fa-9ae4-42bbba237424", "alias": "review profile config", "config": { "update.profile.on.first.login": "missing" @@ -2176,9 +2185,9 @@ "config": {} }, { - "alias": "terms_and_conditions", + "alias": "TERMS_AND_CONDITIONS", "name": "Terms and Conditions", - "providerId": "terms_and_conditions", + "providerId": "TERMS_AND_CONDITIONS", "enabled": false, "defaultAction": false, "priority": 20, @@ -2220,6 +2229,24 @@ "priority": 60, "config": {} }, + { + "alias": "webauthn-register", + "name": "Webauthn Register", + "providerId": "webauthn-register", + "enabled": true, + "defaultAction": false, + "priority": 70, + "config": {} + }, + { + "alias": "webauthn-register-passwordless", + "name": "Webauthn Register Passwordless", + "providerId": "webauthn-register-passwordless", + "enabled": true, + "defaultAction": false, + "priority": 80, + "config": {} + }, { "alias": "update_user_locale", "name": "Update User Locale", @@ -2238,19 +2265,26 @@ "dockerAuthenticationFlow": "docker auth", "attributes": { "cibaBackchannelTokenDeliveryMode": "poll", - "cibaExpiresIn": "120", "cibaAuthRequestedUserHint": "login_hint", - "oauth2DeviceCodeLifespan": "600", - "oauth2DevicePollingInterval": "600", "clientOfflineSessionMaxLifespan": "0", + "oauth2DevicePollingInterval": "5", "clientSessionIdleTimeout": "0", - "userProfileEnabled": "false", + "actionTokenGeneratedByUserLifespan-execute-actions": "", + "actionTokenGeneratedByUserLifespan-verify-email": "", + "clientOfflineSessionIdleTimeout": "0", + "actionTokenGeneratedByUserLifespan-reset-credentials": "", + "cibaInterval": "5", + "realmReusableOtpCode": "false", + "cibaExpiresIn": "120", + "oauth2DeviceCodeLifespan": "600", + "actionTokenGeneratedByUserLifespan-idp-verify-account-via-email": "", "parRequestUriLifespan": "60", "clientSessionMaxLifespan": "0", - "clientOfflineSessionIdleTimeout": "0", - "cibaInterval": "5" + "frontendUrl": "", + "acr.loa.map": "{}", + "shortVerificationUri": "" }, - "keycloakVersion": "17.0.0", + "keycloakVersion": "21.1.1", "userManagedAccessAllowed": true, "clientProfiles": { "profiles": [] diff --git a/src/main/resources/keycloak.json b/src/main/resources/keycloak.json index 9129751e..3cc6f17f 100644 --- a/src/main/resources/keycloak.json +++ b/src/main/resources/keycloak.json @@ -1,10 +1,10 @@ { - "realm": "master", + "realm": "Halcyon", "auth-server-url": "http://localhost:8888/auth/", - "ssl-required": "none", + "ssl-required": "external", "resource": "account", "public-client": true, "verify-token-audience": true, - "use-resource-role-mappings": false, + "use-resource-role-mappings": true, "confidential-port": 0 } \ No newline at end of file diff --git a/src/main/resources/profile.properties b/src/main/resources/profile.properties deleted file mode 100644 index 54d79c0c..00000000 --- a/src/main/resources/profile.properties +++ /dev/null @@ -1,16 +0,0 @@ -# Enabled Keycloak Features, see org.keycloak.common.Profile.Feature -## Features can also be toggled via System-Property, e.g. -Dkeycloak.profile.feature.account_api=enabled - -# Enable Token Exchange. Both are needed for token exchange -feature.token_exchange=enabled -feature.admin_fine_grained_authz=enabled - -# Enable Scripting -feature.scripts=enabled -feature.upload_scripts=disabled - -# Enable new Account Rest API -feature.account_api=enabled - -# Enable new Account Console -feature.account2=enabled \ No newline at end of file diff --git a/src/main/resources/theme/README.md b/src/main/resources/theme/README.md new file mode 100644 index 00000000..9276a3cb --- /dev/null +++ b/src/main/resources/theme/README.md @@ -0,0 +1,24 @@ +Creating Themes +=============== + +Themes are used to configure the look and feel of login pages and the account management console. + +Custom themes packaged in a JAR file should be deployed to the `${kc.home.dir}/providers` directory. After that, run +the `build` command to install them before starting the server. + +You are also able to create your custom themes in this directory, directly. Themes within this directory do not require +the `build` command to be installed. + +When running the server in development mode using `start-dev`, themes are not cached so that you can easily work on them without a need to restart +the server when making changes. + +See the theme section in the [Server Developer Guide](https://www.keycloak.org/docs/latest/server_development/#_themes) for more details about how to create custom themes. + +Overriding the built-in templates +--------------------------------- + +While creating custom themes, especially when overriding templates, it may be useful to use the built-in templates as +a reference. These can be found within the theme directory of `../lib/lib/main/org.keycloak.keycloak-themes-20.0.0.jar`, which can be opened using any +standard ZIP archive tool. + +**Built-in themes should not be modified directly, instead a custom theme should be created.** \ No newline at end of file diff --git a/src/main/resources/theme/providers-only/login/login.ftl b/src/main/resources/theme/providers-only/login/login.ftl new file mode 100644 index 00000000..26840bf0 --- /dev/null +++ b/src/main/resources/theme/providers-only/login/login.ftl @@ -0,0 +1,25 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout displayMessage=!messagesPerField.existsError('username','password') displayInfo=realm.password && realm.registrationAllowed && !registrationDisabled??; section> + <#if section = "header"> + ${msg("loginAccountTitle")} + <#elseif section = "socialProviders" > + <#if realm.password && social.providers??> + + + + + diff --git a/src/main/resources/theme/providers-only/login/resources/css/login.css b/src/main/resources/theme/providers-only/login/resources/css/login.css new file mode 100644 index 00000000..e9ad6604 --- /dev/null +++ b/src/main/resources/theme/providers-only/login/resources/css/login.css @@ -0,0 +1,595 @@ +/* Patternfly CSS places a "bg-login.jpg" as the background on this ".login-pf" class. + This clashes with the "keycloak-bg.png' background defined on the body below. + Therefore the Patternfly background must be set to none. */ +.login-pf { + background: none; +} + +.login-pf body { + background-size: cover; + height: 100%; +} + +textarea.pf-c-form-control { + height: auto; +} + +.pf-c-alert__title { + font-size: var(--pf-global--FontSize--xs); +} + +p.instruction { + margin: 5px 0; +} + +.pf-c-button.pf-m-control { + border: solid var(--pf-global--BorderWidth--sm); + border-color: rgba(230, 230, 230, 0.5); +} + +h1#kc-page-title { + margin-top: 10px; +} + +#kc-locale ul { + background-color: var(--pf-global--BackgroundColor--100); + display: none; + top: 20px; + min-width: 100px; + padding: 0; +} + +#kc-locale-dropdown{ + display: inline-block; +} + +#kc-locale-dropdown:hover ul { + display:block; +} + +#kc-locale-dropdown a { + color: var(--pf-global--Color--200); + text-align: right; + font-size: var(--pf-global--FontSize--sm); +} + +a#kc-current-locale-link::after { + content: "\2c5"; + margin-left: var(--pf-global--spacer--xs) +} + +.login-pf .container { + padding-top: 40px; +} + +.login-pf a:hover { + color: #0099d3; +} + +#kc-logo { + width: 100%; +} + +div.kc-logo-text { + background-image: url(../img/keycloak-logo-text.png); + background-repeat: no-repeat; + height: 63px; + width: 300px; + margin: 0 auto; +} + +div.kc-logo-text span { + display: none; +} + +#kc-header { + color: #ededed; + overflow: visible; + white-space: nowrap; +} + +#kc-header-wrapper { + font-size: 29px; + text-transform: uppercase; + letter-spacing: 3px; + line-height: 1.2em; + padding: 62px 10px 20px; + white-space: normal; +} + +#kc-content { + width: 100%; +} + +#kc-attempted-username { + font-size: 20px; + font-family: inherit; + font-weight: normal; + padding-right: 10px; +} + +#kc-username { + text-align: center; + margin-bottom:-10px; +} + +#kc-webauthn-settings-form { + padding-top: 8px; +} + +#kc-form-webauthn .select-auth-box-parent { + pointer-events: none; +} + +#kc-form-webauthn .select-auth-box-desc { + color: var(--pf-global--palette--black-600); +} + +#kc-form-webauthn .select-auth-box-headline { + color: var(--pf-global--Color--300); +} + +#kc-form-webauthn .select-auth-box-icon { + flex: 0 0 3em; +} + +#kc-form-webauthn .select-auth-box-icon-properties { + margin-top: 10px; + font-size: 1.8em; +} + +#kc-form-webauthn .select-auth-box-icon-properties.unknown-transport-class { + margin-top: 3px; +} + +#kc-form-webauthn .pf-l-stack__item { + margin: -1px 0; +} + +#kc-content-wrapper { + margin-top: 20px; +} + +#kc-form-wrapper { + margin-top: 10px; +} + +#kc-info { + margin: 20px -40px -30px; +} + +#kc-info-wrapper { + font-size: 13px; + padding: 15px 35px; + background-color: #F0F0F0; +} + +#kc-form-options span { + display: block; +} + +#kc-form-options .checkbox { + margin-top: 0; + color: #72767b; +} + +#kc-terms-text { + margin-bottom: 20px; +} + +#kc-registration { + margin-bottom: 0; +} + +/* TOTP */ + +.subtitle { + text-align: right; + margin-top: 30px; + color: #909090; +} + +.required { + color: var(--pf-global--danger-color--200); +} + +ol#kc-totp-settings { + margin: 0; + padding-left: 20px; +} + +ul#kc-totp-supported-apps { + margin-bottom: 10px; +} + +#kc-totp-secret-qr-code { + max-width:150px; + max-height:150px; +} + +#kc-totp-secret-key { + background-color: #fff; + color: #333333; + font-size: 16px; + padding: 10px 0; +} + +/* OAuth */ + +#kc-oauth h3 { + margin-top: 0; +} + +#kc-oauth ul { + list-style: none; + padding: 0; + margin: 0; +} + +#kc-oauth ul li { + border-top: 1px solid rgba(255, 255, 255, 0.1); + font-size: 12px; + padding: 10px 0; +} + +#kc-oauth ul li:first-of-type { + border-top: 0; +} + +#kc-oauth .kc-role { + display: inline-block; + width: 50%; +} + +/* Code */ +#kc-code textarea { + width: 100%; + height: 8em; +} + +/* Social */ +.kc-social-links { + margin-top: 20px; +} + +.kc-social-provider-logo { + font-size: 23px; + width: 30px; + height: 25px; + float: left; +} + +.kc-social-gray { + color: var(--pf-global--Color--200); +} + +.kc-social-item { + margin-bottom: var(--pf-global--spacer--sm); + font-size: 15px; + text-align: center; +} + +.kc-social-provider-name { + position: relative; + top: 3px; +} + +.kc-social-icon-text { + left: -15px; +} + +.kc-social-grid { + display:grid; + grid-column-gap: 10px; + grid-row-gap: 5px; + grid-column-end: span 6; + --pf-l-grid__item--GridColumnEnd: span 6; +} + +.kc-social-grid .kc-social-icon-text { + left: -10px; +} + +.kc-login-tooltip { + position: relative; + display: inline-block; +} + +.kc-social-section { + text-align: center; +} + +.kc-social-section hr{ + margin-bottom: 10px +} + +.kc-login-tooltip .kc-tooltip-text{ + top:-3px; + left:160%; + background-color: black; + visibility: hidden; + color: #fff; + + min-width:130px; + text-align: center; + border-radius: 2px; + box-shadow:0 1px 8px rgba(0,0,0,0.6); + padding: 5px; + + position: absolute; + opacity:0; + transition:opacity 0.5s; +} + +/* Show tooltip */ +.kc-login-tooltip:hover .kc-tooltip-text { + visibility: visible; + opacity:0.7; +} + +/* Arrow for tooltip */ +.kc-login-tooltip .kc-tooltip-text::after { + content: " "; + position: absolute; + top: 15px; + right: 100%; + margin-top: -5px; + border-width: 5px; + border-style: solid; + border-color: transparent black transparent transparent; +} + +@media (min-width: 768px) { + #kc-container-wrapper { + position: absolute; + width: 100%; + } + + .login-pf .container { + padding-right: 80px; + } + + #kc-locale { + position: relative; + text-align: right; + z-index: 9999; + } +} + +@media (max-width: 767px) { + + .login-pf body { + background: white; + } + + #kc-header { + padding-left: 15px; + padding-right: 15px; + float: none; + text-align: left; + } + + #kc-header-wrapper { + font-size: 16px; + font-weight: bold; + padding: 20px 60px 0 0; + color: #72767b; + letter-spacing: 0; + } + + div.kc-logo-text { + margin: 0; + width: 150px; + height: 32px; + background-size: 100%; + } + + #kc-form { + float: none; + } + + #kc-info-wrapper { + border-top: 1px solid rgba(255, 255, 255, 0.1); + background-color: transparent; + } + + .login-pf .container { + padding-top: 15px; + padding-bottom: 15px; + } + + #kc-locale { + position: absolute; + width: 200px; + top: 20px; + right: 20px; + text-align: right; + z-index: 9999; + } +} + +@media (min-height: 646px) { + #kc-container-wrapper { + bottom: 12%; + } +} + +@media (max-height: 645px) { + #kc-container-wrapper { + padding-top: 50px; + top: 20%; + } +} + +.card-pf form.form-actions .btn { + float: right; + margin-left: 10px; +} + +#kc-form-buttons { + margin-top: 20px; +} + +.login-pf-page .login-pf-brand { + margin-top: 20px; + max-width: 360px; + width: 40%; +} + +.select-auth-box-arrow{ + display: flex; + align-items: center; + margin-right: 2rem; +} + +.select-auth-box-icon{ + display: flex; + flex: 0 0 2em; + justify-content: center; + margin-right: 1rem; + margin-left: 3rem; +} + +.select-auth-box-parent{ + border-top: 1px solid var(--pf-global--palette--black-200); + padding-top: 1rem; + padding-bottom: 1rem; + cursor: pointer; +} + +.select-auth-box-parent:hover{ + background-color: #f7f8f8; +} + +.select-auth-container { + padding-bottom: 0px !important; +} + +.select-auth-box-headline { + font-size: var(--pf-global--FontSize--md); + color: var(--pf-global--primary-color--100); + font-weight: bold; +} + +.select-auth-box-desc { + font-size: var(--pf-global--FontSize--sm); +} + +.select-auth-box-paragraph { + text-align: center; + font-size: var(--pf-global--FontSize--md); + margin-bottom: 5px; +} + +.card-pf { + margin: 0 auto; + box-shadow: var(--pf-global--BoxShadow--lg); + padding: 0 20px; + max-width: 500px; + border-top: 4px solid; + border-color: var(--pf-global--primary-color--100); +} + +/*phone*/ +@media (max-width: 767px) { + .login-pf-page .card-pf { + max-width: none; + margin-left: 0; + margin-right: 0; + padding-top: 0; + border-top: 0; + box-shadow: 0 0; + } + + .kc-social-grid { + grid-column-end: 12; + --pf-l-grid__item--GridColumnEnd: span 12; + } + + .kc-social-grid .kc-social-icon-text { + left: -15px; + } +} + +.login-pf-page .login-pf-signup { + font-size: 15px; + color: #72767b; +} +#kc-content-wrapper .row { + margin-left: 0; + margin-right: 0; +} + +.login-pf-page.login-pf-page-accounts { + margin-left: auto; + margin-right: auto; +} + +.login-pf-page .btn-primary { + margin-top: 0; +} + +.login-pf-page .list-view-pf .list-group-item { + border-bottom: 1px solid #ededed; +} + +.login-pf-page .list-view-pf-description { + width: 100%; +} + +#kc-form-login div.form-group:last-of-type, +#kc-register-form div.form-group:last-of-type, +#kc-update-profile-form div.form-group:last-of-type, +#kc-update-email-form div.form-group:last-of-type{ + margin-bottom: 0px; +} + +.no-bottom-margin { + margin-bottom: 0; +} + +#kc-back { + margin-top: 5px; +} + +/* Recovery codes */ +.kc-recovery-codes-warning { + margin-bottom: 32px; +} +.kc-recovery-codes-warning .pf-c-alert__description p { + font-size: 0.875rem; +} +.kc-recovery-codes-list { + list-style: none; + columns: 2; + margin: 16px 0; + padding: 16px 16px 8px 16px; + border: 1px solid #D2D2D2; +} +.kc-recovery-codes-list li { + margin-bottom: 8px; + font-size: 11px; +} +.kc-recovery-codes-list li span { + color: #6A6E73; + width: 16px; + text-align: right; + display: inline-block; + margin-right: 1px; +} + +.kc-recovery-codes-actions { + margin-bottom: 24px; +} +.kc-recovery-codes-actions button { + padding-left: 0; +} +.kc-recovery-codes-actions button i { + margin-right: 8px; +} + +.kc-recovery-codes-confirmation { + align-items: baseline; + margin-bottom: 16px; +} +/* End Recovery codes */ diff --git a/src/main/resources/theme/providers-only/login/resources/img/feedback-error-arrow-down.png b/src/main/resources/theme/providers-only/login/resources/img/feedback-error-arrow-down.png new file mode 100644 index 0000000000000000000000000000000000000000..6f2d9d2aeb1c1461767988a042aae50492d454bc GIT binary patch literal 513 zcmV+c0{;DpP)pbFH`>7=NJimT8HPR1b>!7GRq%%D)H?|Wo% zC@Pq9$n!u*^5*>k`A#3+kZfF(`zJ152&b=J2><{9!0hZbO_;oR8NR%F9kLkE@5Z(} z|9X+Rqj^|(^f2UQ`B&qbstQ}zSHs@7Mx_7%0O*T*Sywa1;J+C|`oj73Q8;qyTvEs3fk{(-JWjQ|4!;N?Hk9Wu}T8~06?E)T~0nO z$|9aN^E2gq`rLgk&kqde>9h9ceYG2J7l(CKD-8eu0DaQs4ti*V_8>1|L40001@p8^a34>6V-IeVqb00000NkvXXu0mjf D@HyfE literal 0 HcmV?d00001 diff --git a/src/main/resources/theme/providers-only/login/resources/img/feedback-error-sign.png b/src/main/resources/theme/providers-only/login/resources/img/feedback-error-sign.png new file mode 100644 index 0000000000000000000000000000000000000000..0dd500445d7249ddd1b1dea7dc79d5e2f75cf223 GIT binary patch literal 343 zcmV-d0jU0oP)Rb^f-HfiA|a#ZS&BN>m{U!Bf9XBlP=AUsPhep z0;u>nQCxb~G6c;4tOM)aDVebC2LF?__!*$Y%|S5$e;ZcIdR+o#XrlC>L#@3RFZO+y z4ac0_$8S?_3L#)o1cV>wsb|~_2qr}-{Xy0sk1ddFrS2NjYBy!#lv@hhf%T~P1a2ls zg9cR!$Y8{U0euD81vt(&PyxuVl0?xy+oT=qfZ( zU<;qzo;L~X{8Dl*OuQ;g6C?jcvTY4MH)UgY@3N+I%Y&B-aM#Gmz4*K9-@h9B>+j#Q z@1I{-{^;BJ=zrF!cK!}Pr!g=bh|HcT>Z=m}wDHmbyC0nDaV#gVm&uycGotW zpv;r|b9e9Fzj)!!_FHF``Wx|qb*+1{*5LE=)%xG0rlsBtdT?-dwR{mlor@ArQ!T)1>sQ|BRGRy6pUXk5Eq%Jj zt#hDSUl#1`1Hp4rtdp{v4HrVu6{1-oD!M}vd literal 0 HcmV?d00001 diff --git a/src/main/resources/theme/providers-only/login/resources/img/feedback-success-sign.png b/src/main/resources/theme/providers-only/login/resources/img/feedback-success-sign.png new file mode 100644 index 0000000000000000000000000000000000000000..640bd71cab7bdfc7a8adcf28ffaf6db736a1c008 GIT binary patch literal 410 zcmeAS@N?(olHy`uVBq!ia0vp^f*{Pn1|+R>-G2comSQK*5Dp-y;YjHK@;M7UB8!3Q zuY)k7lg8`{prB-lYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&3=E9oo-U3d z7QI&|@AYC16lmSQ(|nzi6W2vA0hQiX{shsj9%4*QVx6m7=T11}S0g4pTOrBJ(L2zj zqqUbmAu&;Tnwis)`FE;yFXbH4TM_-V#F%}bS9C7N)nccy^_%s?ydQOE$UA=bQASGdP zUS5*^?Y}eFBd=Y%8?p303zx;a-hVOO%tz8T)jmnDmV3_E#n4nO@i}MX#=5_=rbOm! z*!s=OFZw-8%CQyiuZC^?Jc-4l;#4*gQu&X%Q~loCIB2_ BsrLW? literal 0 HcmV?d00001 diff --git a/src/main/resources/theme/providers-only/login/resources/img/feedback-warning-arrow-down.png b/src/main/resources/theme/providers-only/login/resources/img/feedback-warning-arrow-down.png new file mode 100644 index 0000000000000000000000000000000000000000..6f2d9d2aeb1c1461767988a042aae50492d454bc GIT binary patch literal 513 zcmV+c0{;DpP)pbFH`>7=NJimT8HPR1b>!7GRq%%D)H?|Wo% zC@Pq9$n!u*^5*>k`A#3+kZfF(`zJ152&b=J2><{9!0hZbO_;oR8NR%F9kLkE@5Z(} z|9X+Rqj^|(^f2UQ`B&qbstQ}zSHs@7Mx_7%0O*T*Sywa1;J+C|`oj73Q8;qyTvEs3fk{(-JWjQ|4!;N?Hk9Wu}T8~06?E)T~0nO z$|9aN^E2gq`rLgk&kqde>9h9ceYG2J7l(CKD-8eu0DaQs4ti*V_8>1|L40001@p8^a34>6V-IeVqb00000NkvXXu0mjf D@HyfE literal 0 HcmV?d00001 diff --git a/src/main/resources/theme/providers-only/login/resources/img/feedback-warning-sign.png b/src/main/resources/theme/providers-only/login/resources/img/feedback-warning-sign.png new file mode 100644 index 0000000000000000000000000000000000000000..f9392a356fd3b383997c1ef289b48d02be96c351 GIT binary patch literal 646 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/theme/providers-only/login/resources/img/keycloak-logo-text.png b/src/main/resources/theme/providers-only/login/resources/img/keycloak-logo-text.png new file mode 100644 index 0000000000000000000000000000000000000000..63f3b9f87deb2dee6172d44fbad7177064245101 GIT binary patch literal 19994 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/theme/providers-only/login/theme.properties b/src/main/resources/theme/providers-only/login/theme.properties new file mode 100644 index 00000000..ddd16185 --- /dev/null +++ b/src/main/resources/theme/providers-only/login/theme.properties @@ -0,0 +1,161 @@ +parent=base +import=common/keycloak + +styles=css/login.css +stylesCommon=web_modules/@patternfly/react-core/dist/styles/base.css web_modules/@patternfly/react-core/dist/styles/app.css node_modules/patternfly/dist/css/patternfly.min.css node_modules/patternfly/dist/css/patternfly-additions.min.css lib/pficon/pficon.css + +meta=viewport==width=device-width,initial-scale=1 + +kcHtmlClass=login-pf +kcLoginClass=login-pf-page + +kcLogoLink=http://www.keycloak.org + +kcLogoClass=login-pf-brand + +kcContainerClass=container-fluid +kcContentClass=col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3 col-lg-6 col-lg-offset-3 + +kcHeaderClass=login-pf-page-header +kcFeedbackAreaClass=col-md-12 +kcLocaleClass=col-xs-12 col-sm-1 + +## Locale +kcLocaleMainClass=pf-c-dropdown +kcLocaleListClass=pf-c-dropdown__menu pf-m-align-right +kcLocaleItemClass=pf-c-dropdown__menu-item + +## Alert +kcAlertClass=pf-c-alert pf-m-inline +kcAlertTitleClass=pf-c-alert__title kc-feedback-text + +kcFormAreaClass=col-sm-10 col-sm-offset-1 col-md-8 col-md-offset-2 col-lg-8 col-lg-offset-2 +kcFormCardClass=card-pf + +### Social providers +kcFormSocialAccountListClass=pf-c-login__main-footer-links kc-social-links +kcFormSocialAccountListGridClass=pf-l-grid kc-social-grid +kcFormSocialAccountListButtonClass=pf-c-button pf-m-control pf-m-block kc-social-item kc-social-gray +kcFormSocialAccountGridItem=pf-l-grid__item + +kcFormSocialAccountNameClass=kc-social-provider-name +kcFormSocialAccountLinkClass=pf-c-login__main-footer-links-item-link +kcFormSocialAccountSectionClass=kc-social-section kc-social-gray +kcFormHeaderClass=login-pf-header + +kcFeedbackErrorIcon=fa fa-fw fa-exclamation-circle +kcFeedbackWarningIcon=fa fa-fw fa-exclamation-triangle +kcFeedbackSuccessIcon=fa fa-fw fa-check-circle +kcFeedbackInfoIcon=fa fa-fw fa-info-circle + +kcResetFlowIcon=pficon pficon-arrow fa + +# WebAuthn icons +kcWebAuthnKeyIcon=pficon pficon-key +kcWebAuthnDefaultIcon=pficon pficon-key +kcWebAuthnUnknownIcon=pficon pficon-key unknown-transport-class +kcWebAuthnUSB=fa fa-usb +kcWebAuthnNFC=fa fa-wifi +kcWebAuthnBLE=fa fa-bluetooth-b +kcWebAuthnInternal=pficon pficon-key + +kcFormClass=form-horizontal +kcFormGroupClass=form-group +kcFormGroupErrorClass=has-error +kcLabelClass=pf-c-form__label pf-c-form__label-text +kcLabelWrapperClass=col-xs-12 col-sm-12 col-md-12 col-lg-12 +kcInputClass=pf-c-form-control +kcInputHelperTextBeforeClass=pf-c-form__helper-text pf-c-form__helper-text-before +kcInputHelperTextAfterClass=pf-c-form__helper-text pf-c-form__helper-text-after +kcInputClassRadio=pf-c-radio +kcInputClassRadioInput=pf-c-radio__input +kcInputClassRadioLabel=pf-c-radio__label +kcInputClassCheckbox=pf-c-check +kcInputClassCheckboxInput=pf-c-check__input +kcInputClassCheckboxLabel=pf-c-check__label +kcInputClassRadioCheckboxLabelDisabled=pf-m-disabled +kcInputErrorMessageClass=pf-c-form__helper-text pf-m-error required kc-feedback-text +kcInputWrapperClass=col-xs-12 col-sm-12 col-md-12 col-lg-12 +kcFormOptionsClass=col-xs-12 col-sm-12 col-md-12 col-lg-12 +kcFormButtonsClass=col-xs-12 col-sm-12 col-md-12 col-lg-12 +kcFormSettingClass=login-pf-settings +kcTextareaClass=form-control +kcSignUpClass=login-pf-signup + + +kcInfoAreaClass=col-xs-12 col-sm-4 col-md-4 col-lg-5 details + +### user-profile grouping +kcFormGroupHeader=pf-c-form__group + +##### css classes for form buttons +# main class used for all buttons +kcButtonClass=pf-c-button +# classes defining priority of the button - primary or default (there is typically only one priority button for the form) +kcButtonPrimaryClass=pf-m-primary +kcButtonDefaultClass=btn-default +# classes defining size of the button +kcButtonLargeClass=btn-lg +kcButtonBlockClass=pf-m-block + +##### css classes for input +kcInputLargeClass=input-lg + +##### css classes for form accessability +kcSrOnlyClass=sr-only + +##### css classes for select-authenticator form +kcSelectAuthListClass=pf-l-stack select-auth-container +kcSelectAuthListItemClass=pf-l-stack__item select-auth-box-parent pf-l-split +kcSelectAuthListItemIconClass=pf-l-split__item select-auth-box-icon +kcSelectAuthListItemIconPropertyClass=fa-2x select-auth-box-icon-properties +kcSelectAuthListItemBodyClass=pf-l-split__item pf-l-stack +kcSelectAuthListItemHeadingClass=pf-l-stack__item select-auth-box-headline pf-c-title +kcSelectAuthListItemDescriptionClass=pf-l-stack__item select-auth-box-desc +kcSelectAuthListItemFillClass=pf-l-split__item pf-m-fill +kcSelectAuthListItemArrowClass=pf-l-split__item select-auth-box-arrow +kcSelectAuthListItemArrowIconClass=fa fa-angle-right fa-lg +kcSelectAuthListItemTitle=select-auth-box-paragraph + +##### css classes for the authenticators +kcAuthenticatorDefaultClass=fa fa-list list-view-pf-icon-lg +kcAuthenticatorPasswordClass=fa fa-unlock list-view-pf-icon-lg +kcAuthenticatorOTPClass=fa fa-mobile list-view-pf-icon-lg +kcAuthenticatorWebAuthnClass=fa fa-key list-view-pf-icon-lg +kcAuthenticatorWebAuthnPasswordlessClass=fa fa-key list-view-pf-icon-lg + +##### css classes for the OTP Login Form +kcLoginOTPListClass=pf-c-tile +kcLoginOTPListInputClass=pf-c-tile__input +kcLoginOTPListItemHeaderClass=pf-c-tile__header +kcLoginOTPListItemIconBodyClass=pf-c-tile__icon +kcLoginOTPListItemIconClass=fa fa-mobile +kcLoginOTPListItemTitleClass=pf-c-tile__title + +##### css classes for identity providers logos +kcCommonLogoIdP=kc-social-provider-logo kc-social-gray + +## Social +kcLogoIdP-facebook=fa fa-facebook +kcLogoIdP-google=fa fa-google +kcLogoIdP-github=fa fa-github +kcLogoIdP-linkedin=fa fa-linkedin +kcLogoIdP-instagram=fa fa-instagram +## windows instead of microsoft - not included in PF4 +kcLogoIdP-microsoft=fa fa-windows +kcLogoIdP-bitbucket=fa fa-bitbucket +kcLogoIdP-gitlab=fa fa-gitlab +kcLogoIdP-paypal=fa fa-paypal +kcLogoIdP-stackoverflow=fa fa-stack-overflow +kcLogoIdP-twitter=fa fa-twitter +kcLogoIdP-openshift-v4=pf-icon pf-icon-openshift +kcLogoIdP-openshift-v3=pf-icon pf-icon-openshift + +## Recovery codes +kcRecoveryCodesWarning=kc-recovery-codes-warning +kcRecoveryCodesList=kc-recovery-codes-list +kcRecoveryCodesActions=kc-recovery-codes-actions +kcRecoveryCodesConfirmation=kc-recovery-codes-confirmation +kcCheckClass=pf-c-check +kcCheckInputClass=pf-c-check__input +kcCheckLabelClass=pf-c-check__label From 12ae5998ef30e634e26b2478fa2df349474cf336 Mon Sep 17 00:00:00 2001 From: Erich Bremer Date: Tue, 6 Jun 2023 09:01:40 -0400 Subject: [PATCH 6/8] Split master into master and Halcyon realms to support remote Keycloak operation Add auto creation of admin Halcyon admin user --- .../ebremer/halcyon/server/keycloak/App.java | 39 ++++++--- .../keycloak/HalcyonApplianceBootstrap.java | 51 ++++++++++++ .../com/ebremer/halcyon/wicket/AdminPage.java | 2 +- src/main/resources/keycloak - Copy.json | 10 --- src/main/resources/keycloak-realm-config.json | 80 ++++++++++++++++--- 5 files changed, 146 insertions(+), 36 deletions(-) create mode 100644 src/main/java/com/ebremer/halcyon/server/keycloak/HalcyonApplianceBootstrap.java delete mode 100644 src/main/resources/keycloak - Copy.json diff --git a/src/main/java/com/ebremer/halcyon/server/keycloak/App.java b/src/main/java/com/ebremer/halcyon/server/keycloak/App.java index b3f66b87..2d48ce21 100644 --- a/src/main/java/com/ebremer/halcyon/server/keycloak/App.java +++ b/src/main/java/com/ebremer/halcyon/server/keycloak/App.java @@ -1,18 +1,16 @@ package com.ebremer.halcyon.server.keycloak; +import com.ebremer.halcyon.HalcyonSettings; import java.util.NoSuchElementException; - import org.keycloak.Config; import org.keycloak.exportimport.ExportImportManager; import org.keycloak.models.KeycloakSession; import org.keycloak.services.managers.ApplianceBootstrap; import org.keycloak.services.resources.KeycloakApplication; import org.keycloak.services.util.JsonConfigProviderFactory; - import com.ebremer.halcyon.server.keycloak.providers.JsonProviderFactory; import java.io.File; import java.io.IOException; - import lombok.extern.slf4j.Slf4j; import org.keycloak.exportimport.ExportImportConfig; import static org.keycloak.services.resources.KeycloakApplication.getSessionFactory; @@ -35,21 +33,36 @@ protected ExportImportManager bootstrap() { final ExportImportManager exportImportManager = super.bootstrap(); createMasterRealmAdminUser(); tryImportRealm(); + createRealmUser("admin","admin"); return exportImportManager; } private void createMasterRealmAdminUser() { - KeycloakSession session = getSessionFactory().create(); - ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session); - try { - session.getTransactionManager().begin(); - applianceBootstrap.createMasterRealmUser(properties.username(), properties.password()); - session.getTransactionManager().commit(); - } catch (Exception ex) { - log.warn("Couldn't create keycloak master admin user: {}", ex.getMessage()); - session.getTransactionManager().rollback(); + try (KeycloakSession session = getSessionFactory().create()) { + ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session); + try { + session.getTransactionManager().begin(); + applianceBootstrap.createMasterRealmUser(properties.username(), properties.password()); + session.getTransactionManager().commit(); + } catch (Exception ex) { + log.warn("Couldn't create keycloak master admin user: {}", ex.getMessage()); + session.getTransactionManager().rollback(); + } + } } + + private void createRealmUser(String username, String password) { + try (KeycloakSession session = getSessionFactory().create()) { + HalcyonApplianceBootstrap applianceBootstrap = new HalcyonApplianceBootstrap(session); + try { + session.getTransactionManager().begin(); + applianceBootstrap.createRealmUser(username, password); + session.getTransactionManager().commit(); + } catch (Exception ex) { + System.out.println(ex.getMessage()); + log.warn("Couldn't create keycloak "+HalcyonSettings.realm+" "+username+" user: {}", ex.getMessage()); + session.getTransactionManager().rollback(); + } } - session.close(); } private void tryImportRealm() { diff --git a/src/main/java/com/ebremer/halcyon/server/keycloak/HalcyonApplianceBootstrap.java b/src/main/java/com/ebremer/halcyon/server/keycloak/HalcyonApplianceBootstrap.java new file mode 100644 index 00000000..02f2ea5c --- /dev/null +++ b/src/main/java/com/ebremer/halcyon/server/keycloak/HalcyonApplianceBootstrap.java @@ -0,0 +1,51 @@ +package com.ebremer.halcyon.server.keycloak; + +import com.ebremer.halcyon.HalcyonSettings; +import org.keycloak.models.AdminRoles; +import org.keycloak.models.GroupModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserModel; +import org.keycloak.services.managers.ApplianceBootstrap; + +/** + * + * @author erich + */ +public class HalcyonApplianceBootstrap extends ApplianceBootstrap { + private final KeycloakSession session; + + public HalcyonApplianceBootstrap(KeycloakSession session) { + super(session); + this.session = session; + } + + public void createRealmUser(String username, String password) { + RealmModel realm = session.realms().getRealmByName(HalcyonSettings.realm); + session.getContext().setRealm(realm); + if (session.users().getUsersCount(realm) > 0) { + throw new IllegalStateException("Can't create initial user as users already exists"); + } + UserModel user = session.users().addUser(realm, username); + user.setEnabled(true); + UserCredentialModel usrCredModel = UserCredentialModel.password(password); + user.credentialManager().updateCredential(usrCredModel); + session.groups().getTopLevelGroupsStream(realm).forEach(yay->{ + System.out.println(yay.getName()+" "+yay.getId()); + if ("admin".equals(yay.getName())) { + user.joinGroup(yay); + } + }); + + + + + //GroupModel group = realm. + //user.joinGroup(group); + //RoleModel adminRole = realm.getRole(AdminRoles.MANAGE_REALM); + //user.grantRole(adminRole); + } + +} diff --git a/src/main/java/com/ebremer/halcyon/wicket/AdminPage.java b/src/main/java/com/ebremer/halcyon/wicket/AdminPage.java index 37238878..2ae45ae0 100644 --- a/src/main/java/com/ebremer/halcyon/wicket/AdminPage.java +++ b/src/main/java/com/ebremer/halcyon/wicket/AdminPage.java @@ -15,7 +15,7 @@ public AdminPage() { @Override public void onComponentTag(ComponentTag tag) { super.onComponentTag(tag); - tag.put("src", "/auth/admin/master/console/#/realms/"+HalcyonSettings.realm); + tag.put("src", "/auth/admin/"+HalcyonSettings.realm+"/console/?response_type#/Halcyon/users"); } }); diff --git a/src/main/resources/keycloak - Copy.json b/src/main/resources/keycloak - Copy.json deleted file mode 100644 index 1670e250..00000000 --- a/src/main/resources/keycloak - Copy.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "realm": "master", - "auth-server-url": "http://localhost:8888/auth/", - "ssl-required": "external", - "resource": "account", - "public-client": true, - "verify-token-audience": true, - "use-resource-role-mappings": true, - "confidential-port": 0 -} \ No newline at end of file diff --git a/src/main/resources/keycloak-realm-config.json b/src/main/resources/keycloak-realm-config.json index faaabe7a..80336413 100644 --- a/src/main/resources/keycloak-realm-config.json +++ b/src/main/resources/keycloak-realm-config.json @@ -47,6 +47,15 @@ "failureFactor": 30, "roles": { "realm": [ + { + "id": "7c27c564-4cc8-40d4-8279-95c20b92cc86", + "name": "admin", + "description": "", + "composite": false, + "clientRole": false, + "containerId": "d09e261f-309c-4d66-bf0e-674ce19772fd", + "attributes": {} + }, { "id": "28262476-5ea7-4b58-979e-99e356636c2c", "name": "default-roles-halcyon", @@ -58,9 +67,22 @@ "uma_authorization" ], "client": { + "realm-management": [ + "manage-clients", + "query-groups" + ], + "broker": [ + "read-token" + ], "account": [ + "view-applications", + "view-consent", "manage-account", - "view-profile" + "view-profile", + "manage-account-links", + "view-groups", + "delete-account", + "manage-consent" ] } }, @@ -419,8 +441,42 @@ "name": "admin", "path": "/admin", "attributes": {}, - "realmRoles": [], - "clientRoles": {}, + "realmRoles": [ + "offline_access", + "default-roles-halcyon", + "uma_authorization", + "admin" + ], + "clientRoles": { + "realm-management": [ + "manage-clients", + "view-identity-providers", + "view-realm", + "realm-admin", + "view-authorization", + "view-clients", + "manage-realm", + "query-groups", + "manage-users", + "manage-identity-providers", + "create-client", + "query-clients", + "query-realms" + ], + "broker": [ + "read-token" + ], + "account": [ + "view-applications", + "view-consent", + "manage-account", + "view-profile", + "manage-account-links", + "view-groups", + "delete-account", + "manage-consent" + ] + }, "subGroups": [] } ], @@ -446,8 +502,8 @@ "otpPolicyPeriod": 30, "otpPolicyCodeReusable": false, "otpSupportedApplications": [ - "totpAppFreeOTPName", "totpAppMicrosoftAuthenticatorName", + "totpAppFreeOTPName", "totpAppGoogleName" ], "webAuthnPolicyRpEntityName": "keycloak", @@ -1416,14 +1472,14 @@ "subComponents": {}, "config": { "allowed-protocol-mapper-types": [ + "oidc-usermodel-attribute-mapper", + "oidc-full-name-mapper", "saml-user-attribute-mapper", "saml-role-list-mapper", "oidc-address-mapper", - "oidc-usermodel-attribute-mapper", - "oidc-full-name-mapper", - "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-property-mapper", - "saml-user-property-mapper" + "saml-user-property-mapper", + "oidc-sha256-pairwise-sub-mapper" ] } }, @@ -1494,14 +1550,14 @@ "subComponents": {}, "config": { "allowed-protocol-mapper-types": [ + "oidc-sha256-pairwise-sub-mapper", + "saml-user-property-mapper", + "oidc-address-mapper", "saml-user-attribute-mapper", "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", - "saml-user-property-mapper", "saml-role-list-mapper", - "oidc-usermodel-property-mapper", - "oidc-address-mapper", - "oidc-sha256-pairwise-sub-mapper" + "oidc-usermodel-property-mapper" ] } }, From 558b46ee51ccb5f3af120796c021c99300abbd78 Mon Sep 17 00:00:00 2001 From: Erich Bremer Date: Tue, 6 Jun 2023 11:05:24 -0400 Subject: [PATCH 7/8] Root all paths --- .../halcyon/gui/HalcyonApplication.java | 35 ++++++++++++++---- .../keycloak/HALKeycloakOIDCFilter.java | 13 ++++--- .../java/com/ebremer/halcyon/server/Main.java | 24 +++++++----- .../ebremer/halcyon/server/keycloak/App.java | 1 - .../com/ebremer/halcyon/sparql/Sparql.html | 2 +- .../com/ebremer/halcyon/wicket/BasePage.html | 2 +- .../com/ebremer/halcyon/wicket/MenuPanel.java | 18 ++++----- .../favicon.ico} | Bin .../public-web-resources/images/halcyon.ico | Bin 0 -> 61798 bytes .../images}/halcyon.png | Bin src/main/resources/application.yml | 6 +-- 11 files changed, 63 insertions(+), 38 deletions(-) rename src/main/resources/META-INF/{halcyon.ico => public-web-resources/favicon.ico} (100%) create mode 100644 src/main/resources/META-INF/public-web-resources/images/halcyon.ico rename src/main/resources/META-INF/{ => public-web-resources/images}/halcyon.png (100%) diff --git a/src/main/java/com/ebremer/halcyon/gui/HalcyonApplication.java b/src/main/java/com/ebremer/halcyon/gui/HalcyonApplication.java index 2cdcbd25..f490f1e1 100644 --- a/src/main/java/com/ebremer/halcyon/gui/HalcyonApplication.java +++ b/src/main/java/com/ebremer/halcyon/gui/HalcyonApplication.java @@ -66,7 +66,32 @@ public void init() { getComponentPostOnBeforeRenderListeners().add(new StatelessChecker()); } getCspSettings().blocking().disabled(); - mountPage("/gui", HomePage.class); + mountPage("/", HomePage.class); + mountPage("/adminme", AdminPage.class); + mountPage("/accountpage", AccountPage.class); + mountPage("/login", Login.class); + mountPage("/ListImages", ListImages.class); + // mountPage("/gui/ListFeatures", ListFeatures.class); + mountPage("/viewer", MultiViewer.class); + mountPage("/collections", Collections.class); + mountPage("/sparql", Sparql.class); + mountPage("/about", About.class); + mountPage("/threed", Graph3D.class); + mountPage("/revisionhistory", RevisionHistory.class); + mountPage("/zephyr", Zephyr.class); + //mountPage("/login", LogHal.class); + //mountPage("/gui/dicom", DICOM.class); + //mountPage("/gui/dicom2", DCM.class); + } + + @Override + public RuntimeConfigurationType getConfigurationType() { + return RuntimeConfigurationType.DEVELOPMENT; + } +} + +/* + mountPage("/", HomePage.class); mountPage("/gui/adminme", AdminPage.class); mountPage("/gui/accountpage", AccountPage.class); mountPage("/gui/login", Login.class); @@ -83,10 +108,4 @@ public void init() { mountPage("/gui/login", LogHal.class); //mountPage("/gui/dicom", DICOM.class); //mountPage("/gui/dicom2", DCM.class); - } - - @Override - public RuntimeConfigurationType getConfigurationType() { - return RuntimeConfigurationType.DEVELOPMENT; - } -} +*/ \ No newline at end of file diff --git a/src/main/java/com/ebremer/halcyon/keycloak/HALKeycloakOIDCFilter.java b/src/main/java/com/ebremer/halcyon/keycloak/HALKeycloakOIDCFilter.java index e57a3efa..1f79ec85 100644 --- a/src/main/java/com/ebremer/halcyon/keycloak/HALKeycloakOIDCFilter.java +++ b/src/main/java/com/ebremer/halcyon/keycloak/HALKeycloakOIDCFilter.java @@ -34,8 +34,11 @@ private boolean shouldSkip(HttpServletRequest request) { if (skipPattern == null) { return false; } + String requestPath = request.getRequestURI().substring(request.getContextPath().length()); - return skipPattern.matcher(requestPath).matches(); + boolean haha = skipPattern.matcher(requestPath).matches(); + //System.out.println(haha+" --> "+request.getRequestURI()); + return haha; } public void setConfig(KeycloakOIDCFilterConfig config) { @@ -48,14 +51,14 @@ public void setSessionIdMapper(SessionIdMapper mapper) { @Override public void init(final FilterConfig filterConfig) throws ServletException { - filterConfig.getInitParameterNames().asIterator().forEachRemaining(p->{ - System.out.println("PARAM --> "+p); - }); + //filterConfig.getInitParameterNames().asIterator().forEachRemaining(p->{ + // System.out.println("PARAM --> "+p); + //}); if (!filterConfig.getInitParameterNames().hasMoreElements()) { super.init(config); } else { - System.out.println("INIT -------------------> "+filterConfig.getClass().toGenericString()); + //System.out.println("INIT -------------------> "+filterConfig.getClass().toGenericString()); if (filterConfig instanceof FilterHolder fh) { if (fh.getInitParameter(KeycloakOIDCFilter.CONFIG_FILE_PARAM)==null) { fh.setInitParameter(KeycloakOIDCFilter.CONFIG_FILE_PARAM, "keycloak.json"); diff --git a/src/main/java/com/ebremer/halcyon/server/Main.java b/src/main/java/com/ebremer/halcyon/server/Main.java index 24603724..87ea348f 100644 --- a/src/main/java/com/ebremer/halcyon/server/Main.java +++ b/src/main/java/com/ebremer/halcyon/server/Main.java @@ -27,6 +27,7 @@ import org.apache.wicket.RuntimeConfigurationType; import org.apache.wicket.protocol.http.ContextParamWebApplicationFactory; import org.apache.wicket.protocol.http.WicketFilter; +import static org.apache.wicket.protocol.http.WicketFilter.IGNORE_PATHS_PARAM; import org.keycloak.adapters.servlet.KeycloakOIDCFilter; import org.keycloak.adapters.spi.SessionIdMapper; import org.mitre.dsmiley.httpproxy.ProxyServlet; @@ -60,7 +61,7 @@ public static SessionIdMapper getSessionIdMapper() { @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public ServletRegistrationBean proxyServletRegistrationBean() { - ServletRegistrationBean bean = new ServletRegistrationBean(new HalcyonProxyServlet(), "/sparql/*"); + ServletRegistrationBean bean = new ServletRegistrationBean(new HalcyonProxyServlet(), "/rdf/*"); bean.addInitParameter("targetUri", "http://localhost:"+settings.GetSPARQLPort()+"/rdf"); bean.addInitParameter(ProxyServlet.P_PRESERVECOOKIES, "true"); bean.addInitParameter(ProxyServlet.P_HANDLEREDIRECTS, "true"); @@ -102,6 +103,7 @@ public UndertowServletWebServerFactory embeddedServletContainerFactory() { ServletRegistrationBean iboxServletRegistration () { System.out.println("iboxServletRegistration order: "+Ordered.LOWEST_PRECEDENCE); ServletRegistrationBean srb = new ServletRegistrationBean(); + srb.setOrder(Ordered.HIGHEST_PRECEDENCE+2); srb.setServlet(new ImageServer()); srb.setUrlMappings(Arrays.asList("/iiif/*")); return srb; @@ -109,7 +111,6 @@ ServletRegistrationBean iboxServletRegistration () { @Bean public FilterRegistrationBean KeycloakOIDCFilterFilterRegistration(){ - System.out.println("KeycloakOIDCFilterFilterRegistration order: "+Ordered.HIGHEST_PRECEDENCE); HALKeycloakOIDCFilter filter = new HALKeycloakOIDCFilter(); //filter.setSessionIdMapper(SessionsManager.getSessionsManager().getSessionIdMapper()); filter.setSessionIdMapper(getSessionIdMapper()); @@ -117,9 +118,10 @@ public FilterRegistrationBean KeycloakOIDCFilterFilterReg registration.setFilter(filter); registration.setName("keycloak"); registration.addUrlPatterns("/*"); - registration.setOrder(0); + registration.setOrder(Ordered.HIGHEST_PRECEDENCE+1); registration.addInitParameter(KeycloakOIDCFilter.CONFIG_FILE_PARAM, "keycloak.json"); - registration.addInitParameter(KeycloakOIDCFilter.SKIP_PATTERN_PARAM, "(^/multi-viewer.*|^/iiif.*|^/gui/viewer.*|^/gui|^/gui/about|^/gui/ListImages.*|^/sparql.*|^/wicket/resource/com.*\\.css|^/gui/public|^/gui/vendor/openseadragon/.*|^/auth/.*|^/favicon.ico|^/auth/.*$)"); + //registration.addInitParameter(KeycloakOIDCFilter.SKIP_PATTERN_PARAM, "(^/realms/.*|/|/;jsessionid=.*|/gui/images/halcyon.png|^/wicket/resource/.*|^/multi-viewer.*|^/iiif.*|^/gui/viewer.*|^/gui|^/gui/about|^/gui/ListImages.*|^/sparql.*|^/wicket/resource/com.*\\.css|^/gui/public|^/gui/vendor/openseadragon/.*|^/auth/.*|^/favicon.ico|^/auth/.*$)"); + registration.addInitParameter(KeycloakOIDCFilter.SKIP_PATTERN_PARAM, "(/;jsessionid=.*|/gui/images/halcyon.png|^/wicket/resource/.*|^/multi-viewer.*|^/iiif.*|^/|^/about|^/ListImages.*|^/wicket/resource/com.*\\.css||^/auth/.*|^/favicon.ico)"); registration.setEnabled(true); return registration; } @@ -131,11 +133,13 @@ public FilterRegistrationBean wicketFilterRegistration(){ WicketFilter filter = new WicketFilter(hal); filter.setFilterPath("/"); FilterRegistrationBean registration = new FilterRegistrationBean<>(); + registration.setName("HalcyonWicketFilter"); registration.setFilter(filter); - registration.setOrder(2); + registration.setOrder(Ordered.LOWEST_PRECEDENCE); registration.addInitParameter(ContextParamWebApplicationFactory.APP_CLASS_PARAM, HalcyonApplication.class.getName()); - registration.addInitParameter(WicketFilter.FILTER_MAPPING_PARAM, "/"); - registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD); + registration.addInitParameter(WicketFilter.FILTER_MAPPING_PARAM, "/*"); + registration.addInitParameter(IGNORE_PATHS_PARAM, "/auth/,/three.js/,/multi-viewer/,/iiif/,/halcyon/,/images/,/favicon.ico,/rdf/"); + //registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD); return registration; } @@ -143,14 +147,13 @@ public FilterRegistrationBean wicketFilterRegistration(){ ServletRegistrationBean HalcyonServletRegistration () { ServletRegistrationBean srb = new ServletRegistrationBean(); srb.setServlet(new FeatureServer()); - srb.setOrder(20); + srb.setOrder(Ordered.HIGHEST_PRECEDENCE+3); srb.setUrlMappings(Arrays.asList("/halcyon/*")); return srb; } @Configuration public class HalcyonResourceConfiguration implements WebMvcConfigurer { - @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { String mv = settings.getMultiewerLocation(); @@ -160,8 +163,9 @@ public void addResourceHandlers(ResourceHandlerRegistry registry) { } registry.addResourceHandler("/multi-viewer/**").addResourceLocations(mv); } else { + registry.addResourceHandler("/**").addResourceLocations("classpath:/META-INF/public-web-resources/"); registry.addResourceHandler("/multi-viewer/**").addResourceLocations("classpath:/META-INF/public-web-resources/multi-viewer/"); - registry.addResourceHandler("/three.js/**").addResourceLocations("classpath:/META-INF/public-web-resources/three.js/"); + //registry.addResourceHandler("/three.js/**").addResourceLocations("classpath:/META-INF/public-web-resources/three.js/"); } } } diff --git a/src/main/java/com/ebremer/halcyon/server/keycloak/App.java b/src/main/java/com/ebremer/halcyon/server/keycloak/App.java index 2d48ce21..fac287f5 100644 --- a/src/main/java/com/ebremer/halcyon/server/keycloak/App.java +++ b/src/main/java/com/ebremer/halcyon/server/keycloak/App.java @@ -19,7 +19,6 @@ @Slf4j public class App extends KeycloakApplication { - static ServerProperties properties; @Override diff --git a/src/main/java/com/ebremer/halcyon/sparql/Sparql.html b/src/main/java/com/ebremer/halcyon/sparql/Sparql.html index c8485b13..274d81d0 100644 --- a/src/main/java/com/ebremer/halcyon/sparql/Sparql.html +++ b/src/main/java/com/ebremer/halcyon/sparql/Sparql.html @@ -15,7 +15,7 @@

SPARQL Endpoint

Yasgui.defaults.requestConfig.headers = {'AUTHORIZATION': 'Bearer '+token}; const yasgui = new Yasgui(document.getElementById("yasgui"), { requestConfig: { - endpoint: "/sparql"}, + endpoint: "/rdf"}, copyEndpointOnNewTab: false } ); diff --git a/src/main/java/com/ebremer/halcyon/wicket/BasePage.html b/src/main/java/com/ebremer/halcyon/wicket/BasePage.html index 7ec718da..1d3c00c9 100644 --- a/src/main/java/com/ebremer/halcyon/wicket/BasePage.html +++ b/src/main/java/com/ebremer/halcyon/wicket/BasePage.html @@ -2,7 +2,7 @@ Halcyon - +
diff --git a/src/main/java/com/ebremer/halcyon/wicket/MenuPanel.java b/src/main/java/com/ebremer/halcyon/wicket/MenuPanel.java index 96343dbd..1adad1d4 100644 --- a/src/main/java/com/ebremer/halcyon/wicket/MenuPanel.java +++ b/src/main/java/com/ebremer/halcyon/wicket/MenuPanel.java @@ -22,15 +22,15 @@ public MenuPanel(String id) { super(id); HalcyonPrincipal hp = HalcyonSession.get().getHalcyonPrincipal(); String host = HalcyonSettings.getSettings().getProxyHostName(); - add(new ExternalLink("home", host+"/gui","Home")); - add(new ExternalLink("images", host+"/gui/ListImages","Images")); - add(new ExternalLink("about", host+"/gui/about","About")); - ExternalLink security = new ExternalLink("security", host+"/gui/adminme","Security"); - ExternalLink sparql = new ExternalLink("sparql", host+"/gui/sparql","SPARQL"); - ExternalLink account = new ExternalLink("account", host+"/gui/accountpage","Account"); - ExternalLink threed = new ExternalLink("threed", host+"/gui/threed","3D"); - ExternalLink collections = new ExternalLink("collections", host+"/gui/collections","Collections"); - ExternalLink revisionhistory = new ExternalLink("revisionhistory", host+"/gui/revisionhistory","Revision History"); + add(new ExternalLink("home", host+"/","Home")); + add(new ExternalLink("images", host+"/ListImages","Images")); + add(new ExternalLink("about", host+"/about","About")); + ExternalLink security = new ExternalLink("security", host+"/adminme","Security"); + ExternalLink sparql = new ExternalLink("sparql", host+"/sparql","SPARQL"); + ExternalLink account = new ExternalLink("account", host+"/accountpage","Account"); + ExternalLink threed = new ExternalLink("threed", host+"/threed","3D"); + ExternalLink collections = new ExternalLink("collections", host+"/collections","Collections"); + ExternalLink revisionhistory = new ExternalLink("revisionhistory", host+"/revisionhistory","Revision History"); //ExternalLink login = new ExternalLink("loginLink", host+"/gui/login","Login"); Link login = new Link("loginLink") { @Override diff --git a/src/main/resources/META-INF/halcyon.ico b/src/main/resources/META-INF/public-web-resources/favicon.ico similarity index 100% rename from src/main/resources/META-INF/halcyon.ico rename to src/main/resources/META-INF/public-web-resources/favicon.ico diff --git a/src/main/resources/META-INF/public-web-resources/images/halcyon.ico b/src/main/resources/META-INF/public-web-resources/images/halcyon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a89fc2bf44132fa3ce98f63689427d5cc1f62be3 GIT binary patch literal 61798 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 RFm1w^EDviv7}TBB{{y%&p)mje literal 0 HcmV?d00001 diff --git a/src/main/resources/META-INF/halcyon.png b/src/main/resources/META-INF/public-web-resources/images/halcyon.png similarity index 100% rename from src/main/resources/META-INF/halcyon.png rename to src/main/resources/META-INF/public-web-resources/images/halcyon.png diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 75a0db96..fe58b7f8 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -20,9 +20,9 @@ keycloak: password: admin context-redirect: true - migration: - importProvider: singleFile - importLocation: keycloak-realm-config.json + #migration: + # importProvider: singleFile + #importLocation: keycloak-realm-config.json logging: level: From 387bb103977294c7870a4fc48e7d3f15c63cc5bd Mon Sep 17 00:00:00 2001 From: Erich Bremer Date: Tue, 6 Jun 2023 12:03:20 -0400 Subject: [PATCH 8/8] Add support for Hawkeye sub-project --- .../com/ebremer/halcyon/HalcyonSettings.java | 12 ++++++++++- .../java/com/ebremer/halcyon/server/Main.java | 21 +++++++++++++------ .../public-web-resources/hawkeye/index.html | 3 +++ 3 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 src/main/resources/META-INF/public-web-resources/hawkeye/index.html diff --git a/src/main/java/com/ebremer/halcyon/HalcyonSettings.java b/src/main/java/com/ebremer/halcyon/HalcyonSettings.java index cfda5ce5..3f80ff2b 100644 --- a/src/main/java/com/ebremer/halcyon/HalcyonSettings.java +++ b/src/main/java/com/ebremer/halcyon/HalcyonSettings.java @@ -49,6 +49,7 @@ public final class HalcyonSettings { private final Property urlpathprefix; private final Property SPARQLPORT; private final Property MULTIVIEWERLOCATION; + private final Property HAWKEYELOCATION; private static final String MasterSettingsLocation = "settings.ttl"; private Resource Master; private final HashMap mappings; @@ -83,6 +84,7 @@ private HalcyonSettings() { SPARQLPORT = m.createProperty(HAL.NS+"SPARQLport"); urlpathprefix = m.createProperty(HAL.NS+"urlpathprefix"); MULTIVIEWERLOCATION = m.createProperty(HAL.NS+"MultiviewerLocation"); + HAWKEYELOCATION = m.createProperty(HAL.NS+"HawkeyeLocation"); } public String getwebfiles() { @@ -238,12 +240,19 @@ public String getRDFSecurityStoreLocation() { } public String getMultiewerLocation() { - if (m.contains(Master, m.createProperty(HAL.NS+"MultiviewerLocation"))) { + if (m.contains(Master, MULTIVIEWERLOCATION)) { return m.getProperty(Master, MULTIVIEWERLOCATION).getObject().asResource().getURI(); } return null; } + public String getHawkeyeLocation() { + if (m.contains(Master, HAWKEYELOCATION)) { + return m.getProperty(Master, HAWKEYELOCATION).getObject().asResource().getURI(); + } + return null; + } + public HashMap getmappings() { return mappings; } @@ -273,6 +282,7 @@ public static void main(String[] args) throws MalformedURLException { HalcyonSettings s = HalcyonSettings.getSettings(); System.out.println("Proxy Host Name "+s.getProxyHostName()); System.out.println("Port "+s.GetHTTPPort()); + System.out.println("HAWKEYE : "+s.getHawkeyeLocation()); for (StorageLocation sl : s.getStorageLocations()) { System.out.println(sl.path.toUri()+" **** "+sl.urlpath); } diff --git a/src/main/java/com/ebremer/halcyon/server/Main.java b/src/main/java/com/ebremer/halcyon/server/Main.java index 87ea348f..e0b6db08 100644 --- a/src/main/java/com/ebremer/halcyon/server/Main.java +++ b/src/main/java/com/ebremer/halcyon/server/Main.java @@ -121,7 +121,7 @@ public FilterRegistrationBean KeycloakOIDCFilterFilterReg registration.setOrder(Ordered.HIGHEST_PRECEDENCE+1); registration.addInitParameter(KeycloakOIDCFilter.CONFIG_FILE_PARAM, "keycloak.json"); //registration.addInitParameter(KeycloakOIDCFilter.SKIP_PATTERN_PARAM, "(^/realms/.*|/|/;jsessionid=.*|/gui/images/halcyon.png|^/wicket/resource/.*|^/multi-viewer.*|^/iiif.*|^/gui/viewer.*|^/gui|^/gui/about|^/gui/ListImages.*|^/sparql.*|^/wicket/resource/com.*\\.css|^/gui/public|^/gui/vendor/openseadragon/.*|^/auth/.*|^/favicon.ico|^/auth/.*$)"); - registration.addInitParameter(KeycloakOIDCFilter.SKIP_PATTERN_PARAM, "(/;jsessionid=.*|/gui/images/halcyon.png|^/wicket/resource/.*|^/multi-viewer.*|^/iiif.*|^/|^/about|^/ListImages.*|^/wicket/resource/com.*\\.css||^/auth/.*|^/favicon.ico)"); + registration.addInitParameter(KeycloakOIDCFilter.SKIP_PATTERN_PARAM, "(^/hawkeye/.*|/;jsessionid=.*|/gui/images/halcyon.png|^/wicket/resource/.*|^/multi-viewer.*|^/iiif.*|^/|^/about|^/ListImages.*|^/wicket/resource/com.*\\.css||^/auth/.*|^/favicon.ico)"); registration.setEnabled(true); return registration; } @@ -138,7 +138,7 @@ public FilterRegistrationBean wicketFilterRegistration(){ registration.setOrder(Ordered.LOWEST_PRECEDENCE); registration.addInitParameter(ContextParamWebApplicationFactory.APP_CLASS_PARAM, HalcyonApplication.class.getName()); registration.addInitParameter(WicketFilter.FILTER_MAPPING_PARAM, "/*"); - registration.addInitParameter(IGNORE_PATHS_PARAM, "/auth/,/three.js/,/multi-viewer/,/iiif/,/halcyon/,/images/,/favicon.ico,/rdf/"); + registration.addInitParameter(IGNORE_PATHS_PARAM, "/auth/,/three.js/,/multi-viewer/,/iiif/,/halcyon/,/images/,/favicon.ico,/rdf/,/hawkeye/"); //registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD); return registration; } @@ -163,10 +163,19 @@ public void addResourceHandlers(ResourceHandlerRegistry registry) { } registry.addResourceHandler("/multi-viewer/**").addResourceLocations(mv); } else { - registry.addResourceHandler("/**").addResourceLocations("classpath:/META-INF/public-web-resources/"); - registry.addResourceHandler("/multi-viewer/**").addResourceLocations("classpath:/META-INF/public-web-resources/multi-viewer/"); - //registry.addResourceHandler("/three.js/**").addResourceLocations("classpath:/META-INF/public-web-resources/three.js/"); - } + registry.addResourceHandler("/multi-viewer/**").addResourceLocations("classpath:/META-INF/public-web-resources/multi-viewer/"); + } + String he = settings.getHawkeyeLocation(); + if (he!=null) { + if (he.startsWith("file:///")) { + he = he.replace("file:///", "file:/"); + } + registry.addResourceHandler("/hawkeye/**").addResourceLocations(he); + } else { + registry.addResourceHandler("/hawkeye/**").addResourceLocations("classpath:/META-INF/public-web-resources/hawkeye/"); + } + registry.addResourceHandler("/three.js/**").addResourceLocations("classpath:/META-INF/public-web-resources/three.js/"); + registry.addResourceHandler("/images/**").addResourceLocations("classpath:/META-INF/public-web-resources/images/"); } } diff --git a/src/main/resources/META-INF/public-web-resources/hawkeye/index.html b/src/main/resources/META-INF/public-web-resources/hawkeye/index.html new file mode 100644 index 00000000..9b4bfb2b --- /dev/null +++ b/src/main/resources/META-INF/public-web-resources/hawkeye/index.html @@ -0,0 +1,3 @@ +HAWKEYE +Welcome to Hawkeye project + \ No newline at end of file