Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
a8a8ea4bcd
48 changed files with 338 additions and 143 deletions
|
@ -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
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
22
pom.xml
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -130,7 +130,7 @@ public class XMLTimeUtil {
|
|||
* @return
|
||||
*/
|
||||
public static long inMilis(int valueInMins) {
|
||||
return valueInMins * 60 * 1000;
|
||||
return (long) valueInMins * 60 * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -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']);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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']);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue