KEYCLOAK-18146 Search for clients by client attribute when doing saml artifact resolution
This commit is contained in:
parent
2cb59e2503
commit
4dcb69596b
26 changed files with 364 additions and 52 deletions
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.connections.jpa.updater.liquibase.custom;
|
||||||
|
|
||||||
|
import liquibase.exception.CustomChangeException;
|
||||||
|
import liquibase.statement.core.InsertStatement;
|
||||||
|
import liquibase.structure.core.Table;
|
||||||
|
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.keycloak.protocol.saml.util.ArtifactBindingUtils.computeArtifactBindingIdentifierString;
|
||||||
|
|
||||||
|
public class JpaUpdate14_0_0_MigrateSamlArtifactAttribute extends CustomKeycloakTask {
|
||||||
|
|
||||||
|
private static final String SAML_ARTIFACT_BINDING_IDENTIFIER = "saml.artifact.binding.identifier";
|
||||||
|
|
||||||
|
private final Map<String, String> clientIds = new HashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void generateStatementsImpl() throws CustomChangeException {
|
||||||
|
extractClientsData("SELECT C.ID, C.CLIENT_ID FROM " + getTableName("CLIENT") + " C " +
|
||||||
|
"LEFT JOIN " + getTableName("CLIENT_ATTRIBUTES") + " CA " +
|
||||||
|
"ON C.ID = CA.CLIENT_ID AND CA.NAME='" + SAML_ARTIFACT_BINDING_IDENTIFIER + "' " +
|
||||||
|
"WHERE C.PROTOCOL='saml' AND CA.NAME IS NULL");
|
||||||
|
|
||||||
|
for (Map.Entry<String, String> clientPair : clientIds.entrySet()) {
|
||||||
|
String id = clientPair.getKey();
|
||||||
|
|
||||||
|
String clientId = clientPair.getValue();
|
||||||
|
String samlIdentifier = computeArtifactBindingIdentifierString(clientId);
|
||||||
|
|
||||||
|
statements.add(
|
||||||
|
new InsertStatement(null, null, database.correctObjectName("CLIENT_ATTRIBUTES", Table.class))
|
||||||
|
.addColumnValue("CLIENT_ID", id)
|
||||||
|
.addColumnValue("NAME", SAML_ARTIFACT_BINDING_IDENTIFIER)
|
||||||
|
.addColumnValue("VALUE", samlIdentifier)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void extractClientsData(String sql) throws CustomChangeException {
|
||||||
|
try (PreparedStatement statement = jdbcConnection.prepareStatement(sql);
|
||||||
|
ResultSet rs = statement.executeQuery()) {
|
||||||
|
|
||||||
|
while (rs.next()) {
|
||||||
|
String id = rs.getString(1);
|
||||||
|
String clientId = rs.getString(2);
|
||||||
|
|
||||||
|
if (id == null || id.trim().isEmpty()
|
||||||
|
|| clientId == null || clientId.trim().isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
clientIds.put(id, clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new CustomChangeException(getTaskId() + ": Exception when extracting data from previous version", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getTaskId() {
|
||||||
|
return "Migrate Saml attributes (14.0.0)";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ import org.keycloak.models.ClientProvider;
|
||||||
import org.keycloak.models.ClientProviderFactory;
|
import org.keycloak.models.ClientProviderFactory;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.protocol.saml.SamlConfigAttributes;
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -38,7 +39,8 @@ public class JpaClientProviderFactory implements ClientProviderFactory {
|
||||||
private Set<String> clientSearchableAttributes = null;
|
private Set<String> clientSearchableAttributes = null;
|
||||||
|
|
||||||
private static final List<String> REQUIRED_SEARCHABLE_ATTRIBUTES = Arrays.asList(
|
private static final List<String> REQUIRED_SEARCHABLE_ATTRIBUTES = Arrays.asList(
|
||||||
"saml_idp_initiated_sso_url_name"
|
"saml_idp_initiated_sso_url_name",
|
||||||
|
SamlConfigAttributes.SAML_ARTIFACT_BINDING_IDENTIFIER
|
||||||
);
|
);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -71,4 +71,8 @@
|
||||||
</createIndex>
|
</createIndex>
|
||||||
</changeSet>
|
</changeSet>
|
||||||
|
|
||||||
|
<changeSet author="keycloak" id="KEYCLOAK-18146-add-saml-art-binding-identifier">
|
||||||
|
<customChange class="org.keycloak.connections.jpa.updater.liquibase.custom.JpaUpdate14_0_0_MigrateSamlArtifactAttribute"/>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|
|
@ -141,6 +141,8 @@ import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
import org.keycloak.validation.ValidationUtil;
|
import org.keycloak.validation.ValidationUtil;
|
||||||
|
|
||||||
|
import static org.keycloak.protocol.saml.util.ArtifactBindingUtils.computeArtifactBindingIdentifierString;
|
||||||
|
|
||||||
public class RepresentationToModel {
|
public class RepresentationToModel {
|
||||||
|
|
||||||
private static Logger logger = Logger.getLogger(RepresentationToModel.class);
|
private static Logger logger = Logger.getLogger(RepresentationToModel.class);
|
||||||
|
@ -1407,6 +1409,11 @@ public class RepresentationToModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ("saml".equals(resourceRep.getProtocol())
|
||||||
|
&& (resourceRep.getAttributes() == null
|
||||||
|
|| !resourceRep.getAttributes().containsKey("saml.artifact.binding.identifier"))) {
|
||||||
|
client.setAttribute("saml.artifact.binding.identifier", computeArtifactBindingIdentifierString(resourceRep.getClientId()));
|
||||||
|
}
|
||||||
|
|
||||||
if (resourceRep.getAuthenticationFlowBindingOverrides() != null) {
|
if (resourceRep.getAuthenticationFlowBindingOverrides() != null) {
|
||||||
for (Map.Entry<String, String> entry : resourceRep.getAuthenticationFlowBindingOverrides().entrySet()) {
|
for (Map.Entry<String, String> entry : resourceRep.getAuthenticationFlowBindingOverrides().entrySet()) {
|
||||||
|
@ -1559,6 +1566,12 @@ public class RepresentationToModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ("saml".equals(rep.getProtocol())
|
||||||
|
&& (rep.getAttributes() == null
|
||||||
|
|| !rep.getAttributes().containsKey("saml.artifact.binding.identifier"))) {
|
||||||
|
resource.setAttribute("saml.artifact.binding.identifier", computeArtifactBindingIdentifierString(rep.getClientId()));
|
||||||
|
}
|
||||||
|
|
||||||
if (rep.getAuthenticationFlowBindingOverrides() != null) {
|
if (rep.getAuthenticationFlowBindingOverrides() != null) {
|
||||||
for (Map.Entry<String, String> entry : rep.getAuthenticationFlowBindingOverrides().entrySet()) {
|
for (Map.Entry<String, String> entry : rep.getAuthenticationFlowBindingOverrides().entrySet()) {
|
||||||
if (entry.getValue() == null || entry.getValue().trim().equals("")) {
|
if (entry.getValue() == null || entry.getValue().trim().equals("")) {
|
||||||
|
|
|
@ -2,10 +2,9 @@ package org.keycloak.protocol.saml;
|
||||||
|
|
||||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a way to create and resolve artifacts for SAML Artifact binding
|
* Provides a way to create and resolve artifacts for SAML Artifact binding
|
||||||
|
@ -15,12 +14,12 @@ public interface ArtifactResolver extends Provider {
|
||||||
/**
|
/**
|
||||||
* Returns client model that issued artifact
|
* Returns client model that issued artifact
|
||||||
*
|
*
|
||||||
|
* @param session KeycloakSession for searching for client corresponding client
|
||||||
* @param artifact the artifact
|
* @param artifact the artifact
|
||||||
* @param clients stream of clients, the stream will be searched for a client that issued the artifact
|
|
||||||
* @return the client model that issued the artifact
|
* @return the client model that issued the artifact
|
||||||
* @throws ArtifactResolverProcessingException When an error occurs during client search
|
* @throws ArtifactResolverProcessingException When an error occurs during client search
|
||||||
*/
|
*/
|
||||||
ClientModel selectSourceClient(String artifact, Stream<ClientModel> clients) throws ArtifactResolverProcessingException;
|
ClientModel selectSourceClient(KeycloakSession session, String artifact) throws ArtifactResolverProcessingException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates and stores an artifact
|
* Creates and stores an artifact
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
package org.keycloak.protocol.saml.util;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
public class ArtifactBindingUtils {
|
||||||
|
public static String artifactToResolverProviderId(String artifact) {
|
||||||
|
return byteArrayToResolverProviderId(Base64.getDecoder().decode(artifact));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String byteArrayToResolverProviderId(byte[] ar) {
|
||||||
|
return String.format("%02X%02X", ar[0], ar[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes identifier from the given String, for example, from entityId
|
||||||
|
*
|
||||||
|
* @param identifierFrom String that will be turned into an identifier
|
||||||
|
* @return Base64 of SHA-1 hash of the identifierFrom
|
||||||
|
*/
|
||||||
|
public static String computeArtifactBindingIdentifierString(String identifierFrom) {
|
||||||
|
return Base64.getEncoder().encodeToString(computeArtifactBindingIdentifier(identifierFrom));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns byte representation of the identifier into readable String
|
||||||
|
*
|
||||||
|
* @param identifier byte representation of the identifier
|
||||||
|
* @return Base64 of the identifier
|
||||||
|
*/
|
||||||
|
public static String getArtifactBindingIdentifierString(byte[] identifier) {
|
||||||
|
return Base64.getEncoder().encodeToString(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes 20 bytes long byte identifier of the given string, for example, from entityId
|
||||||
|
*
|
||||||
|
* @param identifierFrom String that will be turned into an identifier
|
||||||
|
* @return SHA-1 hash of the given identifierFrom
|
||||||
|
*/
|
||||||
|
public static byte[] computeArtifactBindingIdentifier(String identifierFrom) {
|
||||||
|
try {
|
||||||
|
MessageDigest sha1Digester = MessageDigest.getInstance("SHA-1");
|
||||||
|
return sha1Digester.digest(identifierFrom.getBytes(StandardCharsets.UTF_8));
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new RuntimeException("JVM does not support required cryptography algorithms: SHA-1/SHA1PRNG.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,22 +1,22 @@
|
||||||
package org.keycloak.protocol.saml;
|
package org.keycloak.protocol.saml;
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.protocol.saml.util.ArtifactBindingUtils;
|
||||||
import org.keycloak.saml.common.constants.GeneralConstants;
|
import org.keycloak.saml.common.constants.GeneralConstants;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.stream.Stream;
|
import java.util.Collections;
|
||||||
|
|
||||||
import static org.keycloak.protocol.saml.DefaultSamlArtifactResolverFactory.TYPE_CODE;
|
import static org.keycloak.protocol.saml.DefaultSamlArtifactResolverFactory.TYPE_CODE;
|
||||||
|
import static org.keycloak.protocol.saml.SamlConfigAttributes.SAML_ARTIFACT_BINDING_IDENTIFIER;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ArtifactResolver for artifact-04 format.
|
* ArtifactResolver for artifact-04 format.
|
||||||
|
@ -43,18 +43,13 @@ public class DefaultSamlArtifactResolver implements ArtifactResolver {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ClientModel selectSourceClient(String artifact, Stream<ClientModel> clients) throws ArtifactResolverProcessingException {
|
public ClientModel selectSourceClient(KeycloakSession session, String artifact) throws ArtifactResolverProcessingException {
|
||||||
try {
|
byte[] source = extractSourceFromArtifact(artifact);
|
||||||
byte[] source = extractSourceFromArtifact(artifact);
|
String identifier = ArtifactBindingUtils.getArtifactBindingIdentifierString(source);
|
||||||
|
|
||||||
MessageDigest sha1Digester = MessageDigest.getInstance("SHA-1");
|
return session.clients().searchClientsByAttributes(session.getContext().getRealm(),
|
||||||
return clients.filter(clientModel -> Arrays.equals(source,
|
Collections.singletonMap(SAML_ARTIFACT_BINDING_IDENTIFIER, identifier), 0, 1)
|
||||||
sha1Digester.digest(clientModel.getClientId().getBytes(Charsets.UTF_8))))
|
.findFirst().orElseThrow(() -> new ArtifactResolverProcessingException("No client matching the artifact source found"));
|
||||||
.findFirst()
|
|
||||||
.orElseThrow(() -> new ArtifactResolverProcessingException("No client matching the artifact source found"));
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new ArtifactResolverProcessingException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -109,8 +104,7 @@ public class DefaultSamlArtifactResolver implements ArtifactResolver {
|
||||||
SecureRandom handleGenerator = SecureRandom.getInstance("SHA1PRNG");
|
SecureRandom handleGenerator = SecureRandom.getInstance("SHA1PRNG");
|
||||||
byte[] trimmedIndex = new byte[2];
|
byte[] trimmedIndex = new byte[2];
|
||||||
|
|
||||||
MessageDigest sha1Digester = MessageDigest.getInstance("SHA-1");
|
byte[] source = ArtifactBindingUtils.computeArtifactBindingIdentifier(entityId);
|
||||||
byte[] source = sha1Digester.digest(entityId.getBytes(Charsets.UTF_8));
|
|
||||||
|
|
||||||
byte[] assertionHandle = new byte[20];
|
byte[] assertionHandle = new byte[20];
|
||||||
handleGenerator.nextBytes(assertionHandle);
|
handleGenerator.nextBytes(assertionHandle);
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.protocol.saml;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.models.ClientConfigResolver;
|
import org.keycloak.models.ClientConfigResolver;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.protocol.saml.util.ArtifactBindingUtils;
|
||||||
import org.keycloak.saml.SignatureAlgorithm;
|
import org.keycloak.saml.SignatureAlgorithm;
|
||||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||||
import org.keycloak.saml.common.util.XmlKeyInfoKeyNameTransformer;
|
import org.keycloak.saml.common.util.XmlKeyInfoKeyNameTransformer;
|
||||||
|
@ -258,4 +259,12 @@ public class SamlClient extends ClientConfigResolver {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setArtifactBindingIdentifierFrom(String identifierFrom) {
|
||||||
|
client.setAttribute(SamlConfigAttributes.SAML_ARTIFACT_BINDING_IDENTIFIER, ArtifactBindingUtils.computeArtifactBindingIdentifierString(identifierFrom));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getArtifactBindingIdentifier() {
|
||||||
|
return client.getAttribute(SamlConfigAttributes.SAML_ARTIFACT_BINDING_IDENTIFIER);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,4 +43,5 @@ public interface SamlConfigAttributes {
|
||||||
String SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE = "saml.encryption." + CertificateInfoHelper.X509CERTIFICATE;
|
String SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE = "saml.encryption." + CertificateInfoHelper.X509CERTIFICATE;
|
||||||
String SAML_ENCRYPTION_PRIVATE_KEY_ATTRIBUTE = "saml.encryption." + CertificateInfoHelper.PRIVATE_KEY;
|
String SAML_ENCRYPTION_PRIVATE_KEY_ATTRIBUTE = "saml.encryption." + CertificateInfoHelper.PRIVATE_KEY;
|
||||||
String SAML_ASSERTION_LIFESPAN = "saml.assertion.lifespan";
|
String SAML_ASSERTION_LIFESPAN = "saml.assertion.lifespan";
|
||||||
|
String SAML_ARTIFACT_BINDING_IDENTIFIER = "saml.artifact.binding.identifier";
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,6 +175,8 @@ public class SamlProtocolFactory extends AbstractLoginProtocolFactory {
|
||||||
if (clientRep.isFrontchannelLogout() == null) {
|
if (clientRep.isFrontchannelLogout() == null) {
|
||||||
newClient.setFrontchannelLogout(true);
|
newClient.setFrontchannelLogout(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client.setArtifactBindingIdentifierFrom(clientRep.getClientId());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
||||||
import org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessor;
|
import org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessor;
|
||||||
import org.keycloak.protocol.saml.profile.ecp.SamlEcpProfileService;
|
import org.keycloak.protocol.saml.profile.ecp.SamlEcpProfileService;
|
||||||
import org.keycloak.protocol.saml.profile.util.Soap;
|
import org.keycloak.protocol.saml.profile.util.Soap;
|
||||||
import org.keycloak.protocol.util.ArtifactBindingUtils;
|
import org.keycloak.protocol.saml.util.ArtifactBindingUtils;
|
||||||
import org.keycloak.rotation.HardcodedKeyLocator;
|
import org.keycloak.rotation.HardcodedKeyLocator;
|
||||||
import org.keycloak.rotation.KeyLocator;
|
import org.keycloak.rotation.KeyLocator;
|
||||||
import org.keycloak.saml.BaseSAML2BindingBuilder;
|
import org.keycloak.saml.BaseSAML2BindingBuilder;
|
||||||
|
@ -341,7 +341,7 @@ public class SamlService extends AuthorizationEndpointBase {
|
||||||
//Find client
|
//Find client
|
||||||
ClientModel client;
|
ClientModel client;
|
||||||
try {
|
try {
|
||||||
client = getArtifactResolver(artifact).selectSourceClient(artifact, realm.getClientsStream());
|
client = getArtifactResolver(artifact).selectSourceClient(session, artifact);
|
||||||
|
|
||||||
Response error = checkClientValidity(client);
|
Response error = checkClientValidity(client);
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
package org.keycloak.protocol.util;
|
|
||||||
|
|
||||||
import org.keycloak.protocol.saml.DefaultSamlArtifactResolverFactory;
|
|
||||||
|
|
||||||
import java.util.Base64;
|
|
||||||
|
|
||||||
public class ArtifactBindingUtils {
|
|
||||||
public static String artifactToResolverProviderId(String artifact) {
|
|
||||||
return byteArrayToResolverProviderId(Base64.getDecoder().decode(artifact));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String byteArrayToResolverProviderId(byte[] ar) {
|
|
||||||
return String.format("%02X%02X", ar[0], ar[1]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -35,6 +35,9 @@ import org.keycloak.protocol.LoginProtocol;
|
||||||
import org.keycloak.protocol.LoginProtocolFactory;
|
import org.keycloak.protocol.LoginProtocolFactory;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
|
import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
|
||||||
|
import org.keycloak.protocol.saml.SamlClient;
|
||||||
|
import org.keycloak.protocol.saml.SamlConfigAttributes;
|
||||||
|
import org.keycloak.protocol.saml.SamlProtocol;
|
||||||
import org.keycloak.representations.adapters.config.BaseRealmConfig;
|
import org.keycloak.representations.adapters.config.BaseRealmConfig;
|
||||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
@ -191,7 +194,8 @@ public class ClientManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clientIdChanged(ClientModel client, String newClientId) {
|
public void clientIdChanged(ClientModel client, ClientRepresentation newClientRepresentation) {
|
||||||
|
String newClientId = newClientRepresentation.getClientId();
|
||||||
logger.debugf("Updating clientId from '%s' to '%s'", client.getClientId(), newClientId);
|
logger.debugf("Updating clientId from '%s' to '%s'", client.getClientId(), newClientId);
|
||||||
|
|
||||||
UserModel serviceAccountUser = realmManager.getSession().users().getServiceAccount(client);
|
UserModel serviceAccountUser = realmManager.getSession().users().getServiceAccount(client);
|
||||||
|
@ -199,6 +203,13 @@ public class ClientManager {
|
||||||
String username = ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + newClientId;
|
String username = ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + newClientId;
|
||||||
serviceAccountUser.setUsername(username);
|
serviceAccountUser.setUsername(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SamlProtocol.LOGIN_PROTOCOL.equals(client.getProtocol())) {
|
||||||
|
SamlClient samlClient = new SamlClient(client);
|
||||||
|
samlClient.setArtifactBindingIdentifierFrom(newClientId);
|
||||||
|
|
||||||
|
newClientRepresentation.getAttributes().put(SamlConfigAttributes.SAML_ARTIFACT_BINDING_IDENTIFIER, samlClient.getArtifactBindingIdentifier());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonPropertyOrder({"realm", "realm-public-key", "bearer-only", "auth-server-url", "ssl-required",
|
@JsonPropertyOrder({"realm", "realm-public-key", "bearer-only", "auth-server-url", "ssl-required",
|
||||||
|
|
|
@ -667,7 +667,7 @@ public class ClientResource {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rep.getClientId() != null && !rep.getClientId().equals(client.getClientId())) {
|
if (rep.getClientId() != null && !rep.getClientId().equals(client.getClientId())) {
|
||||||
new ClientManager(new RealmManager(session)).clientIdChanged(client, rep.getClientId());
|
new ClientManager(new RealmManager(session)).clientIdChanged(client, rep);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rep.isFullScopeAllowed() != null && rep.isFullScopeAllowed() != client.isFullScopeAllowed()) {
|
if (rep.isFullScopeAllowed() != null && rep.isFullScopeAllowed() != client.isFullScopeAllowed()) {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package org.keycloak.testsuite.authentication;
|
||||||
|
|
||||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.protocol.saml.ArtifactResolver;
|
import org.keycloak.protocol.saml.ArtifactResolver;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
@ -10,7 +11,6 @@ import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import static org.keycloak.testsuite.authentication.CustomTestingSamlArtifactResolverFactory.TYPE_CODE;
|
import static org.keycloak.testsuite.authentication.CustomTestingSamlArtifactResolverFactory.TYPE_CODE;
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ public class CustomTestingSamlArtifactResolver implements ArtifactResolver {
|
||||||
public static List<String> list = new ArrayList<>();
|
public static List<String> list = new ArrayList<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ClientModel selectSourceClient(String artifact, Stream<ClientModel> clients) {
|
public ClientModel selectSourceClient(KeycloakSession session, String artifact) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.protocol.saml.ArtifactResolver;
|
import org.keycloak.protocol.saml.ArtifactResolver;
|
||||||
import org.keycloak.protocol.saml.ArtifactResolverFactory;
|
import org.keycloak.protocol.saml.ArtifactResolverFactory;
|
||||||
import org.keycloak.protocol.util.ArtifactBindingUtils;
|
import org.keycloak.protocol.saml.util.ArtifactBindingUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This ArtifactResolver should be used only for testing purposes.
|
* This ArtifactResolver should be used only for testing purposes.
|
||||||
|
|
|
@ -25,7 +25,9 @@ import org.keycloak.client.registration.Auth;
|
||||||
import org.keycloak.client.registration.ClientRegistrationException;
|
import org.keycloak.client.registration.ClientRegistrationException;
|
||||||
import org.keycloak.client.registration.HttpErrorException;
|
import org.keycloak.client.registration.HttpErrorException;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
|
import org.keycloak.protocol.saml.SamlConfigAttributes;
|
||||||
import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
|
import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
|
||||||
|
import org.keycloak.protocol.saml.util.ArtifactBindingUtils;
|
||||||
import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
|
import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
|
||||||
import org.keycloak.representations.idm.ClientInitialAccessPresentation;
|
import org.keycloak.representations.idm.ClientInitialAccessPresentation;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
@ -85,6 +87,7 @@ public class SAMLClientRegistrationTest extends AbstractClientRegistrationTest {
|
||||||
));
|
));
|
||||||
|
|
||||||
assertThat(response.getAttributes().get("saml_single_logout_service_url_redirect"), is("https://LoadBalancer-9.siroe.com:3443/federation/SPSloRedirect/metaAlias/sp"));
|
assertThat(response.getAttributes().get("saml_single_logout_service_url_redirect"), is("https://LoadBalancer-9.siroe.com:3443/federation/SPSloRedirect/metaAlias/sp"));
|
||||||
|
assertThat(response.getAttributes().get(SamlConfigAttributes.SAML_ARTIFACT_BINDING_IDENTIFIER), is(ArtifactBindingUtils.computeArtifactBindingIdentifierString("loadbalancer-9.siroe.com")));
|
||||||
|
|
||||||
Assert.assertNotNull(response.getProtocolMappers());
|
Assert.assertNotNull(response.getProtocolMappers());
|
||||||
Assert.assertEquals(1,response.getProtocolMappers().size());
|
Assert.assertEquals(1,response.getProtocolMappers().size());
|
||||||
|
|
|
@ -45,6 +45,8 @@ import org.keycloak.models.utils.DefaultAuthenticationFlows;
|
||||||
import org.keycloak.models.utils.TimeBasedOTP;
|
import org.keycloak.models.utils.TimeBasedOTP;
|
||||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
|
||||||
|
import org.keycloak.protocol.saml.SamlConfigAttributes;
|
||||||
|
import org.keycloak.protocol.saml.util.ArtifactBindingUtils;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.representations.RefreshToken;
|
import org.keycloak.representations.RefreshToken;
|
||||||
import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
|
import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
|
||||||
|
@ -78,6 +80,7 @@ import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
@ -86,6 +89,7 @@ import java.util.stream.Collectors;
|
||||||
import static org.hamcrest.Matchers.allOf;
|
import static org.hamcrest.Matchers.allOf;
|
||||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.hamcrest.Matchers.hasEntry;
|
||||||
import static org.hamcrest.Matchers.hasItem;
|
import static org.hamcrest.Matchers.hasItem;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
@ -125,7 +129,7 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
protected void testMigratedMigrationData(boolean supportsAuthzService) {
|
protected void testMigratedMigrationData(boolean supportsAuthzService) {
|
||||||
assertNames(migrationRealm.roles().list(), "offline_access", "uma_authorization", "default-roles-migration", "migration-test-realm-role");
|
assertNames(migrationRealm.roles().list(), "offline_access", "uma_authorization", "default-roles-migration", "migration-test-realm-role");
|
||||||
List<String> expectedClientIds = new ArrayList<>(Arrays.asList("account", "account-console", "admin-cli", "broker", "migration-test-client", "realm-management", "security-admin-console"));
|
List<String> expectedClientIds = new ArrayList<>(Arrays.asList("account", "account-console", "admin-cli", "broker", "migration-test-client", "migration-saml-client", "realm-management", "security-admin-console"));
|
||||||
|
|
||||||
if (supportsAuthzService) {
|
if (supportsAuthzService) {
|
||||||
expectedClientIds.add("authz-servlet");
|
expectedClientIds.add("authz-servlet");
|
||||||
|
@ -304,6 +308,10 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void testMigrationTo14_0_0() {
|
||||||
|
testSamlAttributes(migrationRealm);
|
||||||
|
}
|
||||||
|
|
||||||
protected void testDeleteAccount(RealmResource realm) {
|
protected void testDeleteAccount(RealmResource realm) {
|
||||||
ClientRepresentation accountClient = realm.clients().findByClientId(ACCOUNT_MANAGEMENT_CLIENT_ID).get(0);
|
ClientRepresentation accountClient = realm.clients().findByClientId(ACCOUNT_MANAGEMENT_CLIENT_ID).get(0);
|
||||||
ClientResource accountResource = realm.clients().get(accountClient.getId());
|
ClientResource accountResource = realm.clients().get(accountClient.getId());
|
||||||
|
@ -924,6 +932,7 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
|
||||||
protected void testMigrationTo12_x(boolean testRealmAttributesMigration) {
|
protected void testMigrationTo12_x(boolean testRealmAttributesMigration) {
|
||||||
testMigrationTo12_0_0();
|
testMigrationTo12_0_0();
|
||||||
testMigrationTo13_0_0(testRealmAttributesMigration);
|
testMigrationTo13_0_0(testRealmAttributesMigration);
|
||||||
|
testMigrationTo14_0_0();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void testMigrationTo7_x(boolean supportedAuthzServices) {
|
protected void testMigrationTo7_x(boolean supportedAuthzServices) {
|
||||||
|
@ -965,6 +974,17 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
|
||||||
assertThat(migrationRealm2.toRepresentation().getDefaultRole().getName(), equalTo("default-roles-migration2-1"));
|
assertThat(migrationRealm2.toRepresentation().getDefaultRole().getName(), equalTo("default-roles-migration2-1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void testSamlAttributes(RealmResource realm) {
|
||||||
|
log.info("Testing SAML ARTIFACT BINDING IDENTIFIER");
|
||||||
|
|
||||||
|
realm.clients().findAll().stream()
|
||||||
|
.filter(clientRepresentation -> Objects.equals("saml", clientRepresentation.getProtocol()))
|
||||||
|
.forEach(clientRepresentation -> {
|
||||||
|
String clientId = clientRepresentation.getClientId();
|
||||||
|
assertThat(clientRepresentation.getAttributes(), hasEntry(SamlConfigAttributes.SAML_ARTIFACT_BINDING_IDENTIFIER, ArtifactBindingUtils.computeArtifactBindingIdentifierString(clientId)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected void testRealmAttributesMigration() {
|
protected void testRealmAttributesMigration() {
|
||||||
log.info("testing realm attributes migration");
|
log.info("testing realm attributes migration");
|
||||||
Map<String, String> realmAttributes = migrationRealm.toRepresentation().getAttributes();
|
Map<String, String> realmAttributes = migrationRealm.toRepresentation().getAttributes();
|
||||||
|
|
|
@ -19,6 +19,7 @@ import org.keycloak.protocol.saml.SamlConfigAttributes;
|
||||||
import org.keycloak.protocol.saml.SamlProtocol;
|
import org.keycloak.protocol.saml.SamlProtocol;
|
||||||
import org.keycloak.protocol.saml.SamlProtocolUtils;
|
import org.keycloak.protocol.saml.SamlProtocolUtils;
|
||||||
import org.keycloak.protocol.saml.profile.util.Soap;
|
import org.keycloak.protocol.saml.profile.util.Soap;
|
||||||
|
import org.keycloak.protocol.saml.util.ArtifactBindingUtils;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.saml.SAML2LogoutResponseBuilder;
|
import org.keycloak.saml.SAML2LogoutResponseBuilder;
|
||||||
import org.keycloak.saml.common.constants.GeneralConstants;
|
import org.keycloak.saml.common.constants.GeneralConstants;
|
||||||
|
@ -984,4 +985,39 @@ public class ArtifactBindingTest extends AbstractSamlTest {
|
||||||
assertThat(spDescriptor.getSingleLogoutService().get(0).getLocation(), is(equalTo(new URI("http://url.artifact.test"))));
|
assertThat(spDescriptor.getSingleLogoutService().get(0).getLocation(), is(equalTo(new URI("http://url.artifact.test"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testArtifactBindingIdentifierChangedWhenClientIdChanged() throws IOException {
|
||||||
|
ClientRepresentation clientRepresentation = adminClient.realm(REALM_NAME)
|
||||||
|
.clients()
|
||||||
|
.findByClientId(SAML_CLIENT_ID_SALES_POST)
|
||||||
|
.get(0);
|
||||||
|
|
||||||
|
String oldIdentifier = clientRepresentation.getAttributes().get(SamlConfigAttributes.SAML_ARTIFACT_BINDING_IDENTIFIER);
|
||||||
|
assertThat(oldIdentifier, notNullValue());
|
||||||
|
|
||||||
|
final String newClientId = "new_client_id";
|
||||||
|
|
||||||
|
try (ClientAttributeUpdater cau = ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_SALES_POST)
|
||||||
|
.setClientId(newClientId)
|
||||||
|
.update()
|
||||||
|
) {
|
||||||
|
clientRepresentation = adminClient.realm(REALM_NAME)
|
||||||
|
.clients()
|
||||||
|
.findByClientId(newClientId)
|
||||||
|
.get(0);
|
||||||
|
|
||||||
|
String identifier = clientRepresentation.getAttributes().get(SamlConfigAttributes.SAML_ARTIFACT_BINDING_IDENTIFIER);
|
||||||
|
|
||||||
|
assertThat(identifier, not(equalTo(oldIdentifier)));
|
||||||
|
assertThat(identifier, equalTo(ArtifactBindingUtils.computeArtifactBindingIdentifierString(newClientId)));
|
||||||
|
}
|
||||||
|
|
||||||
|
clientRepresentation = adminClient.realm(REALM_NAME)
|
||||||
|
.clients()
|
||||||
|
.findByClientId(SAML_CLIENT_ID_SALES_POST)
|
||||||
|
.get(0);
|
||||||
|
|
||||||
|
assertThat(clientRepresentation.getAttributes().get(SamlConfigAttributes.SAML_ARTIFACT_BINDING_IDENTIFIER), equalTo(oldIdentifier));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2225,6 +2225,21 @@
|
||||||
"useTemplateConfig" : false,
|
"useTemplateConfig" : false,
|
||||||
"useTemplateScope" : false,
|
"useTemplateScope" : false,
|
||||||
"useTemplateMappers" : false
|
"useTemplateMappers" : false
|
||||||
|
}, {
|
||||||
|
"clientId": "migration-saml-client",
|
||||||
|
"enabled": true,
|
||||||
|
"fullScopeAllowed": true,
|
||||||
|
"protocol": "saml",
|
||||||
|
"baseUrl": "http://localhost:8080/sales-post",
|
||||||
|
"redirectUris": [
|
||||||
|
"http://localhost:8080/sales-post/*"
|
||||||
|
],
|
||||||
|
"attributes": {
|
||||||
|
"saml_assertion_consumer_url_post": "http://localhost:8080/sales-post/saml",
|
||||||
|
"saml_single_logout_service_url_post": "http://localhost:8080/sales-post/saml",
|
||||||
|
"saml.authnstatement": "true",
|
||||||
|
"saml_idp_initiated_sso_url_name": "sales-post"
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
"id" : "e6856a02-8f24-48d3-bb06-fae5dddae83e",
|
"id" : "e6856a02-8f24-48d3-bb06-fae5dddae83e",
|
||||||
"clientId" : "realm-management",
|
"clientId" : "realm-management",
|
||||||
|
|
|
@ -2602,6 +2602,21 @@
|
||||||
"useTemplateConfig" : false,
|
"useTemplateConfig" : false,
|
||||||
"useTemplateScope" : false,
|
"useTemplateScope" : false,
|
||||||
"useTemplateMappers" : false
|
"useTemplateMappers" : false
|
||||||
|
}, {
|
||||||
|
"clientId": "migration-saml-client",
|
||||||
|
"enabled": true,
|
||||||
|
"fullScopeAllowed": true,
|
||||||
|
"protocol": "saml",
|
||||||
|
"baseUrl": "http://localhost:8080/sales-post",
|
||||||
|
"redirectUris": [
|
||||||
|
"http://localhost:8080/sales-post/*"
|
||||||
|
],
|
||||||
|
"attributes": {
|
||||||
|
"saml_assertion_consumer_url_post": "http://localhost:8080/sales-post/saml",
|
||||||
|
"saml_single_logout_service_url_post": "http://localhost:8080/sales-post/saml",
|
||||||
|
"saml.authnstatement": "true",
|
||||||
|
"saml_idp_initiated_sso_url_name": "sales-post"
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
"id" : "c8204f6f-f8c2-4af8-9bac-c45c95b4673b",
|
"id" : "c8204f6f-f8c2-4af8-9bac-c45c95b4673b",
|
||||||
"clientId" : "realm-management",
|
"clientId" : "realm-management",
|
||||||
|
|
|
@ -810,6 +810,21 @@
|
||||||
"useTemplateConfig" : false,
|
"useTemplateConfig" : false,
|
||||||
"useTemplateScope" : false,
|
"useTemplateScope" : false,
|
||||||
"useTemplateMappers" : false
|
"useTemplateMappers" : false
|
||||||
|
}, {
|
||||||
|
"clientId": "migration-saml-client",
|
||||||
|
"enabled": true,
|
||||||
|
"fullScopeAllowed": true,
|
||||||
|
"protocol": "saml",
|
||||||
|
"baseUrl": "http://localhost:8080/sales-post",
|
||||||
|
"redirectUris": [
|
||||||
|
"http://localhost:8080/sales-post/*"
|
||||||
|
],
|
||||||
|
"attributes": {
|
||||||
|
"saml_assertion_consumer_url_post": "http://localhost:8080/sales-post/saml",
|
||||||
|
"saml_single_logout_service_url_post": "http://localhost:8080/sales-post/saml",
|
||||||
|
"saml.authnstatement": "true",
|
||||||
|
"saml_idp_initiated_sso_url_name": "sales-post"
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
"id" : "9a37d2c5-6a36-4a2c-b837-f2ea846fb0d5",
|
"id" : "9a37d2c5-6a36-4a2c-b837-f2ea846fb0d5",
|
||||||
"clientId" : "realm-management",
|
"clientId" : "realm-management",
|
||||||
|
|
|
@ -528,6 +528,21 @@
|
||||||
"nodeReRegistrationTimeout" : -1,
|
"nodeReRegistrationTimeout" : -1,
|
||||||
"defaultClientScopes" : [ "web-origins", "role_list", "profile", "roles", "email" ],
|
"defaultClientScopes" : [ "web-origins", "role_list", "profile", "roles", "email" ],
|
||||||
"optionalClientScopes" : [ "address", "phone", "offline_access" ]
|
"optionalClientScopes" : [ "address", "phone", "offline_access" ]
|
||||||
|
}, {
|
||||||
|
"clientId": "migration-saml-client",
|
||||||
|
"enabled": true,
|
||||||
|
"fullScopeAllowed": true,
|
||||||
|
"protocol": "saml",
|
||||||
|
"baseUrl": "http://localhost:8080/sales-post",
|
||||||
|
"redirectUris": [
|
||||||
|
"http://localhost:8080/sales-post/*"
|
||||||
|
],
|
||||||
|
"attributes": {
|
||||||
|
"saml_assertion_consumer_url_post": "http://localhost:8080/sales-post/saml",
|
||||||
|
"saml_single_logout_service_url_post": "http://localhost:8080/sales-post/saml",
|
||||||
|
"saml.authnstatement": "true",
|
||||||
|
"saml_idp_initiated_sso_url_name": "sales-post"
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
"id" : "99a28a93-e2e3-4b1d-a377-8a30f6b4930a",
|
"id" : "99a28a93-e2e3-4b1d-a377-8a30f6b4930a",
|
||||||
"clientId" : "realm-management",
|
"clientId" : "realm-management",
|
||||||
|
|
|
@ -554,6 +554,21 @@
|
||||||
"defaultClientScopes" : [ "web-origins", "role_list", "profile", "roles", "email" ],
|
"defaultClientScopes" : [ "web-origins", "role_list", "profile", "roles", "email" ],
|
||||||
"optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ]
|
"optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ]
|
||||||
}, {
|
}, {
|
||||||
|
"clientId": "migration-saml-client",
|
||||||
|
"enabled": true,
|
||||||
|
"fullScopeAllowed": true,
|
||||||
|
"protocol": "saml",
|
||||||
|
"baseUrl": "http://localhost:8080/sales-post",
|
||||||
|
"redirectUris": [
|
||||||
|
"http://localhost:8080/sales-post/*"
|
||||||
|
],
|
||||||
|
"attributes": {
|
||||||
|
"saml_assertion_consumer_url_post": "http://localhost:8080/sales-post/saml",
|
||||||
|
"saml_single_logout_service_url_post": "http://localhost:8080/sales-post/saml",
|
||||||
|
"saml.authnstatement": "true",
|
||||||
|
"saml_idp_initiated_sso_url_name": "sales-post"
|
||||||
|
}
|
||||||
|
},{
|
||||||
"id" : "cb1a7042-228c-4f8e-b0c9-654f1855d1b8",
|
"id" : "cb1a7042-228c-4f8e-b0c9-654f1855d1b8",
|
||||||
"clientId" : "realm-management",
|
"clientId" : "realm-management",
|
||||||
"name" : "${client_realm-management}",
|
"name" : "${client_realm-management}",
|
||||||
|
|
|
@ -4,6 +4,8 @@ import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.keycloak.admin.client.resource.ClientResource;
|
import org.keycloak.admin.client.resource.ClientResource;
|
||||||
import org.keycloak.admin.client.resource.ClientsResource;
|
import org.keycloak.admin.client.resource.ClientsResource;
|
||||||
|
import org.keycloak.protocol.saml.SamlConfigAttributes;
|
||||||
|
import org.keycloak.protocol.saml.util.ArtifactBindingUtils;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||||
import org.keycloak.testsuite.console.AbstractConsoleTest;
|
import org.keycloak.testsuite.console.AbstractConsoleTest;
|
||||||
|
@ -87,6 +89,7 @@ public abstract class AbstractClientTest extends AbstractConsoleTest {
|
||||||
attributes.put(SAML_SIGNATURE_ALGORITHM, "RSA_SHA256");
|
attributes.put(SAML_SIGNATURE_ALGORITHM, "RSA_SHA256");
|
||||||
attributes.put(SAML_FORCE_NAME_ID_FORMAT, "false");
|
attributes.put(SAML_FORCE_NAME_ID_FORMAT, "false");
|
||||||
attributes.put(SAML_NAME_ID_FORMAT, "username");
|
attributes.put(SAML_NAME_ID_FORMAT, "username");
|
||||||
|
attributes.put(SamlConfigAttributes.SAML_ARTIFACT_BINDING_IDENTIFIER, ArtifactBindingUtils.computeArtifactBindingIdentifierString("saml"));
|
||||||
return attributes;
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,8 @@ package org.keycloak.testsuite.console.clients;
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
|
import org.keycloak.protocol.saml.SamlConfigAttributes;
|
||||||
|
import org.keycloak.protocol.saml.util.ArtifactBindingUtils;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||||
import org.keycloak.testsuite.console.page.clients.settings.ClientSettings;
|
import org.keycloak.testsuite.console.page.clients.settings.ClientSettings;
|
||||||
|
@ -29,6 +31,7 @@ import org.openqa.selenium.By;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static org.keycloak.testsuite.auth.page.login.Login.OIDC;
|
import static org.keycloak.testsuite.auth.page.login.Login.OIDC;
|
||||||
|
@ -202,6 +205,23 @@ public class ClientSettingsTest extends AbstractClientTest {
|
||||||
assertClientSamlAttributes(getSAMLAttributes(), found.getAttributes());
|
assertClientSamlAttributes(getSAMLAttributes(), found.getAttributes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateSAML() {
|
||||||
|
createSAML();
|
||||||
|
|
||||||
|
final String newClientId = "new_client_id";
|
||||||
|
|
||||||
|
clientSettingsPage.form().setClientId(newClientId);
|
||||||
|
clientSettingsPage.form().save();
|
||||||
|
|
||||||
|
ClientRepresentation found = findClientByClientId(newClientId);
|
||||||
|
|
||||||
|
Map<String, String> samlAttributes = getSAMLAttributes();
|
||||||
|
samlAttributes.put(SamlConfigAttributes.SAML_ARTIFACT_BINDING_IDENTIFIER, ArtifactBindingUtils.computeArtifactBindingIdentifierString(newClientId));
|
||||||
|
|
||||||
|
assertClientSamlAttributes(samlAttributes, found.getAttributes());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void invalidSettings() {
|
public void invalidSettings() {
|
||||||
clientsPage.table().createClient();
|
clientsPage.table().createClient();
|
||||||
|
|
Loading…
Reference in a new issue