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
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,10 @@ buildNumber.properties
/patch-tool/.work/

.logs

# E2E test credentials
src/test/e2e/frontend/.env
src/test/e2e/frontend/node_modules/

# Claude Code
.claude/
14 changes: 11 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<groupId>nl.first8.keycloak.broker</groupId>
<artifactId>idp-saml2-extended</artifactId>
<name>KeyCloak: SAML v2.0 - Extended</name>
<name>Keycloak: SAML v2.0 - Extended</name>
<description>
SAML v2.0 extensions that adds more configuration options to connect to SAML Service Providers.
</description>
Expand All @@ -20,7 +20,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

<keycloak.version>26.5.1</keycloak.version>
<keycloak.version>26.5.7</keycloak.version>
<spotbugs.version>4.8.3.1</spotbugs.version>

<jib-maven-plugin.version>3.4.1</jib-maven-plugin.version>
Expand All @@ -29,6 +29,7 @@
</properties>

<build>
<finalName>${project.artifactId}-${project.version}-${git.commit.id.abbrev}</finalName>
<sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
<directory>${project.basedir}/target</directory>
<resources>
Expand All @@ -46,6 +47,7 @@
<goals>
<goal>revision</goal>
</goals>
<phase>validate</phase>
</execution>
</executions>

Expand Down Expand Up @@ -156,6 +158,9 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<argLine>-Djava.util.logging.manager=org.jboss.logmanager.LogManager</argLine>
</configuration>
</plugin>

<plugin>
Expand All @@ -168,6 +173,9 @@
<goals>
<goal>spotbugs</goal>
</goals>
<configuration>
<skip>${skipSpotBugs}</skip>
</configuration>
</execution>
</executions>
<configuration>
Expand Down Expand Up @@ -383,4 +391,4 @@
</profile>
</profiles>

</project>
</project>
22 changes: 13 additions & 9 deletions src/main/java/nl/first8/keycloak/broker/saml/SAMLEndpoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.namespace.QName;

Expand All @@ -36,6 +37,7 @@
import nl.first8.keycloak.dom.saml.v2.assertion.AssertionType;
import nl.first8.keycloak.dom.saml.v2.assertion.AttributeStatementType;
import nl.first8.keycloak.dom.saml.v2.protocol.ResponseType;
import nl.first8.keycloak.protocol.saml.SamlProtocolUtils;
import nl.first8.keycloak.saml.SAMLRequestParser;
import nl.first8.keycloak.saml.common.constants.GeneralConstants;
import nl.first8.keycloak.saml.common.constants.JBossSAMLConstants;
Expand Down Expand Up @@ -70,7 +72,6 @@
import org.keycloak.protocol.saml.SamlMetadataPublicKeyLoader;
import org.keycloak.protocol.saml.SamlPrincipalType;
import org.keycloak.protocol.saml.SamlProtocol;
import org.keycloak.protocol.saml.SamlProtocolUtils;
import org.keycloak.protocol.saml.SamlService;
import org.keycloak.protocol.saml.SamlSessionUtils;
import org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessor;
Expand Down Expand Up @@ -338,10 +339,10 @@ protected Response logoutRequest(LogoutRequestType request, String relayState) {
if (request.getSessionIndex() == null || request.getSessionIndex().isEmpty()) {
AtomicReference<LogoutRequestType> ref = new AtomicReference<>(request);
session.sessions().getUserSessionByBrokerUserIdStream(realm, brokerUserId)
.filter(userSession -> userSession.getState() != UserSessionModel.State.LOGGING_OUT &&
userSession.getState() != UserSessionModel.State.LOGGED_OUT)
.toList() // collect to avoid concurrent modification as backchannelLogout removes the user sessions.
.forEach(processLogout(ref));
.filter(userSession -> userSession.getState() != UserSessionModel.State.LOGGING_OUT &&
userSession.getState() != UserSessionModel.State.LOGGED_OUT)
.collect(Collectors.toList()) // collect to avoid concurrent modification as backchannelLogout removes the user sessions.
.forEach(processLogout(ref));
request = ref.get();

} else {
Expand Down Expand Up @@ -419,7 +420,6 @@ private String getEntityId(UriInfo uriInfo, RealmModel realm) {
}

protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState, String clientId) {

try {
AuthenticationSessionModel authSession;
if (StringUtil.isNotBlank(clientId)) {
Expand Down Expand Up @@ -475,6 +475,7 @@ protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder h
assertionElement = DocumentUtil.getElement(holder.getSamlDocument(), new QName(Objects.requireNonNull(JBossSAMLConstants.ASSERTION.get())));
}

logger.trace("Validating the response Issuer");
// Validate the response Issuer
final String responseIssuer = responseType.getIssuer() != null ? responseType.getIssuer().getValue() : null;
final boolean responseIssuerValidationSuccess = config.getIdpEntityId() == null ||
Expand Down Expand Up @@ -520,6 +521,7 @@ protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder h

AssertionType assertion = responseType.getAssertions().get(0).getAssertion();

logger.trace("Validating the assertion issuer");
// Validate the assertion Issuer
final String assertionIssuer = assertion.getIssuer() != null ? assertion.getIssuer().getValue() : null;
final boolean assertionIssuerValidationSuccess = config.getIdpEntityId() == null ||
Expand All @@ -541,7 +543,7 @@ protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder h
return ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, Messages.INVALID_REQUESTER);
}

BrokeredIdentityContext identity = new BrokeredIdentityContext(principal,config);
BrokeredIdentityContext identity = new BrokeredIdentityContext(principal, config);
identity.getContextData().put(SAML_LOGIN_RESPONSE, responseType);
identity.getContextData().put(SAML_ASSERTION, assertion);
identity.setAuthenticationSession(authSession);
Expand Down Expand Up @@ -585,6 +587,7 @@ protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder h
break;
}
}

if (assertion.getAttributeStatements() != null) {
String email = getX500Attribute(assertion, X500SAMLProfileConstants.EMAIL);
if (email != null) {
Expand All @@ -602,6 +605,7 @@ protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder h
identity.setBrokerSessionId(brokerSessionId);
}

logger.trace("handleLoginResponse finished succesfully.");
return callback.authenticated(identity);
} catch (WebApplicationException e) {
return e.getResponse();
Expand Down Expand Up @@ -691,6 +695,7 @@ public Response handleSamlResponse(String samlResponse, String relayState, Strin
event.error(Errors.INVALID_SAML_RESPONSE);
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_REQUEST);
}
logger.trace("Right before handleSamlResponse's if (config.isValidateSignature())");
if (config.isValidateSignature()) {
try {
if (isArtifactResponse) {
Expand All @@ -715,8 +720,6 @@ public Response handleSamlResponse(String samlResponse, String relayState, Strin
logger.debug("SAML Response was NOT of type ResponseType so calling logout.");
return handleLogoutResponse(holder, statusResponse, relayState);
}


}

private ResponseType convertToResponseType(StatusResponseType statusResponse) {
Expand Down Expand Up @@ -814,6 +817,7 @@ protected void verifySignature(String key, SAMLDocumentHolder documentHolder) th
protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
return SAMLRequestParser.parseRequestPostBinding(samlRequest);
}

@Override
protected SAMLDocumentHolder extractResponseDocument(String response) {
byte[] samlBytes = response.getBytes();
Expand Down
Loading
Loading