2213
This commit is contained in:
parent
71b6ed80ae
commit
78fe064cf0
12 changed files with 221 additions and 38 deletions
|
@ -709,33 +709,6 @@ module.controller('ClientInstallationCtrl', function($scope, realm, client, serv
|
||||||
$scope.installation = installation;
|
$scope.installation = installation;
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
$scope.changeFormat = function() {
|
|
||||||
if ($scope.configFormat == "Keycloak JSON") {
|
|
||||||
$scope.filename = 'keycloak.json';
|
|
||||||
|
|
||||||
var url = ClientInstallation.url({ realm: $routeParams.realm, client: $routeParams.client });
|
|
||||||
$http.get(url).success(function(data) {
|
|
||||||
var tmp = angular.fromJson(data);
|
|
||||||
$scope.installation = angular.toJson(tmp, true);
|
|
||||||
$scope.type = 'application/json';
|
|
||||||
})
|
|
||||||
} else if ($scope.configFormat == "Wildfly/EAP Subsystem XML") {
|
|
||||||
$scope.filename = 'keycloak.xml';
|
|
||||||
|
|
||||||
var url = ClientInstallationJBoss.url({ realm: $routeParams.realm, client: $routeParams.client });
|
|
||||||
$http.get(url).success(function(data) {
|
|
||||||
$scope.installation = data;
|
|
||||||
$scope.type = 'text/xml';
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
console.debug($scope.filename);
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
|
|
||||||
$scope.download = function() {
|
$scope.download = function() {
|
||||||
saveAs(new Blob([$scope.installation], { type: $scope.configFormat.mediaType }), $scope.configFormat.filename);
|
saveAs(new Blob([$scope.installation], { type: $scope.configFormat.mediaType }), $scope.configFormat.filename);
|
||||||
}
|
}
|
||||||
|
@ -1080,7 +1053,7 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
|
||||||
module.controller('CreateClientCtrl', function($scope, realm, client, templates, $route, serverInfo, Client, ClientDescriptionConverter, $location, $modal, Dialog, Notifications) {
|
module.controller('CreateClientCtrl', function($scope, realm, client, templates, $route, serverInfo, Client, ClientDescriptionConverter, $location, $modal, Dialog, Notifications) {
|
||||||
$scope.protocols = ['openid-connect',
|
$scope.protocols = ['openid-connect',
|
||||||
'saml'];//Object.keys(serverInfo.providers['login-protocol'].providers).sort();
|
'saml'];//Object.keys(serverInfo.providers['login-protocol'].providers).sort();
|
||||||
|
$scope.create = true;
|
||||||
$scope.templates = [ {name:'NONE'}];
|
$scope.templates = [ {name:'NONE'}];
|
||||||
for (var i = 0; i < templates.length; i++) {
|
for (var i = 0; i < templates.length; i++) {
|
||||||
var template = templates[i];
|
var template = templates[i];
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
|
|
||||||
<li ng-class="{active: path[4] == 'clustering'}" data-ng-show="!client.publicClient"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/clustering">{{:: 'clustering' | translate}}</a></li>
|
<li ng-class="{active: path[4] == 'clustering'}" data-ng-show="!client.publicClient"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/clustering">{{:: 'clustering' | translate}}</a></li>
|
||||||
|
|
||||||
<li ng-class="{active: path[4] == 'installation'}" data-ng-show="client.protocol != 'saml'">
|
<li ng-class="{active: path[4] == 'installation'}">
|
||||||
<a href="#/realms/{{realm.realm}}/clients/{{client.id}}/installation">{{:: 'installation' | translate}}</a>
|
<a href="#/realms/{{realm.realm}}/clients/{{client.id}}/installation">{{:: 'installation' | translate}}</a>
|
||||||
<kc-tooltip>{{:: 'installation.tooltip' | translate}}</kc-tooltip>
|
<kc-tooltip>{{:: 'installation.tooltip' | translate}}</kc-tooltip>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -36,4 +36,16 @@
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>1.8</source>
|
||||||
|
<target>1.8</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -98,6 +98,8 @@ public class DeploymentBuilder {
|
||||||
|
|
||||||
}
|
}
|
||||||
if (key.isEncryption()) {
|
if (key.isEncryption()) {
|
||||||
|
if (key.getKeystore() != null) {
|
||||||
|
|
||||||
KeyStore keyStore = loadKeystore(resourceLoader, key);
|
KeyStore keyStore = loadKeystore(resourceLoader, key);
|
||||||
try {
|
try {
|
||||||
PrivateKey privateKey = (PrivateKey) keyStore.getKey(key.getKeystore().getPrivateKeyAlias(), key.getKeystore().getPrivateKeyPassword().toCharArray());
|
PrivateKey privateKey = (PrivateKey) keyStore.getKey(key.getKeystore().getPrivateKeyAlias(), key.getKeystore().getPrivateKeyPassword().toCharArray());
|
||||||
|
@ -105,6 +107,18 @@ public class DeploymentBuilder {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (key.getPrivateKeyPem() == null) {
|
||||||
|
throw new RuntimeException("SP signing key must have a PrivateKey defined");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
PrivateKey privateKey = PemUtils.decodePrivateKey(key.getPrivateKeyPem().trim());
|
||||||
|
deployment.setDecryptionKey(privateKey);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,7 @@ public class EntityDescriptorDescriptionConverter implements ClientDescriptionCo
|
||||||
attributes.put(SamlConfigAttributes.SAML_SIGNING_CERTIFICATE_ATTRIBUTE, certPem);
|
attributes.put(SamlConfigAttributes.SAML_SIGNING_CERTIFICATE_ATTRIBUTE, certPem);
|
||||||
} else if (keyDescriptor.getUse() == KeyTypes.ENCRYPTION) {
|
} else if (keyDescriptor.getUse() == KeyTypes.ENCRYPTION) {
|
||||||
attributes.put(SamlConfigAttributes.SAML_ENCRYPT, SamlProtocol.ATTRIBUTE_TRUE_VALUE);
|
attributes.put(SamlConfigAttributes.SAML_ENCRYPT, SamlProtocol.ATTRIBUTE_TRUE_VALUE);
|
||||||
attributes.put(SamlProtocol.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE, certPem);
|
attributes.put(SamlConfigAttributes.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE, certPem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -121,4 +121,21 @@ public class SamlClient extends ClientConfigResolver {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getClientEncryptingCertificate() {
|
||||||
|
return client.getAttribute(SamlConfigAttributes.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientEncryptingCertificate(String val) {
|
||||||
|
client.setAttribute(SamlConfigAttributes.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE, val);
|
||||||
|
|
||||||
|
}
|
||||||
|
public String getClientEncryptingPrivateKey() {
|
||||||
|
return client.getAttribute(SamlConfigAttributes.SAML_ENCRYPTION_PRIVATE_KEY_ATTRIBUTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientEncryptingPrivateKey(String val) {
|
||||||
|
client.setAttribute(SamlConfigAttributes.SAML_ENCRYPTION_PRIVATE_KEY_ATTRIBUTE, val);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,4 +19,6 @@ public interface SamlConfigAttributes {
|
||||||
String SAML_ENCRYPT = "saml.encrypt";
|
String SAML_ENCRYPT = "saml.encrypt";
|
||||||
String SAML_CLIENT_SIGNATURE_ATTRIBUTE = "saml.client.signature";
|
String SAML_CLIENT_SIGNATURE_ATTRIBUTE = "saml.client.signature";
|
||||||
String SAML_SIGNING_CERTIFICATE_ATTRIBUTE = "saml.signing." + ClientAttributeCertificateResource.X509CERTIFICATE;
|
String SAML_SIGNING_CERTIFICATE_ATTRIBUTE = "saml.signing." + ClientAttributeCertificateResource.X509CERTIFICATE;
|
||||||
|
String SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE = "saml.encryption." + ClientAttributeCertificateResource.X509CERTIFICATE;
|
||||||
|
String SAML_ENCRYPTION_PRIVATE_KEY_ATTRIBUTE = "saml.encryption." + ClientAttributeCertificateResource.PRIVATE_KEY;
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,6 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
|
|
||||||
public static final String ATTRIBUTE_TRUE_VALUE = "true";
|
public static final String ATTRIBUTE_TRUE_VALUE = "true";
|
||||||
public static final String ATTRIBUTE_FALSE_VALUE = "false";
|
public static final String ATTRIBUTE_FALSE_VALUE = "false";
|
||||||
public static final String SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE = "saml.encryption." + ClientAttributeCertificateResource.X509CERTIFICATE;
|
|
||||||
public static final String SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE = "saml_assertion_consumer_url_post";
|
public static final String SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE = "saml_assertion_consumer_url_post";
|
||||||
public static final String SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE = "saml_assertion_consumer_url_redirect";
|
public static final String SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE = "saml_assertion_consumer_url_redirect";
|
||||||
public static final String SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE = "saml_single_logout_service_url_post";
|
public static final String SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE = "saml_single_logout_service_url_post";
|
||||||
|
|
|
@ -49,7 +49,7 @@ public class SamlProtocolUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PublicKey getEncryptionValidationKey(ClientModel client) throws VerificationException {
|
public static PublicKey getEncryptionValidationKey(ClientModel client) throws VerificationException {
|
||||||
return getPublicKey(client, SamlProtocol.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE);
|
return getPublicKey(client, SamlConfigAttributes.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PublicKey getPublicKey(ClientModel client, String attribute) throws VerificationException {
|
public static PublicKey getPublicKey(ClientModel client, String attribute) throws VerificationException {
|
||||||
|
|
|
@ -0,0 +1,161 @@
|
||||||
|
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;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class KeycloakSamlClientInstallation implements ClientInstallationProvider {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response generateInstallation(KeycloakSession session, RealmModel realm, ClientModel client, URI baseUri) {
|
||||||
|
SamlClient samlClient = new SamlClient(client);
|
||||||
|
StringBuffer buffer = new StringBuffer();
|
||||||
|
buffer.append("<keycloak-saml-adapter>\n");
|
||||||
|
buffer.append(" <SP entityID=\"").append(client.getClientId()).append("\"\n");
|
||||||
|
buffer.append(" sslPolicy=\"").append(realm.getSslRequired().name()).append("\"\n");
|
||||||
|
buffer.append(" logoutPage=\"SPECIFY YOUR LOGOUT PAGE!\">\n");
|
||||||
|
if (samlClient.requiresClientSignature() || samlClient.requiresEncryption()) {
|
||||||
|
buffer.append(" <Keys>\n");
|
||||||
|
if (samlClient.requiresClientSignature()) {
|
||||||
|
buffer.append(" <Key signing=\"true\">\n");
|
||||||
|
buffer.append(" <PrivateKeyPem>\n");
|
||||||
|
if (samlClient.getClientSigningPrivateKey() == null) {
|
||||||
|
buffer.append(" PRIVATE KEY NOT SET UP OR KNOWN\n");
|
||||||
|
} else {
|
||||||
|
buffer.append(" ").append(samlClient.getClientSigningPrivateKey()).append("\n");
|
||||||
|
}
|
||||||
|
buffer.append(" </PrivateKeyPem>\n");
|
||||||
|
buffer.append(" <CertificatePem>\n");
|
||||||
|
if (samlClient.getClientSigningCertificate() == null) {
|
||||||
|
buffer.append(" YOU MUST CONFIGURE YOUR CLIENT's SIGNING CERTIFICATE\n");
|
||||||
|
} else {
|
||||||
|
buffer.append(" ").append(samlClient.getClientSigningCertificate()).append("\n");
|
||||||
|
}
|
||||||
|
buffer.append(" </CertificatePem>\n");
|
||||||
|
buffer.append(" </Key>\n");
|
||||||
|
}
|
||||||
|
if (samlClient.requiresEncryption()) {
|
||||||
|
buffer.append(" <Key encryption=\"true\">\n");
|
||||||
|
buffer.append(" <PrivateKeyPem>\n");
|
||||||
|
if (samlClient.getClientEncryptingPrivateKey() == null) {
|
||||||
|
buffer.append(" PRIVATE KEY NOT SET UP OR KNOWN\n");
|
||||||
|
} else {
|
||||||
|
buffer.append(" ").append(samlClient.getClientEncryptingPrivateKey()).append("\n");
|
||||||
|
}
|
||||||
|
buffer.append(" </PrivateKeyPem>\n");
|
||||||
|
buffer.append(" </Key>\n");
|
||||||
|
|
||||||
|
}
|
||||||
|
buffer.append(" </Keys>\n");
|
||||||
|
}
|
||||||
|
buffer.append(" <IDP entityID=\"idp\"");
|
||||||
|
if (samlClient.requiresClientSignature()) {
|
||||||
|
buffer.append("\n signatureAlgorithm=\"").append(samlClient.getSignatureAlgorithm()).append("\"");
|
||||||
|
if (samlClient.getCanonicalizationMethod() != null) {
|
||||||
|
buffer.append("\n signatureCanonicalizationMethod=\"").append(samlClient.getCanonicalizationMethod()).append("\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer.append(">\n");
|
||||||
|
buffer.append(" <SingleSignOnService signRequest=\"").append(Boolean.toString(samlClient.requiresClientSignature())).append("\"\n");
|
||||||
|
buffer.append(" validateResponseSignature=\"").append(Boolean.toString(samlClient.requiresRealmSignature())).append("\"\n");
|
||||||
|
buffer.append(" requestBinding=\"POST\"\n");
|
||||||
|
UriBuilder bindingUrlBuilder = UriBuilder.fromUri(baseUri);
|
||||||
|
String bindingUrl = RealmsResource.protocolUrl(bindingUrlBuilder)
|
||||||
|
.build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString();
|
||||||
|
buffer.append(" bindingUrl=\"").append(bindingUrl).append("\"/>\n");
|
||||||
|
|
||||||
|
buffer.append(" <SingleLogoutService signRequest=\"").append(Boolean.toString(samlClient.requiresClientSignature())).append("\"\n");
|
||||||
|
buffer.append(" signResponse=\"").append(Boolean.toString(samlClient.requiresClientSignature())).append("\"\n");
|
||||||
|
buffer.append(" validateRequestSignature=\"").append(Boolean.toString(samlClient.requiresRealmSignature())).append("\"\n");
|
||||||
|
buffer.append(" validateResponseSignature=\"").append(Boolean.toString(samlClient.requiresRealmSignature())).append("\"\n");
|
||||||
|
buffer.append(" requestBinding=\"POST\"\n");
|
||||||
|
buffer.append(" responseBinding=\"POST\"\n");
|
||||||
|
buffer.append(" postBindingUrl=\"").append(bindingUrl).append("\"\n");
|
||||||
|
buffer.append(" redirectBindingUrl=\"").append(bindingUrl).append("\"");
|
||||||
|
buffer.append("/>\n");
|
||||||
|
if (samlClient.requiresRealmSignature()) {
|
||||||
|
buffer.append(" <Keys>\n");
|
||||||
|
buffer.append(" <Key signing=\"true\">\n");
|
||||||
|
buffer.append(" <CertificatePem>\n");
|
||||||
|
buffer.append(" ").append(realm.getCertificatePem()).append("\n");
|
||||||
|
buffer.append(" </CertificatePem>\n");
|
||||||
|
buffer.append(" </Key>\n");
|
||||||
|
buffer.append(" </Keys>\n");
|
||||||
|
}
|
||||||
|
buffer.append(" </IDP>\n");
|
||||||
|
buffer.append(" </SP>\n");
|
||||||
|
buffer.append("</keycloak-saml-adapter>\n");
|
||||||
|
return Response.ok(buffer.toString(), MediaType.TEXT_PLAIN_TYPE).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProtocol() {
|
||||||
|
return SamlProtocol.LOGIN_PROTOCOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayType() {
|
||||||
|
return "Keycloak SAML Adapter keycloak-saml.xml";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return "Keycloak SAML adapter configuration file. Put this in WEB-INF directory if your WAR.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFilename() {
|
||||||
|
return "keycloak-saml.xml";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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 "keycloak-saml";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
org.keycloak.protocol.saml.installation.KeycloakSamlClientInstallation
|
|
@ -57,6 +57,10 @@ public class RealmsResource {
|
||||||
return uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(RealmsResource.class, "getProtocol");
|
return uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(RealmsResource.class, "getProtocol");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static UriBuilder protocolUrl(UriBuilder builder) {
|
||||||
|
return builder.path(RealmsResource.class).path(RealmsResource.class, "getProtocol");
|
||||||
|
}
|
||||||
|
|
||||||
public static UriBuilder clientRegistrationUrl(UriInfo uriInfo) {
|
public static UriBuilder clientRegistrationUrl(UriInfo uriInfo) {
|
||||||
return uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(RealmsResource.class, "getClientsService");
|
return uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(RealmsResource.class, "getClientsService");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue