keycloak-aplcache
Changes
dependencies/server-all/pom.xml 11(+11 -0)
pom.xml 8(+7 -1)
saml/pom.xml 20(+20 -0)
saml/saml-core/pom.xml 55(+55 -0)
saml/saml-protocol/pom.xml 128(+128 -0)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2PostBindingResponseBuilder.java 320(+320 -0)
Details
dependencies/server-all/pom.xml 11(+11 -0)
diff --git a/dependencies/server-all/pom.xml b/dependencies/server-all/pom.xml
index 0f7fed8..a812b08 100755
--- a/dependencies/server-all/pom.xml
+++ b/dependencies/server-all/pom.xml
@@ -138,6 +138,17 @@
<version>${project.version}</version>
</dependency>
+ <!-- saml -->
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-protocol</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.picketlink</groupId>
+ <artifactId>picketlink-federation</artifactId>
+ </dependency>
+
<!-- mongo -->
<dependency>
<groupId>org.keycloak</groupId>
pom.xml 8(+7 -1)
diff --git a/pom.xml b/pom.xml
index 55f5369..73523c0 100755
--- a/pom.xml
+++ b/pom.xml
@@ -18,7 +18,7 @@
<resteasy.version>2.3.7.Final</resteasy.version>
<resteasy.version.latest>3.0.9.Final</resteasy.version.latest>
<undertow.version>1.0.15.Final</undertow.version>
- <picketlink.version>2.7.0.Beta1</picketlink.version>
+ <picketlink.version>2.7.0.CR1-20140924</picketlink.version>
<picketbox.ldap.version>1.0.2.Final</picketbox.ldap.version>
<mongo.driver.version>2.11.3</mongo.driver.version>
<jboss.logging.version>3.1.4.GA</jboss.logging.version>
@@ -107,6 +107,7 @@
<module>picketlink</module>
<module>federation</module>
<module>services</module>
+ <module>saml</module>
<module>social</module>
<module>forms</module>
<module>examples</module>
@@ -231,6 +232,11 @@
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
+ <artifactId>picketlink-federation</artifactId>
+ <version>${picketlink.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.picketlink</groupId>
<artifactId>picketlink-idm-simple-schema</artifactId>
<version>${picketlink.version}</version>
</dependency>
saml/pom.xml 20(+20 -0)
diff --git a/saml/pom.xml b/saml/pom.xml
new file mode 100755
index 0000000..4e7a81f
--- /dev/null
+++ b/saml/pom.xml
@@ -0,0 +1,20 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.1.0-Alpha1-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+ <name>Keycloak SAML Integration</name>
+ <description/>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-saml-pom</artifactId>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>saml-core</module>
+ <module>saml-protocol</module>
+ </modules>
+</project>
saml/saml-core/pom.xml 55(+55 -0)
diff --git a/saml/saml-core/pom.xml b/saml/saml-core/pom.xml
new file mode 100755
index 0000000..87b0250
--- /dev/null
+++ b/saml/saml-core/pom.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.1.0-Alpha1-SNAPSHOT</version>
+ <relativePath>../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-saml-core</artifactId>
+ <name>Keycloak SAML Core</name>
+ <description/>
+
+ <properties>
+ <timestamp>${maven.build.timestamp}</timestamp>
+ <maven.build.timestamp.format>yyyy-MM-dd HH:mm</maven.build.timestamp.format>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>org.picketlink</groupId>
+ <artifactId>picketlink-federation</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>jaxrs-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <resources>
+ <resource>
+ <directory>src/main/resources</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>${maven.compiler.source}</source>
+ <target>${maven.compiler.target}</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
saml/saml-protocol/pom.xml 128(+128 -0)
diff --git a/saml/saml-protocol/pom.xml b/saml/saml-protocol/pom.xml
new file mode 100755
index 0000000..ea64438
--- /dev/null
+++ b/saml/saml-protocol/pom.xml
@@ -0,0 +1,128 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.1.0-Alpha1-SNAPSHOT</version>
+ <relativePath>../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-saml-protocol</artifactId>
+ <name>Keycloak SAML Protocol</name>
+ <description/>
+
+ <properties>
+ <timestamp>${maven.build.timestamp}</timestamp>
+ <maven.build.timestamp.format>yyyy-MM-dd HH:mm</maven.build.timestamp.format>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-services</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-forms-common-freemarker</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-events-api</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-account-api</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-email-api</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-login-api</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-api</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>resteasy-jaxrs</artifactId>
+ <scope>provided</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.picketlink</groupId>
+ <artifactId>picketlink-federation</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>jaxrs-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <resources>
+ <resource>
+ <directory>src/main/resources</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>${maven.compiler.source}</source>
+ <target>${maven.compiler.target}</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2PostBindingResponseBuilder.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2PostBindingResponseBuilder.java
new file mode 100755
index 0000000..0d7d4f5
--- /dev/null
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2PostBindingResponseBuilder.java
@@ -0,0 +1,320 @@
+package org.keycloak.protocol.saml;
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+import org.picketlink.common.PicketLinkLogger;
+import org.picketlink.common.PicketLinkLoggerFactory;
+import org.picketlink.common.constants.GeneralConstants;
+import org.picketlink.common.constants.JBossSAMLURIConstants;
+import org.picketlink.common.exceptions.ConfigurationException;
+import org.picketlink.common.exceptions.ProcessingException;
+import org.picketlink.common.util.DocumentUtil;
+import org.picketlink.identity.federation.api.saml.v2.response.SAML2Response;
+import org.picketlink.identity.federation.api.saml.v2.sig.SAML2Signature;
+import org.picketlink.identity.federation.core.saml.v2.common.IDGenerator;
+import org.picketlink.identity.federation.core.saml.v2.factories.JBossSAMLAuthnResponseFactory;
+import org.picketlink.identity.federation.core.saml.v2.holders.IDPInfoHolder;
+import org.picketlink.identity.federation.core.saml.v2.holders.IssuerInfoHolder;
+import org.picketlink.identity.federation.core.saml.v2.holders.SPInfoHolder;
+import org.picketlink.identity.federation.core.saml.v2.util.StatementUtil;
+import org.picketlink.identity.federation.core.saml.v2.util.XMLTimeUtil;
+import org.picketlink.identity.federation.saml.v2.assertion.AssertionType;
+import org.picketlink.identity.federation.saml.v2.assertion.AttributeStatementType;
+import org.picketlink.identity.federation.saml.v2.assertion.AuthnStatementType;
+import org.picketlink.identity.federation.saml.v2.protocol.ResponseType;
+import org.picketlink.identity.federation.web.util.PostBindingUtil;
+import org.w3c.dom.Document;
+
+import javax.ws.rs.core.CacheControl;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import static org.picketlink.common.util.StringUtil.isNotNull;
+
+/**
+ * <p> Handles for dealing with SAML2 Authentication </p>
+ * <p/>
+ * Configuration Options:
+ *
+ * @author Anil.Saldhana@redhat.com
+*/
+public class SAML2PostBindingResponseBuilder {
+ protected static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
+
+ protected List<String> roles = new LinkedList<String>();
+ protected String userPrincipal;
+ protected boolean multiValuedRoles;
+ protected boolean disableAuthnStatement;
+ protected String requestID;
+ protected String responseIssuer;
+ protected String authMethod;
+ protected String relayState;
+ protected String destination;
+ protected String requestIssuer;
+ protected Map<String, Object> attributes = new HashMap<String, Object>();
+
+
+ public SAML2PostBindingResponseBuilder attributes(Map<String, Object> attributes) {
+ this.attributes = attributes;
+ return this;
+ }
+
+ public SAML2PostBindingResponseBuilder attribute(String name, Object value) {
+ this.attributes.put(name, value);
+ return this;
+ }
+
+ public SAML2PostBindingResponseBuilder requestID(String requestID) {
+ this.requestID =requestID;
+ return this;
+ }
+
+ public SAML2PostBindingResponseBuilder requestIssuer(String requestIssuer) {
+ this.requestIssuer =requestIssuer;
+ return this;
+ }
+
+ public SAML2PostBindingResponseBuilder responseIssuer(String issuer) {
+ this.responseIssuer = issuer;
+ return this;
+ }
+
+ public SAML2PostBindingResponseBuilder roles(List<String> roles) {
+ this.roles = roles;
+ return this;
+ }
+
+ public SAML2PostBindingResponseBuilder roles(String... roles) {
+ for (String role : roles) {
+ this.roles.add(role);
+ }
+ return this;
+ }
+
+ public SAML2PostBindingResponseBuilder authMethod(String authMethod) {
+ this.authMethod = authMethod;
+ return this;
+ }
+
+ public SAML2PostBindingResponseBuilder userPrincipal(String userPrincipal) {
+ this.userPrincipal = userPrincipal;
+ return this;
+ }
+
+ public SAML2PostBindingResponseBuilder relayState(String relayState) {
+ this.relayState = relayState;
+ return this;
+ }
+
+ public SAML2PostBindingResponseBuilder destination(String destination) {
+ this.destination = destination;
+ return this;
+ }
+
+ public SAML2PostBindingResponseBuilder multiValuedRoles(boolean multiValuedRoles) {
+ this.multiValuedRoles = multiValuedRoles;
+ return this;
+ }
+
+ public SAML2PostBindingResponseBuilder disableAuthnStatement(boolean disableAuthnStatement) {
+ this.disableAuthnStatement = disableAuthnStatement;
+ return this;
+ }
+
+ public Response error(String status) throws ConfigurationException, ProcessingException, IOException {
+ Document doc = getErrorResponse(status);
+ return buildResponse(doc);
+
+
+ }
+
+ public Document getErrorResponse(String status) {
+ Document samlResponse = null;
+ ResponseType responseType = null;
+
+ SAML2Response saml2Response = new SAML2Response();
+
+ // Create a response type
+ String id = IDGenerator.create("ID_");
+
+ IssuerInfoHolder issuerHolder = new IssuerInfoHolder(responseIssuer);
+ issuerHolder.setStatusCode(status);
+
+ IDPInfoHolder idp = new IDPInfoHolder();
+ idp.setNameIDFormatValue(null);
+ idp.setNameIDFormat(JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get());
+
+ SPInfoHolder sp = new SPInfoHolder();
+ sp.setResponseDestinationURI(destination);
+
+ responseType = saml2Response.createResponseType(id);
+ responseType.setStatus(JBossSAMLAuthnResponseFactory.createStatusTypeForResponder(status));
+ responseType.setDestination(destination);
+
+ // Lets see how the response looks like
+ if (logger.isTraceEnabled()) {
+ StringWriter sw = new StringWriter();
+ try {
+ saml2Response.marshall(responseType, sw);
+ } catch (ProcessingException e) {
+ logger.trace(e);
+ }
+ logger.trace("SAML Response Document: " + sw.toString());
+ }
+
+ /*
+ if (supportSignature) {
+ try {
+ SAML2Signature ss = new SAML2Signature();
+ samlResponse = ss.sign(responseType, keyManager.getSigningKeyPair());
+ } catch (Exception e) {
+ logger.trace(e);
+ throw new RuntimeException(logger.signatureError(e));
+ }
+ } else
+ try {
+ samlResponse = saml2Response.convert(responseType);
+ } catch (Exception e) {
+ logger.trace(e);
+ }
+ */
+
+ return samlResponse;
+ }
+
+ public Response build() throws ConfigurationException, ProcessingException, IOException {
+ Document responseDoc = getResponse();
+ return buildResponse(responseDoc);
+ }
+
+ protected Response buildResponse(Document responseDoc) throws ProcessingException, ConfigurationException, IOException {
+ byte[] responseBytes = DocumentUtil.getDocumentAsString(responseDoc).getBytes("UTF-8");
+ String samlResponse = PostBindingUtil.base64Encode(new String(responseBytes));
+
+ if (destination == null) {
+ throw logger.nullValueError("Destination is null");
+ }
+
+ StringBuilder builder = new StringBuilder();
+
+ String key = GeneralConstants.SAML_RESPONSE_KEY;
+ builder.append("<HTML>");
+ builder.append("<HEAD>");
+
+ builder.append("<TITLE>HTTP Post Binding Response (Response)</TITLE>");
+ builder.append("</HEAD>");
+ builder.append("<BODY Onload=\"document.forms[0].submit()\">");
+
+ builder.append("<FORM METHOD=\"POST\" ACTION=\"" + destination + "\">");
+ builder.append("<INPUT TYPE=\"HIDDEN\" NAME=\"" + key + "\"" + " VALUE=\"" + samlResponse + "\"/>");
+
+ if (isNotNull(relayState)) {
+ builder.append("<INPUT TYPE=\"HIDDEN\" NAME=\"RelayState\" " + "VALUE=\"" + relayState + "\"/>");
+ }
+
+ builder.append("<NOSCRIPT>");
+ builder.append("<P>JavaScript is disabled. We strongly recommend to enable it. Click the button below to continue.</P>");
+ builder.append("<INPUT TYPE=\"SUBMIT\" VALUE=\"CONTINUE\" />");
+ builder.append("</NOSCRIPT>");
+
+ builder.append("</FORM></BODY></HTML>");
+
+ String str = builder.toString();
+
+ CacheControl cacheControl = new CacheControl();
+ cacheControl.setNoCache(true);
+ return Response.ok(str, MediaType.TEXT_HTML_TYPE)
+ .header("Pragma", "no-cache")
+ .header("Cache-Control", "no-cache, no-store").build();
+ }
+
+ public Document getResponse() throws ConfigurationException, ProcessingException {
+
+ Document samlResponseDocument = null;
+
+ ResponseType responseType = null;
+
+ SAML2Response saml2Response = new SAML2Response();
+
+ // Create a response type
+ String id = IDGenerator.create("ID_");
+
+ IssuerInfoHolder issuerHolder = new IssuerInfoHolder(responseIssuer);
+ issuerHolder.setStatusCode(JBossSAMLURIConstants.STATUS_SUCCESS.get());
+
+ IDPInfoHolder idp = new IDPInfoHolder();
+ idp.setNameIDFormatValue(userPrincipal);
+ idp.setNameIDFormat(JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get());
+
+ SPInfoHolder sp = new SPInfoHolder();
+ sp.setResponseDestinationURI(destination);
+ sp.setRequestID(requestID);
+ sp.setIssuer(requestIssuer);
+ responseType = saml2Response.createResponseType(id, sp, idp, issuerHolder);
+
+ // Add information on the roles
+ AssertionType assertion = responseType.getAssertions().get(0).getAssertion();
+
+ // Create an AuthnStatementType
+ if (!disableAuthnStatement) {
+ String authContextRef = JBossSAMLURIConstants.AC_PASSWORD.get();
+ if (isNotNull(authMethod))
+ authContextRef = authMethod;
+
+ AuthnStatementType authnStatement = StatementUtil.createAuthnStatement(XMLTimeUtil.getIssueInstant(),
+ authContextRef);
+
+ authnStatement.setSessionIndex(assertion.getID());
+
+ assertion.addStatement(authnStatement);
+ }
+
+ if (roles != null && !roles.isEmpty()) {
+ AttributeStatementType attrStatement = StatementUtil.createAttributeStatementForRoles(roles, multiValuedRoles);
+ assertion.addStatement(attrStatement);
+ }
+
+ // Add in the attributes information
+ if (attributes != null && attributes.size() > 0) {
+ AttributeStatementType attStatement = StatementUtil.createAttributeStatement(attributes);
+ assertion.addStatement(attStatement);
+ }
+
+ try {
+ samlResponseDocument = saml2Response.convert(responseType);
+
+ if (logger.isTraceEnabled()) {
+ logger.trace("SAML Response Document: " + DocumentUtil.asString(samlResponseDocument));
+ }
+ } catch (Exception e) {
+ throw logger.samlAssertionMarshallError(e);
+ }
+
+ return samlResponseDocument;
+ }
+}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlLogin.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlLogin.java
new file mode 100755
index 0000000..8344170
--- /dev/null
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlLogin.java
@@ -0,0 +1,170 @@
+package org.keycloak.protocol.saml;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.ClientConnection;
+import org.keycloak.models.ClaimMask;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.protocol.LoginProtocol;
+import org.keycloak.services.managers.ClientSessionCode;
+import org.keycloak.services.resources.RealmsResource;
+import org.keycloak.services.resources.flows.Flows;
+import org.picketlink.common.constants.GeneralConstants;
+import org.picketlink.common.constants.JBossSAMLURIConstants;
+import org.picketlink.common.exceptions.ConfigurationException;
+import org.picketlink.common.exceptions.ProcessingException;
+import org.picketlink.identity.federation.core.saml.v2.constants.X500SAMLProfileConstants;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlLogin implements LoginProtocol {
+ protected static final Logger logger = Logger.getLogger(SamlLogin.class);
+ public static final String LOGIN_PROTOCOL = "saml";
+ public static final String SAML_BINDING = "saml_binding";
+ public static final String SAML_POST_BINDING = "post";
+
+ protected KeycloakSession session;
+
+ protected RealmModel realm;
+
+ protected HttpRequest request;
+
+ protected UriInfo uriInfo;
+
+ protected ClientConnection clientConnection;
+
+
+ @Override
+ public SamlLogin setSession(KeycloakSession session) {
+ this.session = session;
+ return this;
+ }
+
+ @Override
+ public SamlLogin setRealm(RealmModel realm) {
+ this.realm = realm;
+ return this;
+ }
+
+ @Override
+ public SamlLogin setRequest(HttpRequest request) {
+ this.request = request;
+ return this;
+ }
+
+ @Override
+ public SamlLogin setUriInfo(UriInfo uriInfo) {
+ this.uriInfo = uriInfo;
+ return this;
+ }
+
+ @Override
+ public SamlLogin setClientConnection(ClientConnection clientConnection) {
+ this.clientConnection = clientConnection;
+ return this;
+ }
+
+ @Override
+ public Response cancelLogin(ClientSessionModel clientSession) {
+ return getErrorResponse(clientSession, JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get());
+ }
+
+ @Override
+ public Response invalidSessionError(ClientSessionModel clientSession) {
+ return getErrorResponse(clientSession, JBossSAMLURIConstants.STATUS_AUTHNFAILED.get());
+ }
+
+ protected String getResponseIssuer(RealmModel realm) {
+ return RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString();
+ }
+
+ protected Response getErrorResponse(ClientSessionModel clientSession, String status) {
+ String relayState = clientSession.getNote(GeneralConstants.RELAY_STATE);
+ String redirectUri = clientSession.getRedirectUri();
+ SAML2PostBindingResponseBuilder builder = new SAML2PostBindingResponseBuilder();
+ String responseIssuer = getResponseIssuer(realm);
+ builder .relayState(relayState)
+ .destination(redirectUri)
+ .responseIssuer(responseIssuer)
+ .requestIssuer(clientSession.getClient().getClientId());
+ try {
+ return builder.error(status);
+ } catch (Exception e) {
+ return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response");
+ }
+ }
+
+ @Override
+ public Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode) {
+ ClientSessionModel clientSession = accessCode.getClientSession();
+ if (SamlLogin.SAML_POST_BINDING.equals(clientSession.getNote(SamlLogin.SAML_BINDING))) {
+ return postBinding(userSession, clientSession);
+ }
+ throw new RuntimeException("still need to implement redirect binding");
+ }
+
+ protected Response postBinding(UserSessionModel userSession, ClientSessionModel clientSession) {
+ String requestID = clientSession.getNote("REQUEST_ID");
+ String relayState = clientSession.getNote(GeneralConstants.RELAY_STATE);
+ String redirectUri = clientSession.getRedirectUri();
+ String responseIssuer = getResponseIssuer(realm);
+
+ SAML2PostBindingResponseBuilder builder = new SAML2PostBindingResponseBuilder();
+ builder.requestID(requestID)
+ .relayState(relayState)
+ .destination(redirectUri)
+ .responseIssuer(responseIssuer)
+ .requestIssuer(clientSession.getClient().getClientId())
+ .userPrincipal(userSession.getUser().getUsername()) // todo userId instead? There is no username claim it seems
+ .attribute(X500SAMLProfileConstants.USERID.getFriendlyName(), userSession.getUser().getId())
+ .authMethod(JBossSAMLURIConstants.AC_UNSPECIFIED.get());
+ initClaims(builder, clientSession.getClient(), userSession.getUser());
+ if (clientSession.getRoles() != null) {
+ for (String roleId : clientSession.getRoles()) {
+ // todo need a role mapping
+ RoleModel roleModel = clientSession.getRealm().getRoleById(roleId);
+ builder.roles(roleModel.getName());
+ }
+ }
+
+ try {
+ return builder.build();
+ } catch (Exception e) {
+ logger.error("failed", e);
+ return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response");
+ }
+ }
+
+ public void initClaims(SAML2PostBindingResponseBuilder builder, ClientModel model, UserModel user) {
+ if (ClaimMask.hasEmail(model.getAllowedClaimsMask())) {
+ builder.attribute(X500SAMLProfileConstants.EMAIL_ADDRESS.getFriendlyName(), user.getEmail());
+ }
+ if (ClaimMask.hasName(model.getAllowedClaimsMask())) {
+ builder.attribute(X500SAMLProfileConstants.GIVEN_NAME.getFriendlyName(), user.getFirstName());
+ builder.attribute(X500SAMLProfileConstants.SURNAME.getFriendlyName(), user.getLastName());
+ }
+ }
+
+
+ @Override
+ public Response consentDenied(ClientSessionModel clientSession) {
+ return getErrorResponse(clientSession, JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get());
+ }
+
+ @Override
+ public void close() {
+
+ }
+}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlLoginFactory.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlLoginFactory.java
new file mode 100755
index 0000000..21618ed
--- /dev/null
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlLoginFactory.java
@@ -0,0 +1,43 @@
+package org.keycloak.protocol.saml;
+
+import org.keycloak.Config;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.protocol.LoginProtocol;
+import org.keycloak.protocol.LoginProtocolFactory;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.picketlink.identity.federation.core.sts.PicketLinkCoreSTS;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlLoginFactory implements LoginProtocolFactory {
+
+ @Override
+ public Object createProtocolEndpoint(RealmModel realm, EventBuilder event, AuthenticationManager authManager) {
+ return new SamlService(realm, event, authManager);
+ }
+
+ @Override
+ public LoginProtocol create(KeycloakSession session) {
+ return new SamlLogin().setSession(session);
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+ PicketLinkCoreSTS sts = PicketLinkCoreSTS.instance();
+ sts.installDefaultConfiguration();
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public String getId() {
+ return "saml";
+ }
+}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAMLRequestParser.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAMLRequestParser.java
new file mode 100755
index 0000000..4d5b6d4
--- /dev/null
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAMLRequestParser.java
@@ -0,0 +1,48 @@
+package org.keycloak.protocol.saml;
+
+import org.picketlink.common.PicketLinkLogger;
+import org.picketlink.common.PicketLinkLoggerFactory;
+import org.picketlink.identity.federation.api.saml.v2.request.SAML2Request;
+import org.picketlink.identity.federation.core.saml.v2.common.SAMLDocumentHolder;
+import org.picketlink.identity.federation.web.util.PostBindingUtil;
+import org.picketlink.identity.federation.web.util.RedirectBindingUtil;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SAMLRequestParser {
+ private static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
+
+ public static SAMLDocumentHolder parseRedirectBinding(String samlMessage) {
+ InputStream is;
+ is = RedirectBindingUtil.base64DeflateDecode(samlMessage);
+ SAML2Request saml2Request = new SAML2Request();
+ try {
+ saml2Request.getSAML2ObjectFromStream(is);
+ return saml2Request.getSamlDocumentHolder();
+ } catch (Exception e) {
+ logger.samlBase64DecodingError(e);
+ }
+ return null;
+
+ }
+
+ public static SAMLDocumentHolder parsePostBinding(String samlMessage) {
+ InputStream is;
+ byte[] samlBytes = PostBindingUtil.base64Decode(samlMessage);
+ is = new ByteArrayInputStream(samlBytes);
+ SAML2Request saml2Request = new SAML2Request();
+ try {
+ saml2Request.getSAML2ObjectFromStream(is);
+ return saml2Request.getSamlDocumentHolder();
+ } catch (Exception e) {
+ logger.samlBase64DecodingError(e);
+ }
+ return null;
+
+ }
+}
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
new file mode 100755
index 0000000..a5ed37d
--- /dev/null
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -0,0 +1,246 @@
+package org.keycloak.protocol.saml;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
+import org.jboss.resteasy.spi.HttpRequest;
+import org.jboss.resteasy.spi.HttpResponse;
+import org.keycloak.ClientConnection;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
+import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.protocol.oidc.OpenIDConnectService;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.ClientSessionCode;
+import org.keycloak.services.resources.flows.Flows;
+import org.picketlink.common.constants.GeneralConstants;
+import org.picketlink.identity.federation.core.saml.v2.common.SAMLDocumentHolder;
+import org.picketlink.identity.federation.saml.v2.SAML2Object;
+import org.picketlink.identity.federation.saml.v2.protocol.AuthnRequestType;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Cookie;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import javax.ws.rs.ext.Providers;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Resource class for the oauth/openid connect token service
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlService {
+
+ protected static final Logger logger = Logger.getLogger(SamlService.class);
+
+ protected RealmModel realm;
+ private EventBuilder event;
+ protected AuthenticationManager authManager;
+
+ @Context
+ protected Providers providers;
+ @Context
+ protected SecurityContext securityContext;
+ @Context
+ protected UriInfo uriInfo;
+ @Context
+ protected HttpHeaders headers;
+ @Context
+ protected HttpRequest request;
+ @Context
+ protected HttpResponse response;
+ @Context
+ protected KeycloakSession session;
+ @Context
+ protected ClientConnection clientConnection;
+
+ /*
+ @Context
+ protected ResourceContext resourceContext;
+ */
+
+ public SamlService(RealmModel realm, EventBuilder event, AuthenticationManager authManager) {
+ this.realm = realm;
+ this.event = event;
+ this.authManager = authManager;
+ }
+
+ /**
+ */
+ @Path("POST")
+ @POST
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+ public Response loginPage(@FormParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
+ @FormParam(GeneralConstants.RELAY_STATE) String relayState) {
+ event.event(EventType.LOGIN);
+ if (!checkSsl()) {
+ event.error(Errors.SSL_REQUIRED);
+ return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required");
+ }
+ if (!realm.isEnabled()) {
+ event.error(Errors.REALM_DISABLED);
+ return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled");
+ }
+
+ if (samlRequest == null) {
+ event.error(Errors.INVALID_TOKEN);
+ return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
+
+ }
+
+ SAMLDocumentHolder documentHolder = SAMLRequestParser.parsePostBinding(samlRequest);
+ if (documentHolder == null) {
+ event.error(Errors.INVALID_TOKEN);
+ return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
+ }
+
+ SAML2Object samlObject = documentHolder.getSamlObject();
+ if (!(samlObject instanceof AuthnRequestType)) {
+ event.error(Errors.INVALID_TOKEN);
+ return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
+ }
+
+ // Get the SAML Request Message
+ AuthnRequestType requestAbstractType = (AuthnRequestType) samlObject;
+ String issuer = requestAbstractType.getIssuer().getValue();
+ ClientModel client = realm.findClient(issuer);
+
+ if (client == null) {
+ event.error(Errors.CLIENT_NOT_FOUND);
+ return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester.");
+ }
+
+ if (!client.isEnabled()) {
+ event.error(Errors.CLIENT_DISABLED);
+ return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled.");
+ }
+ if ((client instanceof ApplicationModel) && ((ApplicationModel)client).isBearerOnly()) {
+ event.error(Errors.NOT_ALLOWED);
+ return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Bearer-only applications are not allowed to initiate browser login");
+ }
+ if (client.isDirectGrantsOnly()) {
+ event.error(Errors.NOT_ALLOWED);
+ return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "direct-grants-only clients are not allowed to initiate browser login");
+ }
+
+ URI redirectUri = requestAbstractType.getAssertionConsumerServiceURL();
+ String redirect = OpenIDConnectService.verifyRedirectUri(uriInfo, redirectUri.toString(), realm, client);
+
+ if (redirect == null) {
+ event.error(Errors.INVALID_REDIRECT_URI);
+ return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect_uri.");
+ }
+
+
+ ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
+ clientSession.setAuthMethod(SamlLogin.LOGIN_PROTOCOL);
+ clientSession.setRedirectUri(redirect);
+ clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
+ clientSession.setNote(SamlLogin.SAML_BINDING, SamlLogin.SAML_POST_BINDING);
+ clientSession.setNote(GeneralConstants.RELAY_STATE, relayState);
+ clientSession.setNote("REQUEST_ID", requestAbstractType.getID());
+
+ Response response = authManager.checkNonFormAuthentication(session, clientSession, realm, uriInfo, request, clientConnection, headers, event);
+ if (response != null) return response;
+
+ LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo)
+ .setClientSessionCode(new ClientSessionCode(realm, clientSession).getCode());
+
+ String rememberMeUsername = null;
+ if (realm.isRememberMe()) {
+ Cookie rememberMeCookie = headers.getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
+ if (rememberMeCookie != null && !"".equals(rememberMeCookie.getValue())) {
+ rememberMeUsername = rememberMeCookie.getValue();
+ }
+ }
+
+ if (rememberMeUsername != null) {
+ MultivaluedMap<String, String> formData = new MultivaluedMapImpl<String, String>();
+ formData.add(AuthenticationManager.FORM_USERNAME, rememberMeUsername);
+ formData.add("rememberMe", "on");
+
+ forms.setFormData(formData);
+ }
+
+ return forms.createLogin();
+ }
+
+
+ /**
+ * Logout user session. User must be logged in via a session cookie.
+ *
+ * @param redirectUri
+ * @return
+ */
+ @Path("logout")
+ @GET
+ @NoCache
+ public Response logout(final @QueryParam("shit") String redirectUri) {
+ event.event(EventType.LOGOUT);
+ if (redirectUri != null) {
+ event.detail(Details.REDIRECT_URI, redirectUri);
+ }
+ // authenticate identity cookie, but ignore an access token timeout as we're logging out anyways.
+ AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers, false);
+ if (authResult != null) {
+ logout(authResult.getSession());
+ }
+
+ if (redirectUri != null) {
+ String validatedRedirect = OpenIDConnectService.verifyRealmRedirectUri(uriInfo, redirectUri, realm);
+ if (validatedRedirect == null) {
+ return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect uri.");
+ }
+ return Response.status(302).location(UriBuilder.fromUri(validatedRedirect).build()).build();
+ } else {
+ return Response.ok().build();
+ }
+ }
+
+ private void logout(UserSessionModel userSession) {
+ authManager.logout(session, realm, userSession, uriInfo, clientConnection);
+ event.user(userSession.getUser()).session(userSession).success();
+ }
+
+ private boolean checkSsl() {
+ if (uriInfo.getBaseUri().getScheme().equals("https")) {
+ return true;
+ } else {
+ return !realm.getSslRequired().isRequired(clientConnection);
+ }
+ }
+
+ private Response createError(String error, String errorDescription, Response.Status status) {
+ Map<String, String> e = new HashMap<String, String>();
+ e.put(OAuth2Constants.ERROR, error);
+ if (errorDescription != null) {
+ e.put(OAuth2Constants.ERROR_DESCRIPTION, errorDescription);
+ }
+ return Response.status(status).entity(e).type("application/json").build();
+ }
+
+}
diff --git a/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.LoginProtocolFactory b/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.LoginProtocolFactory
new file mode 100755
index 0000000..632a1db
--- /dev/null
+++ b/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.LoginProtocolFactory
@@ -0,0 +1 @@
+org.keycloak.protocol.saml.SamlLoginFactory
\ No newline at end of file
diff --git a/server/src/main/webapp/WEB-INF/jboss-deployment-structure.xml b/server/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
index 513bf8c..f995611 100755
--- a/server/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
+++ b/server/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
@@ -16,5 +16,8 @@
<module name="org.keycloak.keycloak-undertow-adapter" />
<module name="org.keycloak.keycloak-as7-adapter" />
</exclusions>
+ <exclude-subsystems>
+ <subsystem name="webservices"/>
+ </exclude-subsystems>
</deployment>
</jboss-deployment-structure>
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/protocol/LoginProtocol.java b/services/src/main/java/org/keycloak/protocol/LoginProtocol.java
index 8a66f16..86de30a 100755
--- a/services/src/main/java/org/keycloak/protocol/LoginProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/LoginProtocol.java
@@ -18,15 +18,15 @@ import javax.ws.rs.core.UriInfo;
* @version $Revision: 1 $
*/
public interface LoginProtocol extends Provider {
- OpenIDConnect setSession(KeycloakSession session);
+ LoginProtocol setSession(KeycloakSession session);
- OpenIDConnect setRealm(RealmModel realm);
+ LoginProtocol setRealm(RealmModel realm);
- OpenIDConnect setRequest(HttpRequest request);
+ LoginProtocol setRequest(HttpRequest request);
- OpenIDConnect setUriInfo(UriInfo uriInfo);
+ LoginProtocol setUriInfo(UriInfo uriInfo);
- OpenIDConnect setClientConnection(ClientConnection clientConnection);
+ LoginProtocol setClientConnection(ClientConnection clientConnection);
Response cancelLogin(ClientSessionModel clientSession);
Response invalidSessionError(ClientSessionModel clientSession);
diff --git a/testsuite/integration/src/test/resources/testsaml.json b/testsuite/integration/src/test/resources/testsaml.json
new file mode 100755
index 0000000..6dc9d71
--- /dev/null
+++ b/testsuite/integration/src/test/resources/testsaml.json
@@ -0,0 +1,48 @@
+{
+ "id": "demo",
+ "realm": "demo",
+ "enabled": true,
+ "sslRequired": "external",
+ "registrationAllowed": true,
+ "resetPasswordAllowed": true,
+ "passwordCredentialGrantAllowed": true,
+ "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
+ "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+ "requiredCredentials": [ "password" ],
+ "defaultRoles": [ "user" ],
+ "smtpServer": {
+ "from": "auto@keycloak.org",
+ "host": "localhost",
+ "port":"3025"
+ },
+ "users" : [
+ {
+ "username" : "bburke",
+ "enabled": true,
+ "email" : "bburke@redhat.com",
+ "credentials" : [
+ { "type" : "password",
+ "value" : "password" }
+ ],
+ "realmRoles": ["manager"]
+ }
+ ],
+ "applications": [
+ {
+ "name": "http://localhost:8080/sales-post/",
+ "enabled": true,
+ "fullScopeAllowed": true,
+ "redirectUris": [
+ "http://localhost:8080/sales-post/*"
+ ]
+ }
+ ],
+ "roles" : {
+ "realm" : [
+ {
+ "name": "manager",
+ "description": "Have Manager privileges"
+ }
+ ]
+ }
+}