KEYCLOAK-1881 Client installers
This commit is contained in:
parent
4f9e35c0a1
commit
8ae1b1740d
4 changed files with 58 additions and 51 deletions
|
@ -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
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue