Repository: keycloak/keycloak (Java)

Issue:
Provide CLI Parameters for jgroups.* options
### Description

We updated the docs to add Java options to append for the JGroups JDBC configuration. This is now difficult to read and we don't validate these parameters. 

### Discussion

https://github.com/keycloak/keycloak/issues/39614

### Motivation

This is difficult to validate, and hard to pass compared to other parameters. The configuration should be consistent with other configuration options, and document

### Details

See https://www.keycloak.org/server/caching for the docs. 

The following items should become first class options: 

* jgroups.bind.address
* jgroups.bind.port
* jgroups.external_port (need to be system properties as they are not in the XML of ISPN)
* jgroups.external_addr (need to be system properties as they are not in the XML of ISPN)

Not exposed on CLI - we can still expose it as a SPI option:
* jgroups.fd.port-offset

The reason the FD port is in the docs is due to the fact that it opens a network port. We don't expect users to change it. 
With ISPN 16, there is a plan to remove FDSOCK from the TCP setup, see https://github.com/infinispan/infinispan/pull/15038

### Implementation idea

Add CLI options that then set the system properties so they are available for all caches.

```
--cache-embedded-network-bind-address
--cache-embedded-network-bind-port
--cache-embedded-network-external-address
--cache-embedded-network-external-port
```

SPI option:

```
--spi-cache-embedded--default--network-fd-port-offset
```





Fix (patch):
diff --git a/core/src/main/java/org/keycloak/util/UriUtils.java b/core/src/main/java/org/keycloak/util/UriUtils.java
index f9687a55155f..775729adb832 100755
--- a/core/src/main/java/org/keycloak/util/UriUtils.java
+++ b/core/src/main/java/org/keycloak/util/UriUtils.java
@@ -18,7 +18,8 @@ public static String getOrigin(URI uri) {
 
     public static String getOrigin(String uri) {
         String u = uri.toString();
-        return u.substring(0, u.indexOf('/', 8));
+        int e = u.indexOf('/', 8);
+        return e != -1 ? u.substring(0, u.indexOf('/', 8)) : u;
     }
 
     public static boolean isOrigin(String url) {
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
index aa3d9baa53c4..ebfca94936e3 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
@@ -178,12 +178,11 @@ <h2 data-ng-show="create" class="pull-left"><span>{{realm.realm}}</span> Add Cli
                     <span tooltip-placement="right" tooltip="Valid URI pattern a browser can redirect to after a successful login or logout.  Simple wildcards are allowed i.e. 'http://example.com/*'.  Relative path can be specified too i.e. /my/relative/path/*.  Relative paths will generate a redirect URI using the request's host and port.  For SAML, you must set valid URI patterns if you are relying on the consumer service URL embedded with the login request." class="fa fa-info-circle"></span>
                 </div>
                 <div class="form-group" data-ng-show="!client.bearerOnly && !create">
-                    <label class="col-sm-2 control-label" for="baseUrl">Default Redirect URL</label>
+                    <label class="col-sm-2 control-label" for="baseUrl">Base URL</label>
                     <div class="col-sm-6">
-                        <input class="form-control" type="text" name="baseUrl" id="baseUrl"
-                               data-ng-model="client.baseUrl">
+                        <input class="form-control" type="text" name="baseUrl" id="baseUrl" data-ng-model="client.baseUrl">
                     </div>
-                    <span tooltip-placement="right" tooltip="Default URL to use when the auth server needs to redirect back to the client.  This URL will also be used when the auth server needs to link to the client for any reason." class="fa fa-info-circle"></span>
+                    <span tooltip-placement="right" tooltip="Default URL to use when the auth server needs to redirect or link back to the client." class="fa fa-info-circle"></span>
                 </div>
                 <div class="form-group" data-ng-hide="create || protocol == 'saml'">
                     <label class="col-sm-2 control-label" for="adminUrl">Admin URL</label>
diff --git a/forms/common-themes/src/main/resources/theme/base/login/error.ftl b/forms/common-themes/src/main/resources/theme/base/login/error.ftl
index 2c8c1539bad3..95de521af8d9 100755
--- a/forms/common-themes/src/main/resources/theme/base/login/error.ftl
+++ b/forms/common-themes/src/main/resources/theme/base/login/error.ftl
@@ -7,6 +7,9 @@
     <#elseif section = "form">
         <div id="kc-error-message">
             <p class="instruction">${message.summary}</p>
+            <#if client?? && client.baseUrl?has_content>
+                <p><a href="${client.baseUrl}">${msg("backToApplication")}</a></p>
+            </#if>
         </div>
     </#if>
 </@layout.registrationLayout>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
index 6d34f4e45987..0cedf3ed0fa1 100755
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
@@ -157,8 +157,7 @@ couldNotSendAuthenticationRequestMessage=Could not send authentication request t
 unexpectedErrorHandlingRequestMessage=Unexpected error when handling authentication request to identity provider [{0}].
 invalidAccessCodeMessage=Invalid access code.
 sessionNotActiveMessage=Session not active.
-unknownCodeMessage=Unknown code, please login again through your application.
-invalidCodeMessage=Invalid code, please login again through your application.
+invalidCodeMessage=An error occurred, please login again through your application.
 identityProviderUnexpectedErrorMessage=Unexpected error when authenticating with identity provider
 identityProviderNotFoundMessage=Could not find an identity provider with the identifier [{0}].
 realmSupportsNoCredentialsMessage=Realm [{0}] does not support any credential type.
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 00e86f087265..0af14420147e 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -36,6 +36,7 @@
 import org.keycloak.representations.idm.SocialLinkRepresentation;
 import org.keycloak.representations.idm.UserFederationProviderRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.util.UriUtils;
 
 import java.io.IOException;
 import java.net.URI;
@@ -526,7 +527,7 @@ public static ClientModel createClient(KeycloakSession session, RealmModel realm
         client.setManagementUrl(resourceRep.getAdminUrl());
         if (resourceRep.isSurrogateAuthRequired() != null)
             client.setSurrogateAuthRequired(resourceRep.isSurrogateAuthRequired());
-        client.setBaseUrl(resourceRep.getBaseUrl());
+        if (resourceRep.getBaseUrl() != null) client.setBaseUrl(resourceRep.getBaseUrl());
         if (resourceRep.isBearerOnly() != null) client.setBearerOnly(resourceRep.isBearerOnly());
         if (resourceRep.isConsentRequired() != null) client.setConsentRequired(resourceRep.isConsentRequired());
         if (resourceRep.isPublicClient() != null) client.setPublicClient(resourceRep.isPublicClient());
@@ -576,12 +577,8 @@ public static ClientModel createClient(KeycloakSession session, RealmModel realm
                 Set<String> origins = new HashSet<String>();
                 for (String redirectUri : resourceRep.getRedirectUris()) {
                     logger.debugv("add redirect-uri to origin: {0}", redirectUri);
-                    if (redirectUri.startsWith("http:")) {
-                        URI uri = URI.create(redirectUri);
-                        String origin = uri.getScheme() + "://" + uri.getHost();
-                        if (uri.getPort() != -1) {
-                            origin += ":" + uri.getPort();
-                        }
+                    if (redirectUri.startsWith("http")) {
+                        String origin = UriUtils.getOrigin(redirectUri);
                         logger.debugv("adding default client origin: {0}" , origin);
                         origins.add(origin);
                     }
diff --git a/services/src/main/java/org/keycloak/services/messages/Messages.java b/services/src/main/java/org/keycloak/services/messages/Messages.java
index 0c219daf8eeb..aa2e9abd87d9 100755
--- a/services/src/main/java/org/keycloak/services/messages/Messages.java
+++ b/services/src/main/java/org/keycloak/services/messages/Messages.java
@@ -134,8 +134,6 @@ public class Messages {
 
     public static final String SESSION_NOT_ACTIVE = "sessionNotActiveMessage";
 
-    public static final String UNKNOWN_CODE = "unknownCodeMessage";
-
     public static final String INVALID_CODE = "invalidCodeMessage";
 
     public static final String IDENTITY_PROVIDER_UNEXPECTED_ERROR = "identityProviderUnexpectedErrorMessage";
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index f3f5937fc635..1a8be495a1e4 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -195,7 +195,7 @@ public boolean check(String code) {
             clientCode = ClientSessionCode.parse(code, session, realm);
             if (clientCode == null) {
                 event.error(Errors.INVALID_CODE);
-                response = ErrorPage.error(session, Messages.UNKNOWN_CODE);
+                response = ErrorPage.error(session, Messages.INVALID_CODE);
                 return false;
             }
             session.getContext().setClient(clientCode.getClientSession().getClient());
@@ -288,7 +288,7 @@ public Response processLogin(@QueryParam("code") String code,
         ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm);
         if (clientCode == null) {
             event.error(Errors.INVALID_CODE);
-            return ErrorPage.error(session, Messages.UNKNOWN_CODE);
+            return ErrorPage.error(session, Messages.INVALID_CODE);
         }
 
         ClientSessionModel clientSession = clientCode.getClientSession();
@@ -428,7 +428,7 @@ public Response processRegister(@QueryParam("code") String code,
         ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm);
         if (clientCode == null) {
             event.error(Errors.INVALID_CODE);
-            return ErrorPage.error(session, Messages.UNKNOWN_CODE);
+            return ErrorPage.error(session, Messages.INVALID_CODE);
         }
         if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE)) {
             event.error(Errors.INVALID_CODE);
@@ -865,7 +865,7 @@ public Response sendPasswordReset(@QueryParam("code") String code,
         ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm);
         if (accessCode == null) {
             event.error(Errors.INVALID_CODE);
-            return ErrorPage.error(session, Messages.UNKNOWN_CODE);
+            return ErrorPage.error(session, Messages.INVALID_CODE);
         }
         ClientSessionModel clientSession = accessCode.getClientSession();