Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Bill Burke 2017-05-08 13:49:03 -04:00
commit a8a8ea4bcd
48 changed files with 338 additions and 143 deletions

View file

@ -25,7 +25,7 @@ before_script:
- export MAVEN_SKIP_RC=true
install:
- travis_wait 60 mvn install --no-snapshot-updates -Pdistribution -DskipTests=true -B -V -q
- travis_wait 60 mvn install --no-snapshot-updates -Pdistribution -DskipTestsuite -B -V -q
script:
- ./travis-run-tests.sh $TESTS

View file

@ -41,10 +41,10 @@ To start Keycloak during development first build as specified above, then run:
mvn -f testsuite/integration/pom.xml exec:java -Pkeycloak-server
To start Keycloak from the appliance distribution first build the distribution it as specified above, then run:
To start Keycloak from the server distribution first build the distribution it as specified above, then run:
tar xfz distribution/appliance-dist/target/keycloak-appliance-dist-all-<VERSION>.tar.gz
cd keycloak-appliance-dist-all-<VERSION>/keycloak
tar xfz distribution/server-dist/target/keycloak-<VERSION>.tar.gz
cd keycloak-<VERSION>
bin/standalone.sh
To stop the server press `Ctrl + C`.

View file

@ -101,6 +101,7 @@ public class AuthenticatedActionsHandler {
if (!deployment.isCors()) return false;
KeycloakSecurityContext securityContext = facade.getSecurityContext();
String origin = facade.getRequest().getHeader(CorsHeaders.ORIGIN);
String exposeHeaders = deployment.getCorsExposedHeaders();
String requestOrigin = UriUtils.getOrigin(facade.getRequest().getURI());
log.debugv("Origin: {0} uri: {1}", origin, facade.getRequest().getURI());
if (securityContext != null && origin != null && !origin.equals(requestOrigin)) {
@ -124,6 +125,9 @@ public class AuthenticatedActionsHandler {
facade.getResponse().setStatus(200);
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
if (exposeHeaders != null) {
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, exposeHeaders);
}
} else {
log.debugv("cors validation not needed as we're not a secure session or origin header was null: {0}", facade.getRequest().getURI());
}

View file

@ -30,4 +30,5 @@ public interface CorsHeaders {
String ORIGIN = "Origin";
String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";
String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
}

View file

@ -73,6 +73,7 @@ public class KeycloakDeployment {
protected int corsMaxAge = -1;
protected String corsAllowedHeaders;
protected String corsAllowedMethods;
protected String corsExposedHeaders;
protected boolean exposeToken;
protected boolean alwaysRefreshToken;
protected boolean registerNodeAtStartup;
@ -325,6 +326,14 @@ public class KeycloakDeployment {
this.corsAllowedMethods = corsAllowedMethods;
}
public String getCorsExposedHeaders() {
return corsExposedHeaders;
}
public void setCorsExposedHeaders(String corsExposedHeaders) {
this.corsExposedHeaders = corsExposedHeaders;
}
public boolean isExposeToken() {
return exposeToken;
}

View file

@ -96,6 +96,7 @@ public class KeycloakDeploymentBuilder {
deployment.setCorsMaxAge(adapterConfig.getCorsMaxAge());
deployment.setCorsAllowedHeaders(adapterConfig.getCorsAllowedHeaders());
deployment.setCorsAllowedMethods(adapterConfig.getCorsAllowedMethods());
deployment.setCorsExposedHeaders(adapterConfig.getCorsExposedHeaders());
}
// https://tools.ietf.org/html/rfc7636

View file

@ -53,6 +53,7 @@ public class KeycloakDeploymentBuilderTest {
assertEquals(1000, deployment.getCorsMaxAge());
assertEquals("POST, PUT, DELETE, GET", deployment.getCorsAllowedMethods());
assertEquals("X-Custom, X-Custom2", deployment.getCorsAllowedHeaders());
assertEquals("X-Custom3, X-Custom4", deployment.getCorsExposedHeaders());
assertTrue(deployment.isBearerOnly());
assertTrue(deployment.isPublicClient());
assertTrue(deployment.isEnableBasicAuth());

View file

@ -9,6 +9,7 @@
"cors-max-age": 1000,
"cors-allowed-methods": "POST, PUT, DELETE, GET",
"cors-allowed-headers": "X-Custom, X-Custom2",
"cors-exposed-headers": "X-Custom3, X-Custom4",
"bearer-only": true,
"public-client": true,
"enable-basic-auth": true,

View file

@ -587,7 +587,7 @@
req.onreadystatechange = function () {
if (req.readyState == 4) {
if (req.status == 200) {
if (req.status == 200 || fileLoaded(req)) {
var config = JSON.parse(req.responseText);
kc.authServerUrl = config['auth-server-url'];
@ -633,6 +633,10 @@
return promise.promise;
}
function fileLoaded(xhr) {
return xhr.status == 0 && xhr.responseText && xhr.responseURL.startsWith('file:');
}
function setToken(token, refreshToken, idToken, timeLocal) {
if (kc.tokenTimeoutHandle) {
clearTimeout(kc.tokenTimeoutHandle);

View file

@ -124,6 +124,12 @@ public class SharedAttributeDefinitons {
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition CORS_EXPOSED_HEADERS =
new SimpleAttributeDefinitionBuilder("cors-exposed-headers", ModelType.STRING, true)
.setXmlName("cors-exposed-headers")
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition EXPOSE_TOKEN =
new SimpleAttributeDefinitionBuilder("expose-token", ModelType.BOOLEAN, true)
.setXmlName("expose-token")
@ -191,6 +197,7 @@ public class SharedAttributeDefinitons {
ATTRIBUTES.add(CORS_MAX_AGE);
ATTRIBUTES.add(CORS_ALLOWED_HEADERS);
ATTRIBUTES.add(CORS_ALLOWED_METHODS);
ATTRIBUTES.add(CORS_EXPOSED_HEADERS);
ATTRIBUTES.add(EXPOSE_TOKEN);
ATTRIBUTES.add(AUTH_SERVER_URL_FOR_BACKEND_REQUESTS);
ATTRIBUTES.add(ALWAYS_REFRESH_TOKEN);

View file

@ -39,6 +39,7 @@ keycloak.realm.client-key-password=n/a
keycloak.realm.cors-max-age=CORS max-age header
keycloak.realm.cors-allowed-headers=CORS allowed headers
keycloak.realm.cors-allowed-methods=CORS allowed methods
keycloak.realm.cors-exposed-headers=CORS exposed headers
keycloak.realm.expose-token=Enable secure URL that exposes access token
keycloak.realm.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
keycloak.realm.always-refresh-token=Refresh token on every single web request
@ -73,6 +74,7 @@ keycloak.secure-deployment.client-key-password=n/a
keycloak.secure-deployment.cors-max-age=CORS max-age header
keycloak.secure-deployment.cors-allowed-headers=CORS allowed headers
keycloak.secure-deployment.cors-allowed-methods=CORS allowed methods
keycloak.secure-deployment.cors-exposed-headers=CORS exposed headers
keycloak.secure-deployment.expose-token=Enable secure URL that exposes access token
keycloak.secure-deployment.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
keycloak.secure-deployment.always-refresh-token=Refresh token on every single web request

View file

@ -58,6 +58,7 @@
<xs:element name="disable-trust-manager" type="xs:boolean" minOccurs="0" maxOccurs="1" />
<xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="cors-exposed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
@ -88,6 +89,7 @@
<xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1" />
<xs:element name="cors-allowed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="cors-exposed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="resource" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="truststore" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="truststore-password" type="xs:string" minOccurs="0" maxOccurs="1"/>

View file

@ -124,6 +124,12 @@ public class SharedAttributeDefinitons {
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition CORS_EXPOSED_HEADERS =
new SimpleAttributeDefinitionBuilder("cors-exposed-headers", ModelType.STRING, true)
.setXmlName("cors-exposed-headers")
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition EXPOSE_TOKEN =
new SimpleAttributeDefinitionBuilder("expose-token", ModelType.BOOLEAN, true)
.setXmlName("expose-token")
@ -175,6 +181,8 @@ public class SharedAttributeDefinitons {
protected static final List<SimpleAttributeDefinition> ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
static {
ATTRIBUTES.add(REALM_PUBLIC_KEY);
@ -192,6 +200,7 @@ public class SharedAttributeDefinitons {
ATTRIBUTES.add(CORS_MAX_AGE);
ATTRIBUTES.add(CORS_ALLOWED_HEADERS);
ATTRIBUTES.add(CORS_ALLOWED_METHODS);
ATTRIBUTES.add(CORS_EXPOSED_HEADERS);
ATTRIBUTES.add(EXPOSE_TOKEN);
ATTRIBUTES.add(AUTH_SERVER_URL_FOR_BACKEND_REQUESTS);
ATTRIBUTES.add(ALWAYS_REFRESH_TOKEN);

View file

@ -39,6 +39,7 @@ keycloak.realm.client-key-password=n/a
keycloak.realm.cors-max-age=CORS max-age header
keycloak.realm.cors-allowed-headers=CORS allowed headers
keycloak.realm.cors-allowed-methods=CORS allowed methods
keycloak.realm.cors-exposed-headers=CORS exposed headers
keycloak.realm.expose-token=Enable secure URL that exposes access token
keycloak.realm.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
keycloak.realm.always-refresh-token=Refresh token on every single web request
@ -74,6 +75,7 @@ keycloak.secure-deployment.client-key-password=n/a
keycloak.secure-deployment.cors-max-age=CORS max-age header
keycloak.secure-deployment.cors-allowed-headers=CORS allowed headers
keycloak.secure-deployment.cors-allowed-methods=CORS allowed methods
keycloak.secure-deployment.cors-exposed-headers=CORS exposed headers
keycloak.secure-deployment.expose-token=Enable secure URL that exposes access token
keycloak.secure-deployment.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
keycloak.secure-deployment.always-refresh-token=Refresh token on every single web request

View file

@ -58,6 +58,7 @@
<xs:element name="disable-trust-manager" type="xs:boolean" minOccurs="0" maxOccurs="1" />
<xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="cors-exposed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
@ -88,6 +89,7 @@
<xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1" />
<xs:element name="cors-allowed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="cors-exposed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="resource" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="truststore" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="truststore-password" type="xs:string" minOccurs="0" maxOccurs="1"/>

View file

@ -29,7 +29,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
@JsonPropertyOrder({"realm", "realm-public-key", "auth-server-url", "ssl-required",
"resource", "public-client", "credentials",
"use-resource-role-mappings",
"enable-cors", "cors-max-age", "cors-allowed-methods",
"enable-cors", "cors-max-age", "cors-allowed-methods", "cors-exposed-headers",
"expose-token", "bearer-only", "autodetect-bearer-only",
"connection-pool-size",
"allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password",

View file

@ -33,7 +33,7 @@ import java.util.TreeMap;
@JsonPropertyOrder({"realm", "realm-public-key", "auth-server-url", "ssl-required",
"resource", "public-client", "credentials",
"use-resource-role-mappings",
"enable-cors", "cors-max-age", "cors-allowed-methods",
"enable-cors", "cors-max-age", "cors-allowed-methods", "cors-exposed-headers",
"expose-token", "bearer-only", "autodetect-bearer-only", "enable-basic-auth"})
public class BaseAdapterConfig extends BaseRealmConfig {
@JsonProperty("resource")
@ -48,6 +48,8 @@ public class BaseAdapterConfig extends BaseRealmConfig {
protected String corsAllowedHeaders;
@JsonProperty("cors-allowed-methods")
protected String corsAllowedMethods;
@JsonProperty("cors-exposed-headers")
protected String corsExposedHeaders;
@JsonProperty("expose-token")
protected boolean exposeToken;
@JsonProperty("bearer-only")
@ -110,6 +112,14 @@ public class BaseAdapterConfig extends BaseRealmConfig {
this.corsAllowedMethods = corsAllowedMethods;
}
public String getCorsExposedHeaders() {
return corsExposedHeaders;
}
public void setCorsExposedHeaders(String corsExposedHeaders) {
this.corsExposedHeaders = corsExposedHeaders;
}
public boolean isExposeToken() {
return exposeToken;
}

View file

@ -17,7 +17,7 @@
<build xmlns="urn:wildfly:feature-pack-build:1.0">
<dependencies>
<artifact name="org.wildfly:wildfly-feature-pack" />
<artifact name="${feature.parent}" />
</dependencies>
<config>
<standalone template="configuration/standalone/template.xml" subsystems="configuration/standalone/subsystems.xml" output-file="standalone/configuration/standalone.xml" />

View file

@ -61,12 +61,6 @@
<groupId>org.keycloak</groupId>
<artifactId>keycloak-authz-client</artifactId>
</dependency>
<dependency>
<groupId>org.wildfly</groupId>
<artifactId>wildfly-feature-pack</artifactId>
<type>zip</type>
</dependency>
</dependencies>
<build>
@ -119,4 +113,52 @@
</plugins>
</build>
<profiles>
<profile>
<id>community</id>
<activation>
<property>
<name>!product</name>
</property>
</activation>
<properties>
<build-tools.version>${wildfly.build-tools.version}</build-tools.version>
<feature.parent>org.wildfly:wildfly-feature-pack</feature.parent>
</properties>
<dependencies>
<dependency>
<groupId>org.wildfly</groupId>
<artifactId>wildfly-feature-pack</artifactId>
<type>zip</type>
</dependency>
</dependencies>
</profile>
<profile>
<id>product</id>
<activation>
<property>
<name>product</name>
</property>
</activation>
<properties>
<build-tools.version>${eap.build-tools.version}</build-tools.version>
<feature.parent>org.jboss.eap:wildfly-feature-pack</feature.parent>
</properties>
<dependencies>
<dependency>
<groupId>org.jboss.eap</groupId>
<artifactId>wildfly-feature-pack</artifactId>
<version>${eap.version}</version>
<type>zip</type>
</dependency>
</dependencies>
</profile>
</profiles>
</project>

View file

@ -20,7 +20,7 @@
<head>
<title>Authentication Example</title>
<meta http-equiv="Content-Security-Policy" content="default-src *; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval'">
<meta http-equiv="Content-Security-Policy" content="default-src * gap://ready; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval'">
<script type="text/javascript" charset="utf-8" src="cordova.js"></script>
<script type="text/javascript" charset="utf-8" src="keycloak.js"></script>

View file

@ -119,7 +119,7 @@ public class JpaUserCredentialStore implements UserCredentialStore {
entity.setUser(userRef);
em.persist(entity);
MultivaluedHashMap<String, String> config = cred.getConfig();
if (config != null || !config.isEmpty()) {
if (config != null && !config.isEmpty()) {
for (String key : config.keySet()) {
List<String> values = config.getList(key);

22
pom.xml
View file

@ -45,7 +45,7 @@
<wildfly.build-tools.version>1.1.3.Final</wildfly.build-tools.version>
<wildfly11.version>11.0.0.Alpha1</wildfly11.version> <!-- for testing with wf11 pre-releases -->
<wildfly11.build-tools.version>1.1.8.Final</wildfly11.build-tools.version>
<eap.version>7.1.0.Alpha1-redhat-16</eap.version>
<eap.version>7.1.0.Beta1-redhat-2</eap.version>
<eap.build-tools.version>1.1.8.Final</eap.build-tools.version>
<wildfly.core.version>2.0.10.Final</wildfly.core.version>
@ -192,7 +192,6 @@
<module>adapters</module>
<module>authz</module>
<module>examples</module>
<module>testsuite</module>
<module>misc</module>
</modules>
@ -278,11 +277,6 @@
<artifactId>resteasy-undertow</artifactId>
<version>${resteasy.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>async-http-servlet-3.0</artifactId>
<version>${resteasy.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.xml.bind</groupId>
<artifactId>jboss-jaxb-api_2.2_spec</artifactId>
@ -1541,10 +1535,22 @@
<product.name-html>\u003Cstrong\u003ERed Hat\u003C\u002Fstrong\u003E\u003Csup\u003E\u00AE\u003C\u002Fsup\u003E Single Sign On</product.name-html>
<product.version>${project.version}</product.version>
<product.default-profile>product</product.default-profile>
<product.filename.version>7.1</product.filename.version>
<product.filename.version>7.2</product.filename.version>
</properties>
</profile>
<profile>
<id>testsuite</id>
<activation>
<property>
<name>!skipTestsuite</name>
</property>
</activation>
<modules>
<module>testsuite</module>
</modules>
</profile>
<profile>
<id>distribution</id>
<modules>

View file

@ -23,10 +23,10 @@ package org.keycloak.saml;
*/
public class SPMetadataDescriptor {
public static String getSPDescriptor(String binding, String assertionEndpoint, String logoutEndpoint, boolean wantAuthnRequestsSigned, String entityId, String nameIDPolicyFormat, String signingCerts) {
public static String getSPDescriptor(String binding, String assertionEndpoint, String logoutEndpoint, boolean wantAuthnRequestsSigned, boolean wantAssertionsSigned, String entityId, String nameIDPolicyFormat, String signingCerts) {
String descriptor =
"<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" entityID=\"" + entityId + "\">\n" +
" <SPSSODescriptor AuthnRequestsSigned=\"" + wantAuthnRequestsSigned + "\"\n" +
" <SPSSODescriptor AuthnRequestsSigned=\"" + wantAuthnRequestsSigned + "\" WantAssertionsSigned=\"" + wantAssertionsSigned + "\"\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";
if (wantAuthnRequestsSigned && signingCerts != null) {
descriptor += signingCerts;

View file

@ -32,6 +32,7 @@ import org.keycloak.dom.saml.v2.assertion.StatementAbstractType;
import org.keycloak.dom.saml.v2.assertion.SubjectType;
import org.keycloak.dom.saml.v2.assertion.SubjectType.STSubType;
import org.keycloak.dom.saml.v2.protocol.ResponseType;
import org.keycloak.rotation.KeyLocator;
import org.keycloak.saml.common.ErrorCodes;
import org.keycloak.saml.common.PicketLinkLogger;
import org.keycloak.saml.common.PicketLinkLoggerFactory;
@ -286,6 +287,22 @@ public class AssertionUtil {
return false;
}
/**
* Given an assertion element, validate the signature.
*/
public static boolean isSignatureValid(Element assertionElement, KeyLocator keyLocator) {
try {
Document doc = DocumentUtil.createDocument();
Node n = doc.importNode(assertionElement, true);
doc.appendChild(n);
return new SAML2Signature().validate(doc, keyLocator);
} catch (Exception e) {
logger.signatureAssertionValidationError(e);
}
return false;
}
/**
* Check whether the assertion has expired
*
@ -540,7 +557,23 @@ public class AssertionUtil {
return responseType.getAssertions().get(0).getAssertion();
}
public static ResponseType decryptAssertion(ResponseType responseType, PrivateKey privateKey) throws ParsingException, ProcessingException, ConfigurationException {
public static boolean isAssertionEncrypted(ResponseType responseType) throws ProcessingException {
List<ResponseType.RTChoiceType> assertions = responseType.getAssertions();
if (assertions.isEmpty()) {
throw new ProcessingException("No assertion from response.");
}
ResponseType.RTChoiceType rtChoiceType = assertions.get(0);
return rtChoiceType.getEncryptedAssertion() != null;
}
/**
* This method modifies the given responseType, and replaces the encrypted assertion with a decrypted version.
*
* It returns the assertion element as it was decrypted. This can be used in sginature verification.
*/
public static Element decryptAssertion(ResponseType responseType, PrivateKey privateKey) throws ParsingException, ProcessingException, ConfigurationException {
SAML2Response saml2Response = new SAML2Response();
Document doc = saml2Response.convert(responseType);
@ -564,6 +597,6 @@ public class AssertionUtil {
responseType.replaceAssertion(oldID, new ResponseType.RTChoiceType(assertion));
return responseType;
return decryptedDocumentElement;
}
}

View file

@ -130,7 +130,7 @@ public class XMLTimeUtil {
* @return
*/
public static long inMilis(int valueInMins) {
return valueInMins * 60 * 1000;
return (long) valueInMins * 60 * 1000;
}
/**

View file

@ -103,9 +103,9 @@ public class SAMLParserTest {
assertNotNull(rtChoiceType.getEncryptedAssertion());
PrivateKey privateKey = DerUtils.decodePrivateKey(Base64.decode(PRIVATE_KEY));
ResponseType rtWithDecryptedAssertion = AssertionUtil.decryptAssertion(resp, privateKey);
AssertionUtil.decryptAssertion(resp, privateKey);
rtChoiceType = rtWithDecryptedAssertion.getAssertions().get(0);
rtChoiceType = resp.getAssertions().get(0);
assertNotNull(rtChoiceType.getAssertion());
assertNull(rtChoiceType.getEncryptedAssertion());
}

View file

@ -49,9 +49,12 @@ import org.keycloak.protocol.saml.SamlProtocolUtils;
import org.keycloak.saml.SAML2LogoutResponseBuilder;
import org.keycloak.saml.SAMLRequestParser;
import org.keycloak.saml.common.constants.GeneralConstants;
import org.keycloak.saml.common.constants.JBossSAMLConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.common.exceptions.ConfigurationException;
import org.keycloak.saml.common.exceptions.ProcessingException;
import org.keycloak.saml.common.util.DocumentUtil;
import org.keycloak.saml.processing.api.saml.v2.sig.SAML2Signature;
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants;
import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil;
@ -74,6 +77,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import javax.xml.namespace.QName;
import java.io.IOException;
import java.security.Key;
import java.security.cert.X509Certificate;
@ -83,6 +87,8 @@ import java.util.List;
import org.keycloak.rotation.HardcodedKeyLocator;
import org.keycloak.rotation.KeyLocator;
import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import java.util.*;
@ -344,7 +350,38 @@ public class SAMLEndpoint {
if (responseType.getAssertions() == null || responseType.getAssertions().isEmpty()) {
return callback.error(relayState, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
}
AssertionType assertion = AssertionUtil.getAssertion(responseType, keys.getPrivateKey());
boolean assertionIsEncrypted = AssertionUtil.isAssertionEncrypted(responseType);
if (config.isWantAssertionsEncrypted() && !assertionIsEncrypted) {
logger.error("The assertion is not encrypted, which is required.");
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
event.error(Errors.INVALID_SAML_RESPONSE);
return ErrorPage.error(session, Messages.INVALID_REQUESTER);
}
Element assertionElement;
if (assertionIsEncrypted) {
// This methods writes the parsed and decrypted assertion back on the responseType parameter:
assertionElement = AssertionUtil.decryptAssertion(responseType, keys.getPrivateKey());
} else {
/* We verify the assertion using original document to handle cases where the IdP
includes whitespace and/or newlines inside tags. */
assertionElement = DocumentUtil.getElement(holder.getSamlDocument(), new QName(JBossSAMLConstants.ASSERTION.get()));
}
if (config.isWantAssertionsSigned() && config.isValidateSignature()) {
if (!AssertionUtil.isSignatureValid(assertionElement, getIDPKeyLocator())) {
logger.error("validation failed");
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
event.error(Errors.INVALID_SIGNATURE);
return ErrorPage.error(session, Messages.INVALID_REQUESTER);
}
}
AssertionType assertion = responseType.getAssertions().get(0).getAssertion();
SubjectType subject = assertion.getSubject();
SubjectType.STSubType subType = subject.getSubType();
NameIDType subjectNameID = (NameIDType) subType.getBaseID();

View file

@ -236,6 +236,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
boolean wantAuthnRequestsSigned = getConfig().isWantAuthnRequestsSigned();
boolean wantAssertionsSigned = getConfig().isWantAssertionsSigned();
String entityId = getEntityId(uriInfo, realm);
String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
@ -247,7 +248,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
for (RsaKeyMetadata key : keys) {
addKeyInfo(keysString, key, KeyTypes.SIGNING.value());
}
String descriptor = SPMetadataDescriptor.getSPDescriptor(authnBinding, endpoint, endpoint, wantAuthnRequestsSigned, entityId, nameIDPolicyFormat, keysString.toString());
String descriptor = SPMetadataDescriptor.getSPDescriptor(authnBinding, endpoint, endpoint, wantAuthnRequestsSigned, wantAssertionsSigned, entityId, nameIDPolicyFormat, keysString.toString());
return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build();
}

View file

@ -121,6 +121,22 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
getConfig().put("wantAuthnRequestsSigned", String.valueOf(wantAuthnRequestsSigned));
}
public boolean isWantAssertionsSigned() {
return Boolean.valueOf(getConfig().get("wantAssertionsSigned"));
}
public void setWantAssertionsSigned(boolean wantAssertionsSigned) {
getConfig().put("wantAssertionsSigned", String.valueOf(wantAssertionsSigned));
}
public boolean isWantAssertionsEncrypted() {
return Boolean.valueOf(getConfig().get("wantAssertionsEncrypted"));
}
public void setWantAssertionsEncrypted(boolean wantAssertionsEncrypted) {
getConfig().put("wantAssertionsEncrypted", String.valueOf(wantAssertionsEncrypted));
}
public boolean isAddExtensionsElementWithKeyInfo() {
return Boolean.valueOf(getConfig().get("addExtensionsElementWithKeyInfo"));
}

View file

@ -46,6 +46,10 @@ public class ClientBean {
return client.getName();
}
public String getDescription() {
return client.getDescription();
}
public String getBaseUrl() {
return ResolveRelative.resolveRelativeUri(requestUri, client.getRootUrl(), client.getBaseUrl());
}

View file

@ -141,6 +141,15 @@ public class UserInfoEndpoint {
UserSessionModel userSession = session.sessions().getUserSession(realm, token.getSessionState());
ClientSessionModel clientSession = session.sessions().getClientSession(token.getClientSession());
if( userSession == null ) {
userSession = session.sessions().getOfflineUserSession(realm, token.getSessionState());
if( AuthenticationManager.isOfflineSessionValid(realm, userSession)) {
clientSession = session.sessions().getOfflineClientSession(realm, token.getClientSession());
} else {
userSession = null;
clientSession = null;
}
}
if (userSession == null) {
event.error(Errors.USER_SESSION_NOT_FOUND);

View file

@ -47,7 +47,8 @@ public class SamlSPDescriptorClientInstallation implements ClientInstallationPro
String nameIdFormat = samlClient.getNameIDFormat();
if (nameIdFormat == null) nameIdFormat = SamlProtocol.SAML_DEFAULT_NAMEID_FORMAT;
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(), samlClient.requiresAssertionSignature(), client.getClientId(), nameIdFormat, spCertificate);
}
@Override

View file

@ -590,7 +590,7 @@ public class RealmAdminResource {
query.client(client);
}
if (types != null & !types.isEmpty()) {
if (types != null && !types.isEmpty()) {
EventType[] t = new EventType[types.size()];
for (int i = 0; i < t.length; i++) {
t[i] = EventType.valueOf(types.get(i));

View file

@ -48,6 +48,9 @@ public class ModalDialog {
@FindBy(id = "name")
private WebElement nameInput;
@FindBy(className = "modal-body")
private WebElement message;
public void ok() {
waitForModalFadeIn(driver);
okButton.click();
@ -70,4 +73,8 @@ public class ModalDialog {
nameInput.clear();
nameInput.sendKeys(name);
}
public WebElement getMessage() {
return message;
}
}

View file

@ -525,6 +525,27 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
setTimeOffset(0);
}
@Test
// KEYCLOAK-4503
public void initializeWithRefreshToken() {
oauth.realm(EXAMPLE);
oauth.clientId("js-console");
oauth.redirectUri("http://localhost:8280/js-console");
oauth.doLogin("user", "password");
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
String token = oauth.doAccessTokenRequest(code, "password").getAccessToken();
String refreshToken = oauth.doRefreshTokenRequest(token, "password").getRefreshToken();
jsConsoleTestAppPage.navigateTo();
jsConsoleTestAppPage.setInput2(refreshToken);
jsConsoleTestAppPage.initWithRefreshToken();
waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Init Success (Not Authenticated)");
waitUntilElement(jsConsoleTestAppPage.getEventsElement()).text().not().contains("Auth Success");
}
@Test
public void reentrancyCallbackTest() {
logInAndInit("standard");

View file

@ -249,6 +249,24 @@ public class UserInfoTest extends AbstractKeycloakTest {
}
}
@Test
public void testSessionExpiredOfflineAccess() throws Exception {
Client client = ClientBuilder.newClient();
try {
AccessTokenResponse accessTokenResponse = executeGrantAccessTokenRequest(client, true);
testingClient.testing().removeUserSessions("test");
Response response = UserInfoClientUtil.executeUserInfoRequest_getMethod(client, accessTokenResponse.getToken());
testSuccessfulUserInfoResponse(response);
response.close();
} finally {
client.close();
}
}
@Test
public void testUnsuccessfulUserInfoRequest() throws Exception {
Client client = ClientBuilder.newClient();
@ -274,6 +292,10 @@ public class UserInfoTest extends AbstractKeycloakTest {
}
private AccessTokenResponse executeGrantAccessTokenRequest(Client client) {
return executeGrantAccessTokenRequest(client, false);
}
private AccessTokenResponse executeGrantAccessTokenRequest(Client client, boolean requestOfflineToken) {
UriBuilder builder = UriBuilder.fromUri(AUTH_SERVER_ROOT);
URI grantUri = OIDCLoginProtocolService.tokenUrl(builder).build("test");
WebTarget grantTarget = client.target(grantUri);
@ -283,6 +305,9 @@ public class UserInfoTest extends AbstractKeycloakTest {
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD)
.param("username", "test-user@localhost")
.param("password", "password");
if( requestOfflineToken) {
form.param("scope", "offline_access");
}
Response response = grantTarget.request()
.header(HttpHeaders.AUTHORIZATION, header)

View file

@ -17,6 +17,8 @@
package org.keycloak.testsuite.console.authorization;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
import org.junit.Test;
@ -25,10 +27,13 @@ import org.junit.Test;
*/
public class DisableAuthorizationSettingsTest extends AbstractAuthorizationSettingsTest {
public static final String WARNING_MESSAGE = "Are you sure you want to disable authorization ? Once you save your changes, all authorization settings associated with this client will be removed. This operation can not be reverted.";
@Test
public void testDisableAuthorization() throws InterruptedException {
clientSettingsPage.navigateTo();
clientSettingsPage.form().setAuthorizationSettingsEnabled(false);
waitUntilElement(modalDialog.getMessage()).text().contains(WARNING_MESSAGE);
clientSettingsPage.form().confirmDisableAuthorizationSettings();
Thread.sleep(1000);
clientSettingsPage.form().save();
@ -37,4 +42,14 @@ public class DisableAuthorizationSettingsTest extends AbstractAuthorizationSetti
clientSettingsPage.navigateTo();
assertFalse(clientSettingsPage.form().isAuthorizationSettingsEnabled());
}
@Test
public void testCancelDisablingAuthorization() throws InterruptedException {
clientSettingsPage.navigateTo();
clientSettingsPage.form().setAuthorizationSettingsEnabled(false);
waitUntilElement(modalDialog.getMessage()).text().contains(WARNING_MESSAGE);
modalDialog.cancel();
Thread.sleep(1000);
assertTrue(clientSettingsPage.form().isAuthorizationSettingsEnabled());
}
}

View file

@ -892,10 +892,6 @@
<version>${undertow-embedded.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>async-http-servlet-3.0</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>

View file

@ -72,10 +72,6 @@
<groupId>org.jboss.spec.javax.transaction</groupId>
<artifactId>jboss-transaction-api_1.2_spec</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>async-http-servlet-3.0</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>

View file

@ -78,7 +78,7 @@ public class ValidationTest {
public void testBrokerExportDescriptor() throws Exception {
URL schemaFile = getClass().getResource("/schema/saml/v2/saml-schema-metadata-2.0.xsd");
Source xmlFile = new StreamSource(new ByteArrayInputStream(SPMetadataDescriptor.getSPDescriptor(
"POST", "http://realm/assertion", "http://realm/logout", true, "test", SamlProtocol.SAML_DEFAULT_NAMEID_FORMAT, KeycloakModelUtils.generateKeyPairCertificate("test").getCertificate()
"POST", "http://realm/assertion", "http://realm/logout", true, false, "test", SamlProtocol.SAML_DEFAULT_NAMEID_FORMAT, KeycloakModelUtils.generateKeyPairCertificate("test").getCertificate()
).getBytes()), "SP Descriptor");
SchemaFactory schemaFactory = SchemaFactory
.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);

View file

@ -551,7 +551,11 @@ http-post-binding-for-authn-request.tooltip=Indicates whether the AuthnRequest m
http-post-binding-logout=HTTP-POST Binding Logout
http-post-binding-logout.tooltip=Indicates whether to respond to requests using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used.
want-authn-requests-signed=Want AuthnRequests Signed
want-authn-requests-signed.tooltip=Indicates whether the identity provider expects signed a AuthnRequest.
want-authn-requests-signed.tooltip=Indicates whether the identity provider expects a signed AuthnRequest.
want-assertions-signed=Want Assertions Signed
want-assertions-signed.tooltip=Indicates whether this service provider expects a signed Assertion.
want-assertions-encrypted=Want Assertions Encrypted
want-assertions-encrypted.tooltip=Indicates whether this service provider expects an encrypted Assertion.
force-authentication=Force Authentication
identity-provider.force-authentication.tooltip=Indicates whether the identity provider must authenticate the presenter directly rather than rely on a previous security context.
validate-signature=Validate Signature

View file

@ -1943,9 +1943,13 @@ module.factory('errorInterceptor', function($q, $window, $rootScope, $location,
} else if (response.status) {
if (response.data && response.data.errorMessage) {
Notifications.error(response.data.errorMessage);
} else if (response.data && response.data.error_description) {
Notifications.error(response.data.error_description);
} else {
Notifications.error("An unexpected server error has occurred");
}
} else {
Notifications.error("No response from server.");
}
return $q.reject(response);
}

View file

@ -437,9 +437,6 @@ module.controller('ClientCertificateImportCtrl', function($scope, $location, $ht
}).success(function(data, status, headers) {
Notifications.success("Keystore uploaded successfully.");
$location.url(redirectLocation);
}).error(function(data) {
var errorMsg = data['error_description'] ? data['error_description'] : 'The key store can not be uploaded. Please verify the file.';
Notifications.error(errorMsg);
});
//.then(success, error, progress);
}
@ -1229,12 +1226,6 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
}, $scope.clientEdit, function() {
$route.reload();
Notifications.success("Your changes have been saved to the client.");
}, function(error) {
if (error.status == 400 && error.data.error_description) {
Notifications.error(error.data.error_description);
} else {
Notifications.error('Unexpected error when updating client');
}
});
}
};
@ -1348,12 +1339,6 @@ module.controller('CreateClientCtrl', function($scope, realm, client, templates,
var id = l.substring(l.lastIndexOf("/") + 1);
$location.url("/realms/" + realm.realm + "/clients/" + id);
Notifications.success("The client has been created.");
}, function(error) {
if (error.status == 400 && error.data.error_description) {
Notifications.error(error.data.error_description);
} else {
Notifications.error('Unexpected error when creating client');
}
});
};
@ -1818,12 +1803,6 @@ module.controller('ClientProtocolMapperCtrl', function($scope, realm, serverInfo
mapper = angular.copy($scope.mapper);
$location.url("/realms/" + realm.realm + '/clients/' + client.id + "/mappers/" + $scope.model.mapper.id);
Notifications.success("Your changes have been saved.");
}, function(error) {
if (error.status == 400 && error.data.error_description) {
Notifications.error(error.data.error_description);
} else {
Notifications.error('Unexpected error when updating protocol mapper');
}
});
};
@ -1890,14 +1869,6 @@ module.controller('ClientProtocolMapperCreateCtrl', function($scope, realm, serv
var id = l.substring(l.lastIndexOf("/") + 1);
$location.url("/realms/" + realm.realm + '/clients/' + client.id + "/mappers/" + id);
Notifications.success("Mapper has been created.");
}, function(error) {
if (error.status == 400 && error.data.error_description) {
Notifications.error(error.data.error_description);
} else if (error.status == 409 && error.data.errorMessage) {
Notifications.error(error.data.errorMessage);
} else {
Notifications.error('Unexpected error when updating protocol mapper');
}
});
};

View file

@ -1220,10 +1220,6 @@ module.controller('GenericKeystoreCtrl', function($scope, $location, Notificatio
$location.url("/realms/" + realm.realm + "/keys/providers/" + $scope.instance.providerId + "/" + id);
Notifications.success("The provider has been created.");
}, function (errorResponse) {
if (errorResponse.data && errorResponse.data['error_description']) {
Notifications.error(errorResponse.data['error_description']);
}
});
} else {
Components.update({realm: realm.realm,
@ -1232,10 +1228,6 @@ module.controller('GenericKeystoreCtrl', function($scope, $location, Notificatio
$scope.instance, function () {
$route.reload();
Notifications.success("The provider has been updated.");
}, function (errorResponse) {
if (errorResponse.data && errorResponse.data['error_description']) {
Notifications.error(errorResponse.data['error_description']);
}
});
}
};
@ -2478,10 +2470,6 @@ module.controller('ClientRegPolicyDetailCtrl', function($scope, realm, clientReg
var id = l.substring(l.lastIndexOf("/") + 1);
$location.url("/realms/" + realm.realm + "/client-registration/client-reg-policies/" + $scope.instance.providerId + "/" + id);
Notifications.success("The policy has been created.");
}, function (errorResponse) {
if (errorResponse.data && errorResponse.data['error_description']) {
Notifications.error(errorResponse.data['error_description']);
}
});
} else {
Components.update({realm: realm.realm,
@ -2490,10 +2478,6 @@ module.controller('ClientRegPolicyDetailCtrl', function($scope, realm, clientReg
$scope.instance, function () {
$route.reload();
Notifications.success("The policy has been updated.");
}, function (errorResponse) {
if (errorResponse.data && errorResponse.data['error_description']) {
Notifications.error(errorResponse.data['error_description']);
}
});
}
};

View file

@ -59,12 +59,6 @@ module.controller('UserRoleMappingCtrl', function($scope, $http, realm, user, cl
$scope.selectedClientMappings = [];
}
Notifications.success("Role mappings updated.");
}).error(function(response) {
if (response && response['error_description']) {
Notifications.error(response['error_description']);
} else {
Notifications.error("Failed to remove role mapping");
}
});
};
@ -93,12 +87,6 @@ module.controller('UserRoleMappingCtrl', function($scope, $http, realm, user, cl
$scope.realmComposite = CompositeRealmRoleMapping.query({realm : realm.realm, userId : user.id});
$scope.realmRoles = AvailableRealmRoleMapping.query({realm : realm.realm, userId : user.id});
Notifications.success("Role mappings updated.");
}).error(function(response) {
if (response && response['error_description']) {
Notifications.error(response['error_description']);
} else {
Notifications.error("Failed to remove role mapping");
}
});
};
@ -537,12 +525,6 @@ module.controller('UserCredentialsCtrl', function($scope, realm, user, $route, R
Notifications.success("The password has been reset");
$scope.password = null;
$scope.confirmPassword = null;
}, function(response) {
if (response.data && response.data['error_description']) {
Notifications.error(response.data['error_description']);
} else {
Notifications.error("Failed to reset user password");
}
});
}, function() {
$scope.password = null;
@ -822,10 +804,6 @@ module.controller('GenericUserStorageCtrl', function($scope, $location, Notifica
$location.url("/realms/" + realm.realm + "/user-storage/providers/" + $scope.instance.providerId + "/" + id);
Notifications.success("The provider has been created.");
}, function (errorResponse) {
if (errorResponse.data && errorResponse.data['error_description']) {
Notifications.error(errorResponse.data['error_description']);
}
});
} else {
console.log('update existing provider');
@ -835,10 +813,6 @@ module.controller('GenericUserStorageCtrl', function($scope, $location, Notifica
$scope.instance, function () {
$route.reload();
Notifications.success("The provider has been updated.");
}, function (errorResponse) {
if (errorResponse.data && errorResponse.data['error_description']) {
Notifications.error(errorResponse.data['error_description']);
}
});
}
};
@ -950,12 +924,6 @@ module.controller('UserGroupMembershipCtrl', function($scope, $route, realm, gro
UserGroupMapping.remove({realm: realm.realm, userId: user.id, groupId: $scope.selectedGroup.id}, function() {
Notifications.success('Removed group membership');
$route.reload();
}, function(response) {
if (response.data && response.data['error_description']) {
Notifications.error(response.data['error_description']);
} else {
Notifications.error("Failed to leave group");
}
});
};
@ -1231,10 +1199,6 @@ module.controller('LDAPUserStorageCtrl', function($scope, $location, Notificatio
$location.url("/realms/" + realm.realm + "/user-storage/providers/" + $scope.instance.providerId + "/" + id);
Notifications.success("The provider has been created.");
}, function (errorResponse) {
if (errorResponse.data && errorResponse.data['error_description']) {
Notifications.error(errorResponse.data['error_description']);
}
});
} else {
Components.update({realm: realm.realm,
@ -1243,10 +1207,6 @@ module.controller('LDAPUserStorageCtrl', function($scope, $location, Notificatio
$scope.instance, function () {
$route.reload();
Notifications.success("The provider has been updated.");
}, function (errorResponse) {
if (errorResponse.data && errorResponse.data['error_description']) {
Notifications.error(errorResponse.data['error_description']);
}
});
}
};
@ -1409,10 +1369,6 @@ module.controller('LDAPMapperCtrl', function($scope, $route, realm, provider, m
$scope.mapper, function () {
$route.reload();
Notifications.success("The mapper has been updated.");
}, function (errorResponse) {
if (errorResponse.data && errorResponse.data['error_description']) {
Notifications.error(errorResponse.data['error_description']);
}
});
};
@ -1498,10 +1454,6 @@ module.controller('LDAPMapperCreateCtrl', function($scope, realm, provider, mapp
$location.url("/realms/" + realm.realm + "/ldap-mappers/" + $scope.mapper.parentId + "/mappers/" + id);
Notifications.success("The mapper has been created.");
}, function (errorResponse) {
if (errorResponse.data && errorResponse.data['error_description']) {
Notifications.error(errorResponse.data['error_description']);
}
});
};

View file

@ -170,6 +170,20 @@
</div>
<kc-tooltip>{{:: 'want-authn-requests-signed.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="wantAssertionsSigned">{{:: 'want-assertions-signed' | translate}}</label>
<div class="col-md-6">
<input ng-model="identityProvider.config.wantAssertionsSigned" id="wantAssertionsSigned" name="wantAssertionsSigned" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
</div>
<kc-tooltip>{{:: 'want-assertions-signed.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="wantAssertionsEncrypted">{{:: 'want-assertions-encrypted' | translate}}</label>
<div class="col-md-6">
<input ng-model="identityProvider.config.wantAssertionsEncrypted" id="wantAssertionsEncrypted" name="wantAssertionsEncrypted" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
</div>
<kc-tooltip>{{:: 'want-assertions-encrypted.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group" data-ng-show="identityProvider.config.wantAuthnRequestsSigned == 'true'">
<label class="col-md-2 control-label" for="signatureAlgorithm">{{:: 'signature-algorithm' | translate}}</label>
<div class="col-sm-6">

View file

@ -33,7 +33,7 @@
<caption class="hidden">{{:: 'table-of-identity-providers' | translate}}</caption>
<thead>
<tr>
<th colspan="7" class="kc-table-actions">
<th colspan="8" class="kc-table-actions">
<div class="dropdown pull-right" data-ng-show="access.manageIdentityProviders">
<select class="form-control" ng-model="provider"
ng-options="p.name group by p.groupName for p in allProviders track by p.id"

View file

@ -1,5 +1,7 @@
#!/bin/bash -e
mvn install --no-snapshot-updates -DskipTests=true -f testsuite
if [ $1 == "old" ]; then
mvn test -B --no-snapshot-updates -f testsuite/integration
mvn test -B --no-snapshot-updates -f testsuite/jetty