diff --git a/.gitignore b/.gitignore
index df6cd6e..db75fcc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -172,3 +172,4 @@ buildNumber.properties
.classpath
# End of https://www.toptal.com/developers/gitignore/api/maven,intellij+all,eclipse
+/patch-tool/.work/
diff --git a/patch-tool/README.md b/patch-tool/README.md
new file mode 100644
index 0000000..50d13ef
--- /dev/null
+++ b/patch-tool/README.md
@@ -0,0 +1,116 @@
+# Keycloak Git Branch Diff & Patch Tool
+
+This script is designed to work **specifically with the [Keycloak Git repository](https://github.com/keycloak/keycloak)**. It compares selected Java files between two branches of the Keycloak repo, generates a patch, and (optionally) applies that patch to a target directory.
+
+## Features
+
+- Clones the Keycloak repository into a temporary **.work** directory
+- Extracts only files listed in `keycloak-files.txt` (tailored for Keycloak source paths)
+- Compares two branches and creates a **unified diff patch**
+- Cleans up file paths and replaces package names in the patch for project-specific needs
+- Optionally applies the patch to the **parent directory** of the current folder
+
+## Requirements
+
+- **Bash** (Unix/Linux/macOS environment)
+- **git** installed and configured
+- Access to the Keycloak repository (SSH key if private)
+- A `keycloak-files.txt` file in the current directory containing relative file paths from the Keycloak repo
+
+## Usage
+
+```bash
+./patch.sh [--apply] OLD_BRANCH NEW_BRANCH
+````
+
+### Arguments
+
+* `OLD_BRANCH` – The base branch in the Keycloak repo (e.g., `origin/release/26.0`)
+* `NEW_BRANCH` – The comparison branch in the Keycloak repo (e.g., `origin/archive/release/26.1`)
+* `--apply` – Optional flag to **apply** the generated patch to the parent directory
+
+### Examples
+
+Generate patch without applying:
+
+```bash
+./patch.sh origin/release/26.0 origin/archive/release/26.1
+```
+
+Generate and apply patch:
+
+```bash
+./patch.sh --apply origin/release/26.0 origin/archive/release/26.1
+```
+
+## How It Works
+
+```plaintext
++-----------------------------+
+| Start script |
++-----------------------------+
+ |
+ v
++-----------------------------+
+| Validate keycloak-files.txt |
++-----------------------------+
+ |
+ v
++-----------------------------+
+| Clone Keycloak repository |
+| into .work/repo |
++-----------------------------+
+ |
+ v
++-----------------------------+
+| Validate branches |
++-----------------------------+
+ |
+ v
++--------------------------------------+
+| Export listed files from OLD_BRANCH |
+| to .work/files/old |
++--------------------------------------+
+ |
+ v
++--------------------------------------+
+| Export listed files from NEW_BRANCH |
+| to .work/files/new |
++--------------------------------------+
+ |
+ v
++-----------------------------+
+| Generate patch file |
+| .work/patch.patch |
++-----------------------------+
+ |
+ v
++--------------------------------+
+| Post-process patch (clean paths,|
+| replace package names) |
++--------------------------------+
+ |
+ v
++-----------------------------------------+
+| If --apply flag set: |
+| Apply patch to parent directory |
++-----------------------------------------+
+ |
+ v
++-----------------------------+
+| Script ends with patch ready|
++-----------------------------+
+```
+
+## Output
+
+* Generated patch file: `.work/patch.patch`
+* Temporary exported files in `.work/files/old` and `.work/files/new`
+
+## Notes
+
+* The `.work` directory is deleted and recreated on each run to ensure a clean workspace.
+* The script exits immediately on any error (`set -e`).
+* Missing files on either branch will be reported but won’t stop the script.
+* Patch is applied **only** if the `--apply` option is specified.
+
diff --git a/patch-tool/keycloak-files.txt b/patch-tool/keycloak-files.txt
new file mode 100644
index 0000000..5229975
--- /dev/null
+++ b/patch-tool/keycloak-files.txt
@@ -0,0 +1,51 @@
+services/src/main/java/org/keycloak/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java
+services/src/main/java/org/keycloak/broker/saml/mappers/AdvancedAttributeToRoleMapper.java
+services/src/main/java/org/keycloak/broker/saml/mappers/AttributeToRoleMapper.java
+services/src/main/java/org/keycloak/broker/saml/mappers/UserAttributeMapper.java
+services/src/main/java/org/keycloak/broker/saml/mappers/UsernameTemplateMapper.java
+services/src/main/java/org/keycloak/broker/saml/SAMLDataMarshaller.java
+services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
+services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
+services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java
+services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java
+saml-core-api/src/main/java/org/keycloak/dom/saml/v2/assertion/AssertionType.java
+saml-core-api/src/main/java/org/keycloak/dom/saml/v2/assertion/AttributeStatementType.java
+saml-core-api/src/main/java/org/keycloak/dom/saml/v2/metadata/AttributeConsumingServiceType.java
+saml-core-api/src/main/java/org/keycloak/dom/saml/v2/metadata/EntitiesDescriptorType.java
+saml-core-api/src/main/java/org/keycloak/dom/saml/v2/metadata/EntityDescriptorType.java
+saml-core-api/src/main/java/org/keycloak/dom/saml/v2/metadata/SPSSODescriptorType.java
+saml-core-api/src/main/java/org/keycloak/dom/saml/v2/protocol/ResponseType.java
+services/src/main/java/org/keycloak/protocol/saml/mappers/SamlMetadataDescriptorUpdater.java
+services/src/main/java/org/keycloak/protocol/saml/JaxrsSAML2BindingBuilder.java
+services/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java
+saml-core-api/src/main/java/org/keycloak/saml/common/constants/JBossSAMLConstants.java
+saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/request/SAML2Request.java
+saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/request/SecurityActions.java
+saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/response/SAML2Response.java
+saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/sig/SAML2Signature.java
+saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/assertion/AbstractStaxSamlAssertionParser.java
+saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/assertion/SAMLAssertionParser.java
+saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/assertion/SAMLAssertionQNames.java
+saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/assertion/SAMLAttributeParser.java
+saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/assertion/SAMLAttributeStatementParser.java
+saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/assertion/SAMLAttributeValueParser.java
+saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLAttributeConsumingServiceParser.java
+saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLEntitiesDescriptorParser.java
+saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLEntityDescriptorParser.java
+saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/SAMLSPSSODescriptorParser.java
+saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/protocol/SAMLArtifactResponseParser.java
+saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/protocol/SAMLResponseParser.java
+saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParser.java
+saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/factories/JBossSAMLAuthnResponseFactory.java
+saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/factories/SAMLAssertionFactory.java
+saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java
+saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/SAMLMetadataUtil.java
+saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java
+saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLAssertionWriter.java
+saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLMetadataWriter.java
+saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLRequestWriter.java
+saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLResponseWriter.java
+saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLSignatureUtil.java
+saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java
+saml-core/src/main/java/org/keycloak/saml/SAMLRequestParser.java
+services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
diff --git a/patch-tool/patch.sh b/patch-tool/patch.sh
new file mode 100755
index 0000000..70d402c
--- /dev/null
+++ b/patch-tool/patch.sh
@@ -0,0 +1,109 @@
+#!/bin/bash
+
+set -e
+
+APPLY_PATCH=false
+
+usage() {
+ echo "Usage: $0 [--apply] OLD_BRANCH NEW_BRANCH"
+ echo " --apply Apply patch to parent directory after generation"
+ exit 1
+}
+
+# Parse options
+while [[ "$1" == --* ]]; do
+ case "$1" in
+ --apply)
+ APPLY_PATCH=true
+ shift
+ ;;
+ *)
+ usage
+ ;;
+ esac
+done
+
+if [ $# -ne 2 ]; then
+ usage
+fi
+
+OLD_BRANCH="$1"
+NEW_BRANCH="$2"
+
+GIT_REPO_URL="git@github.com:keycloak/keycloak.git"
+WORK_DIR="$(pwd)/.work"
+REPO_DIR="$WORK_DIR/repo"
+OLD_DIR="$WORK_DIR/files/old"
+NEW_DIR="$WORK_DIR/files/new"
+PATCH_FILE="$WORK_DIR/patch.patch"
+FILES_LIST="$(pwd)/keycloak-files.txt"
+
+echo "=== Git Diff Generator ==="
+echo "Repository : $GIT_REPO_URL"
+echo "Old branch : $OLD_BRANCH"
+echo "New branch : $NEW_BRANCH"
+echo "Work dir : $WORK_DIR"
+echo "Apply patch: $APPLY_PATCH"
+
+# Ensure keycloak-files.txt exists
+if [ ! -f "$FILES_LIST" ]; then
+ echo "Error: $FILES_LIST not found in $(pwd)"
+ exit 1
+fi
+
+# Read file list into array
+FILES=()
+while IFS= read -r line; do
+ [[ "$line" =~ ^#.*$ || -z "$line" ]] && continue
+ FILES+=("$line")
+done < "$FILES_LIST"
+
+# Prepare work directory
+rm -rf "$WORK_DIR"
+mkdir -p "$WORK_DIR" "$OLD_DIR" "$NEW_DIR"
+
+echo "Cloning repository..."
+git clone "$GIT_REPO_URL" "$REPO_DIR"
+
+# Validate branches exist
+for BRANCH in "$OLD_BRANCH" "$NEW_BRANCH"; do
+ if ! git -C "$REPO_DIR" ls-remote --exit-code --heads origin "$BRANCH" > /dev/null; then
+ echo "Error: Branch '$BRANCH' does not exist in remote repository."
+ exit 1
+ fi
+done
+
+echo "Exporting files..."
+for FILE in "${FILES[@]}"; do
+ mkdir -p "$OLD_DIR/$(dirname "$FILE")"
+ git -C "$REPO_DIR" show "$OLD_BRANCH:$FILE" > "$OLD_DIR/$FILE" 2>/dev/null || echo "Missing in $OLD_BRANCH: $FILE"
+
+ mkdir -p "$NEW_DIR/$(dirname "$FILE")"
+ git -C "$REPO_DIR" show "$NEW_BRANCH:$FILE" > "$NEW_DIR/$FILE" 2>/dev/null || echo "Missing in $NEW_BRANCH: $FILE"
+done
+
+echo "File count (.java only):"
+echo " $OLD_BRANCH: $(find "$OLD_DIR" -type f -name '*.java' | wc -l)"
+echo " $NEW_BRANCH: $(find "$NEW_DIR" -type f -name '*.java' | wc -l)"
+
+echo "Creating patch..."
+diff --color=never -ruN "$OLD_DIR" "$NEW_DIR" > "$PATCH_FILE" || true
+
+# Post-process diff
+safe_sed() {
+ sed "$1" "$2" > "$2.tmp" && mv "$2.tmp" "$2"
+}
+
+safe_sed "s|$OLD_DIR/saml-core/||g" "$PATCH_FILE"
+safe_sed "s|$NEW_DIR/saml-core/||g" "$PATCH_FILE"
+safe_sed "s|$OLD_DIR/services/||g" "$PATCH_FILE"
+safe_sed "s|$NEW_DIR/services/||g" "$PATCH_FILE"
+safe_sed 's|org/keycloak/|nl/first8/keycloak/|g' "$PATCH_FILE"
+
+echo "Patch created at: $PATCH_FILE"
+
+if [ "$APPLY_PATCH" = true ]; then
+ echo "Applying patch to parent directory: $(dirname "$(pwd)")"
+ patch -d "$(dirname "$(pwd)")" < "$PATCH_FILE"
+ echo "Patch applied successfully."
+fi
diff --git a/pom.xml b/pom.xml
index 64e3125..fabe70b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,7 +20,7 @@
UTF-8
UTF-8
- 26.0.0
+ 26.1.5
4.8.3.1
3.4.1
@@ -383,4 +383,4 @@
-
\ No newline at end of file
+
diff --git a/src/main/java/nl/first8/keycloak/protocol/saml/SamlProtocolUtils.java b/src/main/java/nl/first8/keycloak/protocol/saml/SamlProtocolUtils.java
index 11edd99..415f171 100644
--- a/src/main/java/nl/first8/keycloak/protocol/saml/SamlProtocolUtils.java
+++ b/src/main/java/nl/first8/keycloak/protocol/saml/SamlProtocolUtils.java
@@ -3,7 +3,7 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.net.URI;
-
+import java.nio.charset.StandardCharsets;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
@@ -151,7 +151,7 @@ public static void verifyRedirectSignature(SAMLDocumentHolder documentHolder, Ke
String decodedAlgorithm = RedirectBindingUtil.urlDecode(encodedParams.getFirst(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY));
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.getFromXmlMethod(decodedAlgorithm);
if (!RedirectBindingSignatureUtil.validateRedirectBindingSignature(signatureAlgorithm,
- rawQuery.getBytes("UTF-8"), decodedSignature, locator, keyId)) {
+ rawQuery.getBytes(StandardCharsets.UTF_8), decodedSignature, locator, keyId)) {
throw new VerificationException("Invalid query param signature");
}
} catch (Exception e) {
diff --git a/src/main/java/nl/first8/keycloak/services/resources/IdentityBrokerService.java b/src/main/java/nl/first8/keycloak/services/resources/IdentityBrokerService.java
index 6d04761..21998ab 100644
--- a/src/main/java/nl/first8/keycloak/services/resources/IdentityBrokerService.java
+++ b/src/main/java/nl/first8/keycloak/services/resources/IdentityBrokerService.java
@@ -353,6 +353,8 @@ public Response performLogin(@PathParam("provider_alias") String providerAlias,
}
return response;
}
+ } catch (WebApplicationException e) {
+ return e.getResponse();
} catch (IdentityBrokerException e) {
return redirectToErrorPage(Response.Status.BAD_GATEWAY, Messages.COULD_NOT_SEND_AUTHENTICATION_REQUEST, e, providerAlias);
} catch (Exception e) {
@@ -1123,7 +1125,7 @@ private AuthenticationSessionModel parseSessionCode(String code, String clientId
private Response checkAccountManagementFailedLinking(AuthenticationSessionModel authSession, String error, Object... parameters) {
UserSessionModel userSession = new AuthenticationSessionManager(session).getUserSession(authSession);
- if (userSession != null && authSession.getClient() != null && authSession.getClient().getClientId().equals(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID)) {
+ if (userSession != null && authSession.getClient() != null) {
this.event.event(EventType.FEDERATED_IDENTITY_LINK);
UserModel user = userSession.getUser();
@@ -1154,7 +1156,7 @@ private Response checkPassiveLoginError(AuthenticationSessionModel authSession,
.setHttpHeaders(headers)
.setUriInfo(session.getContext().getUri())
.setEventBuilder(event);
- return protocol.sendError(authSession, error);
+ return protocol.sendError(authSession, error, null);
}
return null;
}