keycloak-uncached

Details

diff --git a/common/src/main/java/org/keycloak/common/util/StringPropertyReplacer.java b/common/src/main/java/org/keycloak/common/util/StringPropertyReplacer.java
index 3e4839a..283eb3e 100755
--- a/common/src/main/java/org/keycloak/common/util/StringPropertyReplacer.java
+++ b/common/src/main/java/org/keycloak/common/util/StringPropertyReplacer.java
@@ -98,7 +98,7 @@ public final class StringPropertyReplacer
     public static String replaceProperties(final String string, final Properties props)
     {
         final char[] chars = string.toCharArray();
-        StringBuffer buffer = new StringBuffer();
+        StringBuilder buffer = new StringBuilder();
         boolean properties = false;
         int state = NORMAL;
         int start = 0;
diff --git a/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java b/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java
index e6c10af..cc94b8a 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java
@@ -17,26 +17,21 @@
 
 package org.keycloak.saml;
 
+import org.keycloak.common.util.PemUtils;
+
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
 public class SPMetadataDescriptor {
+
     public static String getSPDescriptor(String binding, String assertionEndpoint, String logoutEndpoint, boolean wantAuthnRequestsSigned, String entityId, String nameIDPolicyFormat, String certificatePem) {
         String descriptor =
                 "<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" entityID=\"" + entityId + "\">\n" +
                 "    <SPSSODescriptor AuthnRequestsSigned=\"" + wantAuthnRequestsSigned + "\"\n" +
                 "            protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol http://schemas.xmlsoap.org/ws/2003/07/secext\">\n";
         if (wantAuthnRequestsSigned) {
-            descriptor +=
-                    "        <KeyDescriptor use=\"signing\">\n" +
-                            "            <dsig:KeyInfo xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\">\n" +
-                            "                <dsig:X509Data>\n" +
-                            "                    <dsig:X509Certificate>\n" + certificatePem + "\n" +
-                            "                    </dsig:X509Certificate>\n" +
-                            "                </dsig:X509Data>\n" +
-                            "            </dsig:KeyInfo>\n" +
-                            "        </KeyDescriptor>\n";
+            descriptor += xmlKeyInfo(null, certificatePem, "signing", true);
         }
         descriptor +=
                 "        <SingleLogoutService Binding=\"" + binding + "\" Location=\"" + logoutEndpoint + "\"/>\n" +
@@ -44,10 +39,34 @@ public class SPMetadataDescriptor {
                 "        </NameIDFormat>\n" +
                 "        <AssertionConsumerService\n" +
                 "                Binding=\"" + binding + "\" Location=\"" + assertionEndpoint + "\"\n" +
-                "                index=\"1\" isDefault=\"true\" />\n";
-        descriptor +=
+                "                index=\"1\" isDefault=\"true\" />\n" +
                 "    </SPSSODescriptor>\n" +
                 "</EntityDescriptor>\n";
         return descriptor;
     }
+
+    public static String xmlKeyInfo(String indentation, String keyId, String pemEncodedCertificate, String purpose, boolean declareDSigNamespace) {
+        if (pemEncodedCertificate == null) {
+            return "";
+        }
+
+        StringBuilder target = new StringBuilder()
+          .append(indentation).append("<KeyDescriptor use=\"").append(purpose).append("\">\n")
+          .append(indentation).append("  <dsig:KeyInfo").append(declareDSigNamespace ? " xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\">\n" : ">\n");
+
+        if (keyId != null) {
+            target.append(indentation).append("    <dsig:KeyName>").append(keyId).append("</dsig:KeyName>\n");
+        }
+
+        target
+          .append(indentation).append("    <dsig:X509Data>\n")
+          .append(indentation).append("      <dsig:X509Certificate>").append(pemEncodedCertificate).append("</dsig:X509Certificate>\n")
+          .append(indentation).append("    </dsig:X509Data>\n")
+          .append(indentation).append("  </dsig:KeyInfo>\n")
+          .append(indentation).append("</KeyDescriptor>\n")
+        ;
+
+        return target.toString();
+    }
+
 }
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
index e02093c..40f615e 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -74,6 +74,17 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
 import java.security.PublicKey;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import org.keycloak.common.util.StringPropertyReplacer;
+import org.keycloak.dom.saml.v2.metadata.KeyTypes;
+import org.keycloak.keys.KeyMetadata;
+import org.keycloak.rotation.HardcodedKeyLocator;
+import org.keycloak.rotation.KeyLocator;
+import org.keycloak.saml.SPMetadataDescriptor;
+import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
 
 /**
  * Resource class for the oauth/openid connect token service
@@ -541,12 +552,30 @@ public class SamlService extends AuthorizationEndpointBase {
     public static String getIDPMetadataDescriptor(UriInfo uriInfo, KeycloakSession session, RealmModel realm) throws IOException {
         InputStream is = SamlService.class.getResourceAsStream("/idp-metadata-template.xml");
         String template = StreamUtil.readString(is);
-        template = template.replace("${idp.entityID}", RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString());
-        template = template.replace("${idp.sso.HTTP-POST}", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
-        template = template.replace("${idp.sso.HTTP-Redirect}", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
-        template = template.replace("${idp.sls.HTTP-POST}", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
-        template = template.replace("${idp.signing.certificate}", PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate()));
-        return template;
+        Properties props = new Properties();
+        props.put("idp.entityID", RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString());
+        props.put("idp.sso.HTTP-POST", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
+        props.put("idp.sso.HTTP-Redirect", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
+        props.put("idp.sls.HTTP-POST", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
+        StringBuilder keysString = new StringBuilder();
+        Set<KeyMetadata> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list
+          ? (int) (o2.getProviderPriority() - o1.getProviderPriority())
+          : (o1.getStatus() == KeyMetadata.Status.PASSIVE ? 1 : -1));
+        keys.addAll(session.keys().getKeys(realm, false));
+        for (KeyMetadata key : keys) {
+            addKeyInfo(keysString, key, KeyTypes.SIGNING.value());
+        }
+        props.put("idp.signing.certificates", keysString.toString());
+        return StringPropertyReplacer.replaceProperties(template, props);
+    }
+
+    private static void addKeyInfo(StringBuilder target, KeyMetadata key, String purpose) {
+        if (key == null) {
+            return;
+        }
+
+        target.append(SPMetadataDescriptor.xmlKeyInfo("                        ",
+          key.getKid(), PemUtils.encodeCertificate(key.getCertificate()), purpose, false));
     }
 
     @GET
diff --git a/services/src/main/resources/idp-metadata-template.xml b/services/src/main/resources/idp-metadata-template.xml
index 0a53647..a4416cd 100755
--- a/services/src/main/resources/idp-metadata-template.xml
+++ b/services/src/main/resources/idp-metadata-template.xml
@@ -16,22 +16,12 @@
   ~ limitations under the License.
   -->
 
-<EntitiesDescriptor Name="urn:keycloak"
-					xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+<EntitiesDescriptor Name="urn:keycloak" xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
 					xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">
 	<EntityDescriptor entityID="${idp.entityID}">
 		<IDPSSODescriptor WantAuthnRequestsSigned="true"
 			protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
-
-			<KeyDescriptor use="signing">
-				 <dsig:KeyInfo>
-					 <dsig:X509Data>
-						 <dsig:X509Certificate>
-							 ${idp.signing.certificate}
-						 </dsig:X509Certificate>
-					 </dsig:X509Data>
-				 </dsig:KeyInfo>
-			</KeyDescriptor>
+${idp.signing.certificates}
 			<SingleLogoutService
 					Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
 					Location="${idp.sls.HTTP-POST}" />