Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,4 @@ buildNumber.properties
.classpath

# End of https://www.toptal.com/developers/gitignore/api/maven,intellij+all,eclipse
/patch-tool/.work/
116 changes: 116 additions & 0 deletions patch-tool/README.md
Original file line number Diff line number Diff line change
@@ -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.

51 changes: 51 additions & 0 deletions patch-tool/keycloak-files.txt
Original file line number Diff line number Diff line change
@@ -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
109 changes: 109 additions & 0 deletions patch-tool/patch.sh
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

<keycloak.version>26.0.0</keycloak.version>
<keycloak.version>26.1.5</keycloak.version>
<spotbugs.version>4.8.3.1</spotbugs.version>

<jib-maven-plugin.version>3.4.1</jib-maven-plugin.version>
Expand Down Expand Up @@ -383,4 +383,4 @@
</profile>
</profiles>

</project>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;
}
Expand Down