KEYCLOAK-1881 Client installers

This commit is contained in:
Hynek Mlnarik 2016-11-03 16:26:44 +01:00
parent 4f9e35c0a1
commit 8ae1b1740d
4 changed files with 58 additions and 51 deletions

View file

@ -18,7 +18,6 @@
package org.keycloak.protocol.saml.installation; package org.keycloak.protocol.saml.installation;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.common.util.PemUtils;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
@ -42,14 +41,14 @@ public class KeycloakSamlClientInstallation implements ClientInstallationProvide
@Override @Override
public Response generateInstallation(KeycloakSession session, RealmModel realm, ClientModel client, URI baseUri) { public Response generateInstallation(KeycloakSession session, RealmModel realm, ClientModel client, URI baseUri) {
SamlClient samlClient = new SamlClient(client); SamlClient samlClient = new SamlClient(client);
StringBuffer buffer = new StringBuffer(); StringBuilder buffer = new StringBuilder();
buffer.append("<keycloak-saml-adapter>\n"); buffer.append("<keycloak-saml-adapter>\n");
baseXml(session, realm, client, baseUri, samlClient, buffer); baseXml(session, realm, client, baseUri, samlClient, buffer);
buffer.append("</keycloak-saml-adapter>\n"); buffer.append("</keycloak-saml-adapter>\n");
return Response.ok(buffer.toString(), MediaType.TEXT_PLAIN_TYPE).build(); return Response.ok(buffer.toString(), MediaType.TEXT_PLAIN_TYPE).build();
} }
public static void baseXml(KeycloakSession session, RealmModel realm, ClientModel client, URI baseUri, SamlClient samlClient, StringBuffer buffer) { public static void baseXml(KeycloakSession session, RealmModel realm, ClientModel client, URI baseUri, SamlClient samlClient, StringBuilder buffer) {
buffer.append(" <SP entityID=\"").append(client.getClientId()).append("\"\n"); buffer.append(" <SP entityID=\"").append(client.getClientId()).append("\"\n");
buffer.append(" sslPolicy=\"").append(realm.getSslRequired().name()).append("\"\n"); buffer.append(" sslPolicy=\"").append(realm.getSslRequired().name()).append("\"\n");
buffer.append(" logoutPage=\"SPECIFY YOUR LOGOUT PAGE!\">\n"); buffer.append(" logoutPage=\"SPECIFY YOUR LOGOUT PAGE!\">\n");
@ -113,15 +112,6 @@ public class KeycloakSamlClientInstallation implements ClientInstallationProvide
buffer.append(" postBindingUrl=\"").append(bindingUrl).append("\"\n"); buffer.append(" postBindingUrl=\"").append(bindingUrl).append("\"\n");
buffer.append(" redirectBindingUrl=\"").append(bindingUrl).append("\""); buffer.append(" redirectBindingUrl=\"").append(bindingUrl).append("\"");
buffer.append("/>\n"); buffer.append("/>\n");
if (samlClient.requiresRealmSignature()) {
buffer.append(" <Keys>\n");
buffer.append(" <Key signing=\"true\">\n");
buffer.append(" <CertificatePem>\n");
buffer.append(" ").append(PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate())).append("\n");
buffer.append(" </CertificatePem>\n");
buffer.append(" </Key>\n");
buffer.append(" </Keys>\n");
}
buffer.append(" </IDP>\n"); buffer.append(" </IDP>\n");
buffer.append(" </SP>\n"); buffer.append(" </SP>\n");
} }
@ -138,7 +128,7 @@ public class KeycloakSamlClientInstallation implements ClientInstallationProvide
@Override @Override
public String getHelpText() { public String getHelpText() {
return "Keycloak SAML adapter configuration file. Put this in WEB-INF directory if your WAR."; return "Keycloak SAML adapter configuration file. Put this in WEB-INF directory of your WAR.";
} }
@Override @Override

View file

@ -39,7 +39,7 @@ public class KeycloakSamlSubsystemInstallation implements ClientInstallationProv
@Override @Override
public Response generateInstallation(KeycloakSession session, RealmModel realm, ClientModel client, URI baseUri) { public Response generateInstallation(KeycloakSession session, RealmModel realm, ClientModel client, URI baseUri) {
SamlClient samlClient = new SamlClient(client); SamlClient samlClient = new SamlClient(client);
StringBuffer buffer = new StringBuffer(); StringBuilder buffer = new StringBuilder();
buffer.append("<secure-deployment name=\"YOUR-WAR.war\">\n"); buffer.append("<secure-deployment name=\"YOUR-WAR.war\">\n");
KeycloakSamlClientInstallation.baseXml(session, realm, client, baseUri, samlClient, buffer); KeycloakSamlClientInstallation.baseXml(session, realm, client, baseUri, samlClient, buffer);
buffer.append("</secure-deployment>\n"); buffer.append("</secure-deployment>\n");

View file

@ -32,6 +32,11 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriBuilder;
import java.net.URI; import java.net.URI;
import java.util.Set;
import java.util.TreeSet;
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
import org.keycloak.keys.KeyMetadata;
import org.keycloak.saml.SPMetadataDescriptor;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -41,49 +46,61 @@ public class SamlIDPDescriptorClientInstallation implements ClientInstallationPr
public static String getIDPDescriptorForClient(KeycloakSession session, RealmModel realm, ClientModel client, URI serverBaseUri) { public static String getIDPDescriptorForClient(KeycloakSession session, RealmModel realm, ClientModel client, URI serverBaseUri) {
SamlClient samlClient = new SamlClient(client); SamlClient samlClient = new SamlClient(client);
String idpEntityId = RealmsResource.realmBaseUrl(UriBuilder.fromUri(serverBaseUri)).build(realm.getName()).toString(); String idpEntityId = RealmsResource.realmBaseUrl(UriBuilder.fromUri(serverBaseUri)).build(realm.getName()).toString();
String idp = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + StringBuilder sb = new StringBuilder();
"<EntityDescriptor entityID=\"" + idpEntityId + "\"\n" + sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
" xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\"\n" + + "<EntityDescriptor entityID=\"").append(idpEntityId).append("\"\n"
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n" + + " xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\"\n"
" <IDPSSODescriptor WantAuthnRequestsSigned=\"" + Boolean.toString(samlClient.requiresClientSignature()) + "\"\n" + + " xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\"\n"
" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n"; + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
+ " <IDPSSODescriptor WantAuthnRequestsSigned=\"")
.append(samlClient.requiresClientSignature())
.append("\"\n"
+ " protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n");
if (samlClient.forceNameIDFormat() && samlClient.getNameIDFormat() != null) { if (samlClient.forceNameIDFormat() && samlClient.getNameIDFormat() != null) {
idp += " <NameIDFormat>" + samlClient.getNameIDFormat() + "</NameIDFormat>\n"; sb.append(" <NameIDFormat>").append(samlClient.getNameIDFormat()).append("</NameIDFormat>\n");
} else { } else {
idp += " <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>\n" + sb.append(" <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: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:unspecified</NameIDFormat>\n"
" <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</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(); String bindUrl = RealmsResource.protocolUrl(UriBuilder.fromUri(serverBaseUri)).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString();
idp += "\n" + sb.append("\n"
" <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n" + + " <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n"
" Location=\"" + bindUrl + "\" />\n"; + " Location=\"").append(bindUrl).append("\" />\n");
if (!samlClient.forcePostBinding()) { if (! samlClient.forcePostBinding()) {
idp += " <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"\n" + sb.append(" <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"\n"
" Location=\"" + bindUrl + "\" />\n"; + " Location=\"").append(bindUrl).append("\" />\n");
} }
idp += " <SingleLogoutService\n" + sb.append(" <SingleLogoutService\n"
" Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n" + + " Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n"
" Location=\"" + bindUrl + "\" />\n"; + " Location=\"").append(bindUrl).append("\" />\n");
if (!samlClient.forcePostBinding()) { if (! samlClient.forcePostBinding()) {
idp += " <SingleLogoutService\n" + sb.append(" <SingleLogoutService\n"
" Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"\n" + + " Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"\n"
" Location=\"" + bindUrl + "\" />\n"; + " Location=\"").append(bindUrl).append("\" />\n");
} }
idp += " <KeyDescriptor use=\"signing\">\n" +
" <dsig:KeyInfo xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\">\n" + Set<KeyMetadata> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list
" <dsig:X509Data>\n" + ? (int) (o2.getProviderPriority() - o1.getProviderPriority())
" <dsig:X509Certificate>\n" + : (o1.getStatus() == KeyMetadata.Status.PASSIVE ? 1 : -1));
" " + PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate()) + "\n" + keys.addAll(session.keys().getKeys(realm, false));
" </dsig:X509Certificate>\n" + for (KeyMetadata key : keys) {
" </dsig:X509Data>\n" + addKeyInfo(sb, key, KeyTypes.SIGNING.value());
" </dsig:KeyInfo>\n" + }
" </KeyDescriptor>\n" +
" </IDPSSODescriptor>\n" + sb.append(" </IDPSSODescriptor>\n"
"</EntityDescriptor>\n"; + "</EntityDescriptor>\n");
return idp; return sb.toString();
}
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));
} }
@Override @Override

View file

@ -46,7 +46,7 @@ public class SamlSPDescriptorClientInstallation implements ClientInstallationPro
if (logoutUrl == null) logoutUrl = client.getManagementUrl(); if (logoutUrl == null) logoutUrl = client.getManagementUrl();
String nameIdFormat = samlClient.getNameIDFormat(); String nameIdFormat = samlClient.getNameIDFormat();
if (nameIdFormat == null) nameIdFormat = SamlProtocol.SAML_DEFAULT_NAMEID_FORMAT; if (nameIdFormat == null) nameIdFormat = SamlProtocol.SAML_DEFAULT_NAMEID_FORMAT;
String spCertificate = SPMetadataDescriptor.xmlKeyInfo(null, samlClient.getClientSigningCertificate(), KeyTypes.SIGNING.value(), true); String spCertificate = SPMetadataDescriptor.xmlKeyInfo(" ", null, samlClient.getClientSigningCertificate(), KeyTypes.SIGNING.value(), true);
return SPMetadataDescriptor.getSPDescriptor(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get(), assertionUrl, logoutUrl, samlClient.requiresClientSignature(), client.getClientId(), nameIdFormat, spCertificate); return SPMetadataDescriptor.getSPDescriptor(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get(), assertionUrl, logoutUrl, samlClient.requiresClientSignature(), client.getClientId(), nameIdFormat, spCertificate);
} }