diff --git a/.travis.yml b/.travis.yml index 57147b004f..ec6ed80dac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/README.md b/README.md index a4454a0fc9..c2efec51d5 100755 --- a/README.md +++ b/README.md @@ -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-.tar.gz - cd keycloak-appliance-dist-all-/keycloak + tar xfz distribution/server-dist/target/keycloak-.tar.gz + cd keycloak- bin/standalone.sh To stop the server press `Ctrl + C`. diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AuthenticatedActionsHandler.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AuthenticatedActionsHandler.java index 472afb7d45..7f86ba1bde 100755 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AuthenticatedActionsHandler.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AuthenticatedActionsHandler.java @@ -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()); } diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/CorsHeaders.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/CorsHeaders.java index 715af3dce6..416c3923f8 100755 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/CorsHeaders.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/CorsHeaders.java @@ -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"; } diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java index ba7bc5d759..31f842c654 100755 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java @@ -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; } diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java index 2fd92760c6..a65175390e 100755 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java @@ -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 diff --git a/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java b/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java index 233c1ed2f0..a4f04ec152 100644 --- a/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java +++ b/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java @@ -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()); diff --git a/adapters/oidc/adapter-core/src/test/resources/keycloak.json b/adapters/oidc/adapter-core/src/test/resources/keycloak.json index 9f0a204826..f53432ffee 100644 --- a/adapters/oidc/adapter-core/src/test/resources/keycloak.json +++ b/adapters/oidc/adapter-core/src/test/resources/keycloak.json @@ -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, diff --git a/adapters/oidc/js/src/main/resources/keycloak.js b/adapters/oidc/js/src/main/resources/keycloak.js index 3f09229a42..89b15b8a52 100755 --- a/adapters/oidc/js/src/main/resources/keycloak.js +++ b/adapters/oidc/js/src/main/resources/keycloak.js @@ -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); diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SharedAttributeDefinitons.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SharedAttributeDefinitons.java index e4ff98816a..a7676eade0 100755 --- a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SharedAttributeDefinitons.java +++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SharedAttributeDefinitons.java @@ -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); diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/org/keycloak/subsystem/wf8/extension/LocalDescriptions.properties b/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/org/keycloak/subsystem/wf8/extension/LocalDescriptions.properties index 6244b0c797..71101a1c24 100755 --- a/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/org/keycloak/subsystem/wf8/extension/LocalDescriptions.properties +++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/org/keycloak/subsystem/wf8/extension/LocalDescriptions.properties @@ -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 diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd b/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd index e9839bc069..cc51ec41ab 100755 --- a/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd +++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd @@ -58,6 +58,7 @@ + @@ -88,6 +89,7 @@ + diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SharedAttributeDefinitons.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SharedAttributeDefinitons.java index 02d14e5b81..fafed423c9 100755 --- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SharedAttributeDefinitons.java +++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SharedAttributeDefinitons.java @@ -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 ATTRIBUTES = new ArrayList(); 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); diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties b/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties index c0ca52e0dc..a297c1dcc3 100755 --- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties +++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties @@ -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 diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd b/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd index 84399a361a..81182090d9 100755 --- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd +++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd @@ -58,6 +58,7 @@ + @@ -88,6 +89,7 @@ + diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java index f063962a55..ddd525bbd7 100755 --- a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java +++ b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java @@ -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", diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java index dfe4ae633d..4a2b7e288f 100755 --- a/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java +++ b/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java @@ -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; } diff --git a/distribution/feature-packs/adapter-feature-pack/feature-pack-build.xml b/distribution/feature-packs/adapter-feature-pack/feature-pack-build.xml index e249722284..50ea3d7636 100644 --- a/distribution/feature-packs/adapter-feature-pack/feature-pack-build.xml +++ b/distribution/feature-packs/adapter-feature-pack/feature-pack-build.xml @@ -17,7 +17,7 @@ - + diff --git a/distribution/feature-packs/adapter-feature-pack/pom.xml b/distribution/feature-packs/adapter-feature-pack/pom.xml index e51f22056d..be9c989d96 100755 --- a/distribution/feature-packs/adapter-feature-pack/pom.xml +++ b/distribution/feature-packs/adapter-feature-pack/pom.xml @@ -61,12 +61,6 @@ org.keycloak keycloak-authz-client - - - org.wildfly - wildfly-feature-pack - zip - @@ -119,4 +113,52 @@ + + + + community + + + !product + + + + + ${wildfly.build-tools.version} + org.wildfly:wildfly-feature-pack + + + + + org.wildfly + wildfly-feature-pack + zip + + + + + + product + + + product + + + + + ${eap.build-tools.version} + org.jboss.eap:wildfly-feature-pack + + + + + org.jboss.eap + wildfly-feature-pack + ${eap.version} + zip + + + + + diff --git a/examples/cordova/www/index.html b/examples/cordova/www/index.html index 296986d112..78646df303 100644 --- a/examples/cordova/www/index.html +++ b/examples/cordova/www/index.html @@ -20,7 +20,7 @@ Authentication Example - + diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStore.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStore.java index d3539fe8d7..a1a0cdb6a0 100644 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStore.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStore.java @@ -119,7 +119,7 @@ public class JpaUserCredentialStore implements UserCredentialStore { entity.setUser(userRef); em.persist(entity); MultivaluedHashMap config = cred.getConfig(); - if (config != null || !config.isEmpty()) { + if (config != null && !config.isEmpty()) { for (String key : config.keySet()) { List values = config.getList(key); diff --git a/pom.xml b/pom.xml index b14081a54c..4f128e29b5 100755 --- a/pom.xml +++ b/pom.xml @@ -45,7 +45,7 @@ 1.1.3.Final 11.0.0.Alpha1 1.1.8.Final - 7.1.0.Alpha1-redhat-16 + 7.1.0.Beta1-redhat-2 1.1.8.Final 2.0.10.Final @@ -192,7 +192,6 @@ adapters authz examples - testsuite misc @@ -278,11 +277,6 @@ resteasy-undertow ${resteasy.version} - - org.jboss.resteasy - async-http-servlet-3.0 - ${resteasy.version} - org.jboss.spec.javax.xml.bind jboss-jaxb-api_2.2_spec @@ -1541,10 +1535,22 @@ \u003Cstrong\u003ERed Hat\u003C\u002Fstrong\u003E\u003Csup\u003E\u00AE\u003C\u002Fsup\u003E Single Sign On ${project.version} product - 7.1 + 7.2 + + testsuite + + + !skipTestsuite + + + + testsuite + + + distribution diff --git a/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java b/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java index 9a28137694..5feda2b568 100755 --- a/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java +++ b/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java @@ -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 = "\n" + - " \n"; if (wantAuthnRequestsSigned && signingCerts != null) { descriptor += signingCerts; diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java index 714ee3f6f5..bb15d233bf 100755 --- a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java +++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java @@ -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 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; } } \ No newline at end of file diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/XMLTimeUtil.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/XMLTimeUtil.java index 31916ff627..8510d47658 100755 --- a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/XMLTimeUtil.java +++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/XMLTimeUtil.java @@ -130,7 +130,7 @@ public class XMLTimeUtil { * @return */ public static long inMilis(int valueInMins) { - return valueInMins * 60 * 1000; + return (long) valueInMins * 60 * 1000; } /** @@ -241,4 +241,4 @@ public class XMLTimeUtil { } } } -} \ No newline at end of file +} diff --git a/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java b/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java index 52dd7baa9a..713a5bdfe8 100644 --- a/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java +++ b/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java @@ -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()); } diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java index 72d84ee5c0..5825f60554 100755 --- a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java +++ b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java @@ -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(); diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java index 6a453b96fa..c28cda8936 100755 --- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java +++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java @@ -236,6 +236,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider= 0 - TIME_SKEW_TOLERANCE); - assertTrue("TimeSkew was: " + timeSkew + ", but should be ~0", timeSkew <= TIME_SKEW_TOLERANCE); + assertTrue("TimeSkew was: " + timeSkew + ", but should be ~0", timeSkew <= TIME_SKEW_TOLERANCE); setTimeOffset(40); jsConsoleTestAppPage.refreshToken(); @@ -414,7 +414,7 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl timeSkew = Integer.parseInt(jsConsoleTestAppPage.getTimeSkewValue().getText()); assertTrue("TimeSkew was: " + timeSkew + ", but should be ~-40", timeSkew + 40 >= 0 - TIME_SKEW_TOLERANCE); - assertTrue("TimeSkew was: " + timeSkew + ", but should be ~-40", timeSkew + 40 <= TIME_SKEW_TOLERANCE); + assertTrue("TimeSkew was: " + timeSkew + ", but should be ~-40", timeSkew + 40 <= TIME_SKEW_TOLERANCE); } // KEYCLOAK-4179 @@ -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"); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java index e6ec392e6e..c57b64e3a0 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java @@ -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,8 +292,12 @@ 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"); + URI grantUri = OIDCLoginProtocolService.tokenUrl(builder).build("test"); WebTarget grantTarget = client.target(grantUri); String header = BasicAuthHelper.createHeader("test-app", "password"); @@ -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) diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authorization/DisableAuthorizationSettingsTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authorization/DisableAuthorizationSettingsTest.java index 3e3359e6db..0c60f0f170 100644 --- a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authorization/DisableAuthorizationSettingsTest.java +++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authorization/DisableAuthorizationSettingsTest.java @@ -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()); + } } diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml index f0d569c2df..4e29ec3051 100755 --- a/testsuite/integration-arquillian/tests/pom.xml +++ b/testsuite/integration-arquillian/tests/pom.xml @@ -892,10 +892,6 @@ ${undertow-embedded.version} - - org.jboss.resteasy - async-http-servlet-3.0 - org.jboss.resteasy resteasy-client diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml index 4542557e06..cda3bbbfa8 100755 --- a/testsuite/integration/pom.xml +++ b/testsuite/integration/pom.xml @@ -72,10 +72,6 @@ org.jboss.spec.javax.transaction jboss-transaction-api_1.2_spec - - org.jboss.resteasy - async-http-servlet-3.0 - org.jboss.resteasy resteasy-jaxrs diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/ValidationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/ValidationTest.java index aa4ca666cc..6833b3415b 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/ValidationTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/ValidationTest.java @@ -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); diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index 175e39e98c..db61ff7a4c 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -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 diff --git a/themes/src/main/resources/theme/base/admin/resources/js/app.js b/themes/src/main/resources/theme/base/admin/resources/js/app.js index 58db5a09cb..734040e0fc 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/app.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/app.js @@ -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); } diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js index a2cbea4c84..315b8a53cb 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js @@ -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'); - } }); }; diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js index 4f2a62f410..d5b63c3c2a 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js @@ -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']); - } }); } }; diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js index 8f426189f2..13d83436b7 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js @@ -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']); - } }); }; diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html index 9c79a47e7d..aa6733a3ba 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html @@ -170,6 +170,20 @@ {{:: 'want-authn-requests-signed.tooltip' | translate}} +
+ +
+ +
+ {{:: 'want-assertions-signed.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'want-assertions-encrypted.tooltip' | translate}} +
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider.html index d764b90c78..3422752962 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider.html @@ -33,7 +33,7 @@ {{:: 'table-of-identity-providers' | translate}} - +