keycloak-developers

Merge pull request #2034 from patriot1burke/master saml

1/14/2016 10:26:07 PM

Details

diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
index 0014470..12b0428 100755
--- a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
+++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
@@ -38,6 +38,7 @@ import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder;
 import org.keycloak.saml.SAML2AuthnRequestBuilder;
 import org.keycloak.saml.SAML2LogoutRequestBuilder;
 import org.keycloak.saml.SAML2NameIDPolicyBuilder;
+import org.keycloak.saml.SPMetadataDescriptor;
 import org.keycloak.saml.SignatureAlgorithm;
 import org.keycloak.saml.common.constants.GeneralConstants;
 import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
@@ -227,31 +228,11 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
                 .build().toString();
 
 
-
-        String descriptor =
-                "<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" entityID=\"" + getEntityId(uriInfo, realm) + "\">\n" +
-                "    <SPSSODescriptor AuthnRequestsSigned=\"" + getConfig().isWantAuthnRequestsSigned() + "\"\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" +
-                "        <NameIDFormat>" + getConfig().getNameIDPolicyFormat() + "\n" +
-                "        </NameIDFormat>\n" +
-                "        <SingleLogoutService Binding=\"" + authnBinding + "\" Location=\"" + endpoint + "\"/>\n" +
-                "        <AssertionConsumerService\n" +
-                "                Binding=\"" + authnBinding + "\" Location=\"" + endpoint + "\"\n" +
-                "                index=\"1\" isDefault=\"true\" />\n";
-        if (getConfig().isWantAuthnRequestsSigned()) {
-            descriptor +=
-                "        <KeyDescriptor use=\"signing\">\n" +
-                "            <dsig:KeyInfo xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\">\n" +
-                "                <dsig:X509Data>\n" +
-                "                    <dsig:X509Certificate>\n" + realm.getCertificatePem() + "\n" +
-                "                    </dsig:X509Certificate>\n" +
-                "                </dsig:X509Data>\n" +
-                "            </dsig:KeyInfo>\n" +
-                "        </KeyDescriptor>\n";
-        }
-        descriptor +=
-                "    </SPSSODescriptor>\n" +
-                "</EntityDescriptor>\n";
+        boolean wantAuthnRequestsSigned = getConfig().isWantAuthnRequestsSigned();
+        String entityId = getEntityId(uriInfo, realm);
+        String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
+        String certificatePem = realm.getCertificatePem();
+        String descriptor = SPMetadataDescriptor.getSPDescriptor(authnBinding, endpoint, endpoint, wantAuthnRequestsSigned, entityId, nameIDPolicyFormat, certificatePem);
         return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build();
     }
 
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
index b0853cb..aac6a55 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
@@ -700,14 +700,28 @@ module.controller('ClientInstallationCtrl', function($scope, realm, client, serv
 
     $scope.changeFormat = function() {
         var url = ClientInstallation.url({ realm: $routeParams.realm, client: $routeParams.client, provider: $scope.configFormat.id });
-        $http.get(url).success(function(data) {
-            var installation = data;
-            if ($scope.configFormat.mediaType == 'application/json') {
-                installation = angular.fromJson(data);
-                installation = angular.toJson(installation, true);
-            }
-            $scope.installation = installation;
-        })
+        if ($scope.configFormat.mediaType == 'application/zip') {
+            $http({
+                url: url,
+                method: 'GET',
+                responseType: 'arraybuffer',
+                cache: false
+            }).success(function(data) {
+                var installation = data;
+                $scope.installation = installation;
+                }
+            );
+        } else {
+            $http.get(url).success(function (data) {
+                var installation = data;
+                if ($scope.configFormat.mediaType == 'application/json') {
+                    installation = angular.fromJson(data);
+                    installation = angular.toJson(installation, true);
+                }
+                $scope.installation = installation;
+            });
+        }
+
     };
     $scope.download = function() {
         saveAs(new Blob([$scope.installation], { type: $scope.configFormat.mediaType }), $scope.configFormat.filename);
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-installation.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-installation.html
index 1530ba8..d03fdd2 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-installation.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-installation.html
@@ -26,7 +26,7 @@
             <div class="form-group" ng-show="installation">
                 <div class="col-sm-12">
                     <a class="btn btn-primary btn-lg" data-ng-click="download()" type="submit" ng-show="installation">{{:: 'download' | translate}}</a>
-                    <textarea class="form-control" rows="20" kc-select-action="click">{{installation}}</textarea>
+                    <textarea class="form-control" rows="20" kc-select-action="click" data-ng-hide="configFormat.downloadOnly">{{installation}}</textarea>
                 </div>
             </div>
         </fieldset>
diff --git a/saml/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java b/saml/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java
new file mode 100755
index 0000000..78ff2f3
--- /dev/null
+++ b/saml/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java
@@ -0,0 +1,35 @@
+package org.keycloak.saml;
+
+/**
+ * @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" +
+                "        <NameIDFormat>" + nameIDPolicyFormat + "\n" +
+                "        </NameIDFormat>\n" +
+                "        <SingleLogoutService Binding=\"" + binding + "\" Location=\"" + logoutEndpoint + "\"/>\n" +
+                "        <AssertionConsumerService\n" +
+                "                Binding=\"" + binding + "\" Location=\"" + assertionEndpoint + "\"\n" +
+                "                index=\"1\" isDefault=\"true\" />\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 +=
+                "    </SPSSODescriptor>\n" +
+                "</EntityDescriptor>\n";
+        return descriptor;
+    }
+}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/installation/ModAuthMellonClientInstallation.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/installation/ModAuthMellonClientInstallation.java
new file mode 100755
index 0000000..c7bd844
--- /dev/null
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/installation/ModAuthMellonClientInstallation.java
@@ -0,0 +1,117 @@
+package org.keycloak.protocol.saml.installation;
+
+import org.keycloak.Config;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.protocol.ClientInstallationProvider;
+import org.keycloak.protocol.saml.SamlClient;
+import org.keycloak.protocol.saml.SamlProtocol;
+
+import javax.ws.rs.core.Response;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ModAuthMellonClientInstallation implements ClientInstallationProvider {
+    @Override
+    public Response generateInstallation(KeycloakSession session, RealmModel realm, ClientModel client, URI serverBaseUri) {
+        SamlClient samlClient = new SamlClient(client);
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        ZipOutputStream zip = new ZipOutputStream(baos);
+        String idpDescriptor = SamlIDPDescriptorClientInstallation.getIDPDescriptorForClient(realm, client, serverBaseUri);
+        String spDescriptor = SamlSPDescriptorClientInstallation.getSPDescriptorForClient(client);
+        String clientDirName = client.getClientId()
+                .replace('/', '_')
+                .replace(' ', '_');
+        try {
+            zip.putNextEntry(new ZipEntry(clientDirName + "/idp-metadata.xml"));
+            zip.write(idpDescriptor.getBytes());
+            zip.closeEntry();
+            zip.putNextEntry(new ZipEntry(clientDirName + "/sp-metadata.xml"));
+            zip.write(spDescriptor.getBytes());
+            zip.closeEntry();
+            if (samlClient.requiresClientSignature()) {
+                if (samlClient.getClientSigningPrivateKey() != null) {
+                    zip.putNextEntry(new ZipEntry(clientDirName + "/client-private-key.pem"));
+                    zip.write(samlClient.getClientSigningPrivateKey().getBytes());
+                    zip.closeEntry();
+                }
+                if (samlClient.getClientSigningCertificate() != null) {
+                    zip.putNextEntry(new ZipEntry(clientDirName + "/client-cert.pem"));
+                    zip.write(samlClient.getClientSigningCertificate().getBytes());
+                    zip.closeEntry();
+                }
+            }
+            zip.close();
+            baos.close();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+
+
+        return Response.ok(baos.toByteArray(), getMediaType()).build();
+    }
+
+    @Override
+    public String getProtocol() {
+        return SamlProtocol.LOGIN_PROTOCOL;
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "Mod Auth Mellon files";
+    }
+
+    @Override
+    public String getHelpText() {
+        return "This is a zip file.  It contains a SAML SP descriptor, SAML IDP descriptor,  private key pem, and certificate pem that you will use to configure mod_auth_mellon for Apache.  You'll use these files when crafting the main Apache configuration file.  See mod_auth_mellon website for more details.";
+    }
+
+    @Override
+    public String getFilename() {
+        return "keycloak-mod-auth-mellon-sp-config.zip";
+    }
+
+    @Override
+    public String getMediaType() {
+        return "application/zip";
+    }
+
+    @Override
+    public boolean isDownloadOnly() {
+        return true;
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    @Override
+    public ClientInstallationProvider create(KeycloakSession session) {
+        return this;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public String getId() {
+        return "mod-auth-mellon";
+    }
+}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/installation/SamlIDPDescriptorClientInstallation.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/installation/SamlIDPDescriptorClientInstallation.java
new file mode 100755
index 0000000..0caee9b
--- /dev/null
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/installation/SamlIDPDescriptorClientInstallation.java
@@ -0,0 +1,120 @@
+package org.keycloak.protocol.saml.installation;
+
+import org.keycloak.Config;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.protocol.ClientInstallationProvider;
+import org.keycloak.protocol.saml.SamlClient;
+import org.keycloak.protocol.saml.SamlProtocol;
+import org.keycloak.services.resources.RealmsResource;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import java.net.URI;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlIDPDescriptorClientInstallation implements ClientInstallationProvider {
+    public static String getIDPDescriptorForClient(RealmModel realm, ClientModel client, URI serverBaseUri) {
+        SamlClient samlClient = new SamlClient(client);
+        String idpEntityId = RealmsResource.realmBaseUrl(UriBuilder.fromUri(serverBaseUri)).build(realm.getName()).toString();
+        String idp = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                 "<EntityDescriptor entityID=\"" + idpEntityId + "\"\n" +
+                "                   xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\"\n" +
+                "                   xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n" +
+                "   <IDPSSODescriptor WantAuthnRequestsSigned=\"" + Boolean.toString(samlClient.requiresClientSignature()) + "\"\n" +
+                "      protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n";
+        if (samlClient.forceNameIDFormat() && samlClient.getNameIDFormat() != null) {
+            idp +=  "      " + samlClient.getNameIDFormat();
+        } else {
+            idp +=  "      <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>\n" +
+                    "      <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>\n" +
+                    "      <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>\n" +
+                    "      <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>\n";
+        }
+        String bindUrl = RealmsResource.protocolUrl(UriBuilder.fromUri(serverBaseUri)).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString();
+        idp +=  "\n" +
+                "      <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n" +
+                "         Location=\"" + bindUrl + "\" />\n" +
+                "      <SingleLogoutService\n" +
+                "         Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n" +
+                "         Location=\"" + bindUrl + "\" />\n" +
+                "            <KeyDescriptor use=\"signing\">\n" +
+                "                <dsig:KeyInfo xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\">\n" +
+                "                    <dsig:X509Data>\n" +
+                "                        <dsig:X509Certificate>\n" +
+                "                            " + realm.getCertificatePem() + "\n" +
+                "                        </dsig:X509Certificate>\n" +
+                "                    </dsig:X509Data>\n" +
+                "                </dsig:KeyInfo>\n" +
+                "            </KeyDescriptor>\n" +
+                "      </IDPSSODescriptor>\n" +
+                "</EntityDescriptor>\n";
+        return idp;
+    }
+
+    @Override
+    public Response generateInstallation(KeycloakSession session, RealmModel realm, ClientModel client, URI serverBaseUri) {
+        String descriptor = getIDPDescriptorForClient(realm, client, serverBaseUri);
+        return Response.ok(descriptor, MediaType.TEXT_PLAIN_TYPE).build();
+    }
+
+    @Override
+    public String getProtocol() {
+        return SamlProtocol.LOGIN_PROTOCOL;
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "SAML Metadata IDPSSODescriptor";
+    }
+
+    @Override
+    public String getHelpText() {
+        return "SAML Metadata IDSSODescriptor tailored for the client.  This is special because not every client may require things like digital signatures";
+    }
+
+    @Override
+    public String getFilename() {
+        return "client-tailored-saml-idp-metadata.xml";
+    }
+
+    public String getMediaType() {
+        return MediaType.APPLICATION_XML;
+    }
+
+    @Override
+    public boolean isDownloadOnly() {
+        return false;
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    @Override
+    public ClientInstallationProvider create(KeycloakSession session) {
+        return this;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public String getId() {
+        return "saml-idp-descriptor";
+    }
+}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java
new file mode 100755
index 0000000..0165e30
--- /dev/null
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java
@@ -0,0 +1,93 @@
+package org.keycloak.protocol.saml.installation;
+
+import org.keycloak.Config;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.protocol.ClientInstallationProvider;
+import org.keycloak.protocol.saml.SamlClient;
+import org.keycloak.protocol.saml.SamlProtocol;
+import org.keycloak.saml.SPMetadataDescriptor;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.net.URI;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlSPDescriptorClientInstallation implements ClientInstallationProvider {
+    public static String getSPDescriptorForClient(ClientModel client) {
+        SamlClient samlClient = new SamlClient(client);
+        String assertionUrl = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE);
+        if (assertionUrl == null) assertionUrl = client.getManagementUrl();
+        String logoutUrl = client.getAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE);
+        if (logoutUrl == null) logoutUrl = client.getManagementUrl();
+        String nameIdFormat = samlClient.getNameIDFormat();
+        if (nameIdFormat == null) nameIdFormat = SamlProtocol.SAML_DEFAULT_NAMEID_FORMAT;
+        return SPMetadataDescriptor.getSPDescriptor(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get(), assertionUrl, logoutUrl, samlClient.requiresClientSignature(), client.getClientId(), nameIdFormat, samlClient.getClientSigningCertificate());
+    }
+
+    @Override
+    public Response generateInstallation(KeycloakSession session, RealmModel realm, ClientModel client, URI serverBaseUri) {
+        String descriptor = getSPDescriptorForClient(client);
+        return Response.ok(descriptor, MediaType.TEXT_PLAIN_TYPE).build();
+    }
+
+    @Override
+    public String getProtocol() {
+        return SamlProtocol.LOGIN_PROTOCOL;
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "SAML Metadata SPSSODescriptor";
+    }
+
+    @Override
+    public String getHelpText() {
+        return "SAML SP Metadata EntityDescriptor or rather SPSSODescriptor. This is an XML file.";
+    }
+
+    @Override
+    public String getFilename() {
+        return "saml-sp-metadata.xml";
+    }
+
+    public String getMediaType() {
+        return MediaType.APPLICATION_XML;
+    }
+
+    @Override
+    public boolean isDownloadOnly() {
+        return false;
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    @Override
+    public ClientInstallationProvider create(KeycloakSession session) {
+        return this;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public String getId() {
+        return "saml-sp-descriptor";
+    }
+}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlClient.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlClient.java
index 3ac9892..3ddd883 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlClient.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlClient.java
@@ -3,6 +3,8 @@ package org.keycloak.protocol.saml;
 import org.keycloak.models.ClientConfigResolver;
 import org.keycloak.models.ClientModel;
 import org.keycloak.saml.SignatureAlgorithm;
+import org.keycloak.saml.common.constants.GeneralConstants;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -37,7 +39,24 @@ public class SamlClient extends ClientConfigResolver {
     }
 
     public String getNameIDFormat() {
-        return resolveAttribute(SamlConfigAttributes.SAML_NAME_ID_FORMAT_ATTRIBUTE);
+        String nameIdFormat = null;
+
+        String configuredNameIdFormat = resolveAttribute(SamlConfigAttributes.SAML_NAME_ID_FORMAT_ATTRIBUTE);
+        if (configuredNameIdFormat != null) {
+            if (configuredNameIdFormat.equals("email")) {
+                nameIdFormat = JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get();
+            } else if (configuredNameIdFormat.equals("persistent")) {
+                nameIdFormat = JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get();
+            } else if (configuredNameIdFormat.equals("transient")) {
+                nameIdFormat = JBossSAMLURIConstants.NAMEID_FORMAT_TRANSIENT.get();
+            } else if (configuredNameIdFormat.equals("username")) {
+                nameIdFormat = JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get();
+            } else {
+                nameIdFormat = JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get();
+            }
+        }
+        return nameIdFormat;
+
     }
     public void setNameIDFormat(String format) {
         client.setAttribute(SamlConfigAttributes.SAML_NAME_ID_FORMAT_ATTRIBUTE, format);
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
index c76c853..d6af4d8 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
@@ -251,18 +251,8 @@ public class SamlProtocol implements LoginProtocol {
         boolean forceFormat = samlClient.forceNameIDFormat();
         String configuredNameIdFormat = samlClient.getNameIDFormat();
         if ((nameIdFormat == null || forceFormat) && configuredNameIdFormat != null) {
-            if (configuredNameIdFormat.equals("email")) {
-                nameIdFormat = JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get();
-            } else if (configuredNameIdFormat.equals("persistent")) {
-                nameIdFormat = JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get();
-            } else if (configuredNameIdFormat.equals("transient")) {
-                nameIdFormat = JBossSAMLURIConstants.NAMEID_FORMAT_TRANSIENT.get();
-            } else if (configuredNameIdFormat.equals("username")) {
-                nameIdFormat = JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get();
-            } else {
-                nameIdFormat = JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get();
-            }
-        }
+            nameIdFormat = configuredNameIdFormat;
+         }
         if (nameIdFormat == null)
             return SAML_DEFAULT_NAMEID_FORMAT;
         return nameIdFormat;
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
index bd6e846..1f90735 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -1,5 +1,6 @@
 package org.keycloak.protocol.saml;
 
+import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
 import java.security.PublicKey;
@@ -14,6 +15,7 @@ import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
 
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.spi.ResteasyProviderFactory;
@@ -477,7 +479,12 @@ public class SamlService extends AuthorizationEndpointBase {
     @Path("descriptor")
     @Produces(MediaType.APPLICATION_XML)
     public String getDescriptor() throws Exception {
-        InputStream is = getClass().getResourceAsStream("/idp-metadata-template.xml");
+        return getIDPMetadataDescriptor(uriInfo, realm);
+
+    }
+
+    public static String getIDPMetadataDescriptor(UriInfo uriInfo, 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());
@@ -485,7 +492,6 @@ public class SamlService extends AuthorizationEndpointBase {
         template = template.replace("${idp.sls.HTTP-POST}", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
         template = template.replace("${idp.signing.certificate}", realm.getCertificatePem());
         return template;
-
     }
 
     @GET
diff --git a/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.ClientInstallationProvider b/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.ClientInstallationProvider
index f8e9df5..f1d1ef2 100755
--- a/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.ClientInstallationProvider
+++ b/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.ClientInstallationProvider
@@ -1 +1,4 @@
 org.keycloak.protocol.saml.installation.KeycloakSamlClientInstallation
+org.keycloak.protocol.saml.installation.SamlSPDescriptorClientInstallation
+org.keycloak.protocol.saml.installation.SamlIDPDescriptorClientInstallation
+org.keycloak.protocol.saml.installation.ModAuthMellonClientInstallation
diff --git a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
index e0ea200..7e13bf9 100755
--- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
@@ -46,7 +46,12 @@ public class RealmsResource {
     protected BruteForceProtector protector;
 
     public static UriBuilder realmBaseUrl(UriInfo uriInfo) {
-        return uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(RealmsResource.class, "getRealmResource");
+        UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder();
+        return realmBaseUrl(baseUriBuilder);
+    }
+
+    public static UriBuilder realmBaseUrl(UriBuilder baseUriBuilder) {
+        return baseUriBuilder.path(RealmsResource.class).path(RealmsResource.class, "getRealmResource");
     }
 
     public static UriBuilder accountUrl(UriBuilder base) {