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
|
- export MAVEN_SKIP_RC=true
|
||||||
|
|
||||||
install:
|
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:
|
script:
|
||||||
- ./travis-run-tests.sh $TESTS
|
- ./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
|
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
|
tar xfz distribution/server-dist/target/keycloak-<VERSION>.tar.gz
|
||||||
cd keycloak-appliance-dist-all-<VERSION>/keycloak
|
cd keycloak-<VERSION>
|
||||||
bin/standalone.sh
|
bin/standalone.sh
|
||||||
|
|
||||||
To stop the server press `Ctrl + C`.
|
To stop the server press `Ctrl + C`.
|
||||||
|
|
|
@ -101,6 +101,7 @@ public class AuthenticatedActionsHandler {
|
||||||
if (!deployment.isCors()) return false;
|
if (!deployment.isCors()) return false;
|
||||||
KeycloakSecurityContext securityContext = facade.getSecurityContext();
|
KeycloakSecurityContext securityContext = facade.getSecurityContext();
|
||||||
String origin = facade.getRequest().getHeader(CorsHeaders.ORIGIN);
|
String origin = facade.getRequest().getHeader(CorsHeaders.ORIGIN);
|
||||||
|
String exposeHeaders = deployment.getCorsExposedHeaders();
|
||||||
String requestOrigin = UriUtils.getOrigin(facade.getRequest().getURI());
|
String requestOrigin = UriUtils.getOrigin(facade.getRequest().getURI());
|
||||||
log.debugv("Origin: {0} uri: {1}", origin, facade.getRequest().getURI());
|
log.debugv("Origin: {0} uri: {1}", origin, facade.getRequest().getURI());
|
||||||
if (securityContext != null && origin != null && !origin.equals(requestOrigin)) {
|
if (securityContext != null && origin != null && !origin.equals(requestOrigin)) {
|
||||||
|
@ -124,6 +125,9 @@ public class AuthenticatedActionsHandler {
|
||||||
facade.getResponse().setStatus(200);
|
facade.getResponse().setStatus(200);
|
||||||
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
|
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
|
||||||
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
|
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
|
||||||
|
if (exposeHeaders != null) {
|
||||||
|
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, exposeHeaders);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log.debugv("cors validation not needed as we're not a secure session or origin header was null: {0}", facade.getRequest().getURI());
|
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 ORIGIN = "Origin";
|
||||||
String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
|
String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
|
||||||
String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";
|
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 int corsMaxAge = -1;
|
||||||
protected String corsAllowedHeaders;
|
protected String corsAllowedHeaders;
|
||||||
protected String corsAllowedMethods;
|
protected String corsAllowedMethods;
|
||||||
|
protected String corsExposedHeaders;
|
||||||
protected boolean exposeToken;
|
protected boolean exposeToken;
|
||||||
protected boolean alwaysRefreshToken;
|
protected boolean alwaysRefreshToken;
|
||||||
protected boolean registerNodeAtStartup;
|
protected boolean registerNodeAtStartup;
|
||||||
|
@ -325,6 +326,14 @@ public class KeycloakDeployment {
|
||||||
this.corsAllowedMethods = corsAllowedMethods;
|
this.corsAllowedMethods = corsAllowedMethods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCorsExposedHeaders() {
|
||||||
|
return corsExposedHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCorsExposedHeaders(String corsExposedHeaders) {
|
||||||
|
this.corsExposedHeaders = corsExposedHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isExposeToken() {
|
public boolean isExposeToken() {
|
||||||
return exposeToken;
|
return exposeToken;
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,6 +96,7 @@ public class KeycloakDeploymentBuilder {
|
||||||
deployment.setCorsMaxAge(adapterConfig.getCorsMaxAge());
|
deployment.setCorsMaxAge(adapterConfig.getCorsMaxAge());
|
||||||
deployment.setCorsAllowedHeaders(adapterConfig.getCorsAllowedHeaders());
|
deployment.setCorsAllowedHeaders(adapterConfig.getCorsAllowedHeaders());
|
||||||
deployment.setCorsAllowedMethods(adapterConfig.getCorsAllowedMethods());
|
deployment.setCorsAllowedMethods(adapterConfig.getCorsAllowedMethods());
|
||||||
|
deployment.setCorsExposedHeaders(adapterConfig.getCorsExposedHeaders());
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc7636
|
// https://tools.ietf.org/html/rfc7636
|
||||||
|
|
|
@ -53,6 +53,7 @@ public class KeycloakDeploymentBuilderTest {
|
||||||
assertEquals(1000, deployment.getCorsMaxAge());
|
assertEquals(1000, deployment.getCorsMaxAge());
|
||||||
assertEquals("POST, PUT, DELETE, GET", deployment.getCorsAllowedMethods());
|
assertEquals("POST, PUT, DELETE, GET", deployment.getCorsAllowedMethods());
|
||||||
assertEquals("X-Custom, X-Custom2", deployment.getCorsAllowedHeaders());
|
assertEquals("X-Custom, X-Custom2", deployment.getCorsAllowedHeaders());
|
||||||
|
assertEquals("X-Custom3, X-Custom4", deployment.getCorsExposedHeaders());
|
||||||
assertTrue(deployment.isBearerOnly());
|
assertTrue(deployment.isBearerOnly());
|
||||||
assertTrue(deployment.isPublicClient());
|
assertTrue(deployment.isPublicClient());
|
||||||
assertTrue(deployment.isEnableBasicAuth());
|
assertTrue(deployment.isEnableBasicAuth());
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
"cors-max-age": 1000,
|
"cors-max-age": 1000,
|
||||||
"cors-allowed-methods": "POST, PUT, DELETE, GET",
|
"cors-allowed-methods": "POST, PUT, DELETE, GET",
|
||||||
"cors-allowed-headers": "X-Custom, X-Custom2",
|
"cors-allowed-headers": "X-Custom, X-Custom2",
|
||||||
|
"cors-exposed-headers": "X-Custom3, X-Custom4",
|
||||||
"bearer-only": true,
|
"bearer-only": true,
|
||||||
"public-client": true,
|
"public-client": true,
|
||||||
"enable-basic-auth": true,
|
"enable-basic-auth": true,
|
||||||
|
|
|
@ -587,7 +587,7 @@
|
||||||
|
|
||||||
req.onreadystatechange = function () {
|
req.onreadystatechange = function () {
|
||||||
if (req.readyState == 4) {
|
if (req.readyState == 4) {
|
||||||
if (req.status == 200) {
|
if (req.status == 200 || fileLoaded(req)) {
|
||||||
var config = JSON.parse(req.responseText);
|
var config = JSON.parse(req.responseText);
|
||||||
|
|
||||||
kc.authServerUrl = config['auth-server-url'];
|
kc.authServerUrl = config['auth-server-url'];
|
||||||
|
@ -633,6 +633,10 @@
|
||||||
return promise.promise;
|
return promise.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fileLoaded(xhr) {
|
||||||
|
return xhr.status == 0 && xhr.responseText && xhr.responseURL.startsWith('file:');
|
||||||
|
}
|
||||||
|
|
||||||
function setToken(token, refreshToken, idToken, timeLocal) {
|
function setToken(token, refreshToken, idToken, timeLocal) {
|
||||||
if (kc.tokenTimeoutHandle) {
|
if (kc.tokenTimeoutHandle) {
|
||||||
clearTimeout(kc.tokenTimeoutHandle);
|
clearTimeout(kc.tokenTimeoutHandle);
|
||||||
|
|
|
@ -124,6 +124,12 @@ public class SharedAttributeDefinitons {
|
||||||
.setAllowExpression(true)
|
.setAllowExpression(true)
|
||||||
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
|
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
|
||||||
.build();
|
.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 =
|
protected static final SimpleAttributeDefinition EXPOSE_TOKEN =
|
||||||
new SimpleAttributeDefinitionBuilder("expose-token", ModelType.BOOLEAN, true)
|
new SimpleAttributeDefinitionBuilder("expose-token", ModelType.BOOLEAN, true)
|
||||||
.setXmlName("expose-token")
|
.setXmlName("expose-token")
|
||||||
|
@ -191,6 +197,7 @@ public class SharedAttributeDefinitons {
|
||||||
ATTRIBUTES.add(CORS_MAX_AGE);
|
ATTRIBUTES.add(CORS_MAX_AGE);
|
||||||
ATTRIBUTES.add(CORS_ALLOWED_HEADERS);
|
ATTRIBUTES.add(CORS_ALLOWED_HEADERS);
|
||||||
ATTRIBUTES.add(CORS_ALLOWED_METHODS);
|
ATTRIBUTES.add(CORS_ALLOWED_METHODS);
|
||||||
|
ATTRIBUTES.add(CORS_EXPOSED_HEADERS);
|
||||||
ATTRIBUTES.add(EXPOSE_TOKEN);
|
ATTRIBUTES.add(EXPOSE_TOKEN);
|
||||||
ATTRIBUTES.add(AUTH_SERVER_URL_FOR_BACKEND_REQUESTS);
|
ATTRIBUTES.add(AUTH_SERVER_URL_FOR_BACKEND_REQUESTS);
|
||||||
ATTRIBUTES.add(ALWAYS_REFRESH_TOKEN);
|
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-max-age=CORS max-age header
|
||||||
keycloak.realm.cors-allowed-headers=CORS allowed headers
|
keycloak.realm.cors-allowed-headers=CORS allowed headers
|
||||||
keycloak.realm.cors-allowed-methods=CORS allowed methods
|
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.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.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
|
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-max-age=CORS max-age header
|
||||||
keycloak.secure-deployment.cors-allowed-headers=CORS allowed headers
|
keycloak.secure-deployment.cors-allowed-headers=CORS allowed headers
|
||||||
keycloak.secure-deployment.cors-allowed-methods=CORS allowed methods
|
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.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.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
|
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="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="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-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="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="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"/>
|
<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="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="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-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="resource" type="xs:string" minOccurs="0" maxOccurs="1" />
|
||||||
<xs:element name="truststore" 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"/>
|
<xs:element name="truststore-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||||
|
|
|
@ -124,6 +124,12 @@ public class SharedAttributeDefinitons {
|
||||||
.setAllowExpression(true)
|
.setAllowExpression(true)
|
||||||
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
|
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
|
||||||
.build();
|
.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 =
|
protected static final SimpleAttributeDefinition EXPOSE_TOKEN =
|
||||||
new SimpleAttributeDefinitionBuilder("expose-token", ModelType.BOOLEAN, true)
|
new SimpleAttributeDefinitionBuilder("expose-token", ModelType.BOOLEAN, true)
|
||||||
.setXmlName("expose-token")
|
.setXmlName("expose-token")
|
||||||
|
@ -175,6 +181,8 @@ public class SharedAttributeDefinitons {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
protected static final List<SimpleAttributeDefinition> ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
|
protected static final List<SimpleAttributeDefinition> ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
|
||||||
static {
|
static {
|
||||||
ATTRIBUTES.add(REALM_PUBLIC_KEY);
|
ATTRIBUTES.add(REALM_PUBLIC_KEY);
|
||||||
|
@ -192,6 +200,7 @@ public class SharedAttributeDefinitons {
|
||||||
ATTRIBUTES.add(CORS_MAX_AGE);
|
ATTRIBUTES.add(CORS_MAX_AGE);
|
||||||
ATTRIBUTES.add(CORS_ALLOWED_HEADERS);
|
ATTRIBUTES.add(CORS_ALLOWED_HEADERS);
|
||||||
ATTRIBUTES.add(CORS_ALLOWED_METHODS);
|
ATTRIBUTES.add(CORS_ALLOWED_METHODS);
|
||||||
|
ATTRIBUTES.add(CORS_EXPOSED_HEADERS);
|
||||||
ATTRIBUTES.add(EXPOSE_TOKEN);
|
ATTRIBUTES.add(EXPOSE_TOKEN);
|
||||||
ATTRIBUTES.add(AUTH_SERVER_URL_FOR_BACKEND_REQUESTS);
|
ATTRIBUTES.add(AUTH_SERVER_URL_FOR_BACKEND_REQUESTS);
|
||||||
ATTRIBUTES.add(ALWAYS_REFRESH_TOKEN);
|
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-max-age=CORS max-age header
|
||||||
keycloak.realm.cors-allowed-headers=CORS allowed headers
|
keycloak.realm.cors-allowed-headers=CORS allowed headers
|
||||||
keycloak.realm.cors-allowed-methods=CORS allowed methods
|
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.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.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
|
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-max-age=CORS max-age header
|
||||||
keycloak.secure-deployment.cors-allowed-headers=CORS allowed headers
|
keycloak.secure-deployment.cors-allowed-headers=CORS allowed headers
|
||||||
keycloak.secure-deployment.cors-allowed-methods=CORS allowed methods
|
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.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.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
|
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="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="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-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="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="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"/>
|
<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="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="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-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="resource" type="xs:string" minOccurs="0" maxOccurs="1" />
|
||||||
<xs:element name="truststore" 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"/>
|
<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",
|
@JsonPropertyOrder({"realm", "realm-public-key", "auth-server-url", "ssl-required",
|
||||||
"resource", "public-client", "credentials",
|
"resource", "public-client", "credentials",
|
||||||
"use-resource-role-mappings",
|
"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",
|
"expose-token", "bearer-only", "autodetect-bearer-only",
|
||||||
"connection-pool-size",
|
"connection-pool-size",
|
||||||
"allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password",
|
"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",
|
@JsonPropertyOrder({"realm", "realm-public-key", "auth-server-url", "ssl-required",
|
||||||
"resource", "public-client", "credentials",
|
"resource", "public-client", "credentials",
|
||||||
"use-resource-role-mappings",
|
"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"})
|
"expose-token", "bearer-only", "autodetect-bearer-only", "enable-basic-auth"})
|
||||||
public class BaseAdapterConfig extends BaseRealmConfig {
|
public class BaseAdapterConfig extends BaseRealmConfig {
|
||||||
@JsonProperty("resource")
|
@JsonProperty("resource")
|
||||||
|
@ -48,6 +48,8 @@ public class BaseAdapterConfig extends BaseRealmConfig {
|
||||||
protected String corsAllowedHeaders;
|
protected String corsAllowedHeaders;
|
||||||
@JsonProperty("cors-allowed-methods")
|
@JsonProperty("cors-allowed-methods")
|
||||||
protected String corsAllowedMethods;
|
protected String corsAllowedMethods;
|
||||||
|
@JsonProperty("cors-exposed-headers")
|
||||||
|
protected String corsExposedHeaders;
|
||||||
@JsonProperty("expose-token")
|
@JsonProperty("expose-token")
|
||||||
protected boolean exposeToken;
|
protected boolean exposeToken;
|
||||||
@JsonProperty("bearer-only")
|
@JsonProperty("bearer-only")
|
||||||
|
@ -110,6 +112,14 @@ public class BaseAdapterConfig extends BaseRealmConfig {
|
||||||
this.corsAllowedMethods = corsAllowedMethods;
|
this.corsAllowedMethods = corsAllowedMethods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCorsExposedHeaders() {
|
||||||
|
return corsExposedHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCorsExposedHeaders(String corsExposedHeaders) {
|
||||||
|
this.corsExposedHeaders = corsExposedHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isExposeToken() {
|
public boolean isExposeToken() {
|
||||||
return exposeToken;
|
return exposeToken;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
<build xmlns="urn:wildfly:feature-pack-build:1.0">
|
<build xmlns="urn:wildfly:feature-pack-build:1.0">
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<artifact name="org.wildfly:wildfly-feature-pack" />
|
<artifact name="${feature.parent}" />
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<config>
|
<config>
|
||||||
<standalone template="configuration/standalone/template.xml" subsystems="configuration/standalone/subsystems.xml" output-file="standalone/configuration/standalone.xml" />
|
<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>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-authz-client</artifactId>
|
<artifactId>keycloak-authz-client</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.wildfly</groupId>
|
|
||||||
<artifactId>wildfly-feature-pack</artifactId>
|
|
||||||
<type>zip</type>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@ -119,4 +113,52 @@
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</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>
|
</project>
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
<head>
|
<head>
|
||||||
<title>Authentication Example</title>
|
<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="cordova.js"></script>
|
||||||
<script type="text/javascript" charset="utf-8" src="keycloak.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);
|
entity.setUser(userRef);
|
||||||
em.persist(entity);
|
em.persist(entity);
|
||||||
MultivaluedHashMap<String, String> config = cred.getConfig();
|
MultivaluedHashMap<String, String> config = cred.getConfig();
|
||||||
if (config != null || !config.isEmpty()) {
|
if (config != null && !config.isEmpty()) {
|
||||||
|
|
||||||
for (String key : config.keySet()) {
|
for (String key : config.keySet()) {
|
||||||
List<String> values = config.getList(key);
|
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>
|
<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.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>
|
<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>
|
<eap.build-tools.version>1.1.8.Final</eap.build-tools.version>
|
||||||
<wildfly.core.version>2.0.10.Final</wildfly.core.version>
|
<wildfly.core.version>2.0.10.Final</wildfly.core.version>
|
||||||
|
|
||||||
|
@ -192,7 +192,6 @@
|
||||||
<module>adapters</module>
|
<module>adapters</module>
|
||||||
<module>authz</module>
|
<module>authz</module>
|
||||||
<module>examples</module>
|
<module>examples</module>
|
||||||
<module>testsuite</module>
|
|
||||||
<module>misc</module>
|
<module>misc</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
|
@ -278,11 +277,6 @@
|
||||||
<artifactId>resteasy-undertow</artifactId>
|
<artifactId>resteasy-undertow</artifactId>
|
||||||
<version>${resteasy.version}</version>
|
<version>${resteasy.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
|
||||||
<artifactId>async-http-servlet-3.0</artifactId>
|
|
||||||
<version>${resteasy.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.spec.javax.xml.bind</groupId>
|
<groupId>org.jboss.spec.javax.xml.bind</groupId>
|
||||||
<artifactId>jboss-jaxb-api_2.2_spec</artifactId>
|
<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.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.version>${project.version}</product.version>
|
||||||
<product.default-profile>product</product.default-profile>
|
<product.default-profile>product</product.default-profile>
|
||||||
<product.filename.version>7.1</product.filename.version>
|
<product.filename.version>7.2</product.filename.version>
|
||||||
</properties>
|
</properties>
|
||||||
</profile>
|
</profile>
|
||||||
|
|
||||||
|
<profile>
|
||||||
|
<id>testsuite</id>
|
||||||
|
<activation>
|
||||||
|
<property>
|
||||||
|
<name>!skipTestsuite</name>
|
||||||
|
</property>
|
||||||
|
</activation>
|
||||||
|
<modules>
|
||||||
|
<module>testsuite</module>
|
||||||
|
</modules>
|
||||||
|
</profile>
|
||||||
|
|
||||||
<profile>
|
<profile>
|
||||||
<id>distribution</id>
|
<id>distribution</id>
|
||||||
<modules>
|
<modules>
|
||||||
|
|
|
@ -23,10 +23,10 @@ package org.keycloak.saml;
|
||||||
*/
|
*/
|
||||||
public class SPMetadataDescriptor {
|
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 =
|
String descriptor =
|
||||||
"<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" entityID=\"" + entityId + "\">\n" +
|
"<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";
|
" 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) {
|
if (wantAuthnRequestsSigned && signingCerts != null) {
|
||||||
descriptor += signingCerts;
|
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;
|
||||||
import org.keycloak.dom.saml.v2.assertion.SubjectType.STSubType;
|
import org.keycloak.dom.saml.v2.assertion.SubjectType.STSubType;
|
||||||
import org.keycloak.dom.saml.v2.protocol.ResponseType;
|
import org.keycloak.dom.saml.v2.protocol.ResponseType;
|
||||||
|
import org.keycloak.rotation.KeyLocator;
|
||||||
import org.keycloak.saml.common.ErrorCodes;
|
import org.keycloak.saml.common.ErrorCodes;
|
||||||
import org.keycloak.saml.common.PicketLinkLogger;
|
import org.keycloak.saml.common.PicketLinkLogger;
|
||||||
import org.keycloak.saml.common.PicketLinkLoggerFactory;
|
import org.keycloak.saml.common.PicketLinkLoggerFactory;
|
||||||
|
@ -286,6 +287,22 @@ public class AssertionUtil {
|
||||||
return false;
|
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
|
* Check whether the assertion has expired
|
||||||
*
|
*
|
||||||
|
@ -540,7 +557,23 @@ public class AssertionUtil {
|
||||||
return responseType.getAssertions().get(0).getAssertion();
|
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();
|
SAML2Response saml2Response = new SAML2Response();
|
||||||
|
|
||||||
Document doc = saml2Response.convert(responseType);
|
Document doc = saml2Response.convert(responseType);
|
||||||
|
@ -564,6 +597,6 @@ public class AssertionUtil {
|
||||||
|
|
||||||
responseType.replaceAssertion(oldID, new ResponseType.RTChoiceType(assertion));
|
responseType.replaceAssertion(oldID, new ResponseType.RTChoiceType(assertion));
|
||||||
|
|
||||||
return responseType;
|
return decryptedDocumentElement;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -130,7 +130,7 @@ public class XMLTimeUtil {
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static long inMilis(int valueInMins) {
|
public static long inMilis(int valueInMins) {
|
||||||
return valueInMins * 60 * 1000;
|
return (long) valueInMins * 60 * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -241,4 +241,4 @@ public class XMLTimeUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,9 +103,9 @@ public class SAMLParserTest {
|
||||||
assertNotNull(rtChoiceType.getEncryptedAssertion());
|
assertNotNull(rtChoiceType.getEncryptedAssertion());
|
||||||
|
|
||||||
PrivateKey privateKey = DerUtils.decodePrivateKey(Base64.decode(PRIVATE_KEY));
|
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());
|
assertNotNull(rtChoiceType.getAssertion());
|
||||||
assertNull(rtChoiceType.getEncryptedAssertion());
|
assertNull(rtChoiceType.getEncryptedAssertion());
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,9 +49,12 @@ import org.keycloak.protocol.saml.SamlProtocolUtils;
|
||||||
import org.keycloak.saml.SAML2LogoutResponseBuilder;
|
import org.keycloak.saml.SAML2LogoutResponseBuilder;
|
||||||
import org.keycloak.saml.SAMLRequestParser;
|
import org.keycloak.saml.SAMLRequestParser;
|
||||||
import org.keycloak.saml.common.constants.GeneralConstants;
|
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.constants.JBossSAMLURIConstants;
|
||||||
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
||||||
import org.keycloak.saml.common.exceptions.ProcessingException;
|
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.common.SAMLDocumentHolder;
|
||||||
import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants;
|
import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants;
|
||||||
import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil;
|
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.Response;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import javax.xml.namespace.QName;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
@ -83,6 +87,8 @@ import java.util.List;
|
||||||
import org.keycloak.rotation.HardcodedKeyLocator;
|
import org.keycloak.rotation.HardcodedKeyLocator;
|
||||||
import org.keycloak.rotation.KeyLocator;
|
import org.keycloak.rotation.KeyLocator;
|
||||||
import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
|
import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
@ -344,7 +350,38 @@ public class SAMLEndpoint {
|
||||||
if (responseType.getAssertions() == null || responseType.getAssertions().isEmpty()) {
|
if (responseType.getAssertions() == null || responseType.getAssertions().isEmpty()) {
|
||||||
return callback.error(relayState, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
|
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 subject = assertion.getSubject();
|
||||||
SubjectType.STSubType subType = subject.getSubType();
|
SubjectType.STSubType subType = subject.getSubType();
|
||||||
NameIDType subjectNameID = (NameIDType) subType.getBaseID();
|
NameIDType subjectNameID = (NameIDType) subType.getBaseID();
|
||||||
|
|
|
@ -236,6 +236,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
||||||
|
|
||||||
|
|
||||||
boolean wantAuthnRequestsSigned = getConfig().isWantAuthnRequestsSigned();
|
boolean wantAuthnRequestsSigned = getConfig().isWantAuthnRequestsSigned();
|
||||||
|
boolean wantAssertionsSigned = getConfig().isWantAssertionsSigned();
|
||||||
String entityId = getEntityId(uriInfo, realm);
|
String entityId = getEntityId(uriInfo, realm);
|
||||||
String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
|
String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
|
||||||
|
|
||||||
|
@ -247,7 +248,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
||||||
for (RsaKeyMetadata key : keys) {
|
for (RsaKeyMetadata key : keys) {
|
||||||
addKeyInfo(keysString, key, KeyTypes.SIGNING.value());
|
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();
|
return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -121,6 +121,22 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
|
||||||
getConfig().put("wantAuthnRequestsSigned", String.valueOf(wantAuthnRequestsSigned));
|
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() {
|
public boolean isAddExtensionsElementWithKeyInfo() {
|
||||||
return Boolean.valueOf(getConfig().get("addExtensionsElementWithKeyInfo"));
|
return Boolean.valueOf(getConfig().get("addExtensionsElementWithKeyInfo"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,10 @@ public class ClientBean {
|
||||||
return client.getName();
|
return client.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return client.getDescription();
|
||||||
|
}
|
||||||
|
|
||||||
public String getBaseUrl() {
|
public String getBaseUrl() {
|
||||||
return ResolveRelative.resolveRelativeUri(requestUri, client.getRootUrl(), client.getBaseUrl());
|
return ResolveRelative.resolveRelativeUri(requestUri, client.getRootUrl(), client.getBaseUrl());
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,6 +141,15 @@ public class UserInfoEndpoint {
|
||||||
|
|
||||||
UserSessionModel userSession = session.sessions().getUserSession(realm, token.getSessionState());
|
UserSessionModel userSession = session.sessions().getUserSession(realm, token.getSessionState());
|
||||||
ClientSessionModel clientSession = session.sessions().getClientSession(token.getClientSession());
|
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) {
|
if (userSession == null) {
|
||||||
event.error(Errors.USER_SESSION_NOT_FOUND);
|
event.error(Errors.USER_SESSION_NOT_FOUND);
|
||||||
|
|
|
@ -47,7 +47,8 @@ public class SamlSPDescriptorClientInstallation implements ClientInstallationPro
|
||||||
String nameIdFormat = samlClient.getNameIDFormat();
|
String nameIdFormat = samlClient.getNameIDFormat();
|
||||||
if (nameIdFormat == null) nameIdFormat = SamlProtocol.SAML_DEFAULT_NAMEID_FORMAT;
|
if (nameIdFormat == null) nameIdFormat = SamlProtocol.SAML_DEFAULT_NAMEID_FORMAT;
|
||||||
String spCertificate = SPMetadataDescriptor.xmlKeyInfo(" ", null, samlClient.getClientSigningCertificate(), KeyTypes.SIGNING.value(), true);
|
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
|
@Override
|
||||||
|
|
|
@ -590,7 +590,7 @@ public class RealmAdminResource {
|
||||||
query.client(client);
|
query.client(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (types != null & !types.isEmpty()) {
|
if (types != null && !types.isEmpty()) {
|
||||||
EventType[] t = new EventType[types.size()];
|
EventType[] t = new EventType[types.size()];
|
||||||
for (int i = 0; i < t.length; i++) {
|
for (int i = 0; i < t.length; i++) {
|
||||||
t[i] = EventType.valueOf(types.get(i));
|
t[i] = EventType.valueOf(types.get(i));
|
||||||
|
|
|
@ -48,6 +48,9 @@ public class ModalDialog {
|
||||||
@FindBy(id = "name")
|
@FindBy(id = "name")
|
||||||
private WebElement nameInput;
|
private WebElement nameInput;
|
||||||
|
|
||||||
|
@FindBy(className = "modal-body")
|
||||||
|
private WebElement message;
|
||||||
|
|
||||||
public void ok() {
|
public void ok() {
|
||||||
waitForModalFadeIn(driver);
|
waitForModalFadeIn(driver);
|
||||||
okButton.click();
|
okButton.click();
|
||||||
|
@ -70,4 +73,8 @@ public class ModalDialog {
|
||||||
nameInput.clear();
|
nameInput.clear();
|
||||||
nameInput.sendKeys(name);
|
nameInput.sendKeys(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public WebElement getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -404,7 +404,7 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
|
||||||
|
|
||||||
int timeSkew = Integer.parseInt(jsConsoleTestAppPage.getTimeSkewValue().getText());
|
int timeSkew = Integer.parseInt(jsConsoleTestAppPage.getTimeSkewValue().getText());
|
||||||
assertTrue("TimeSkew was: " + timeSkew + ", but should be ~0", timeSkew >= 0 - TIME_SKEW_TOLERANCE);
|
assertTrue("TimeSkew was: " + timeSkew + ", but should be ~0", timeSkew >= 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);
|
setTimeOffset(40);
|
||||||
jsConsoleTestAppPage.refreshToken();
|
jsConsoleTestAppPage.refreshToken();
|
||||||
|
@ -414,7 +414,7 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
|
||||||
|
|
||||||
timeSkew = Integer.parseInt(jsConsoleTestAppPage.getTimeSkewValue().getText());
|
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 >= 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
|
// KEYCLOAK-4179
|
||||||
|
@ -525,6 +525,27 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
|
||||||
setTimeOffset(0);
|
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
|
@Test
|
||||||
public void reentrancyCallbackTest() {
|
public void reentrancyCallbackTest() {
|
||||||
logInAndInit("standard");
|
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
|
@Test
|
||||||
public void testUnsuccessfulUserInfoRequest() throws Exception {
|
public void testUnsuccessfulUserInfoRequest() throws Exception {
|
||||||
Client client = ClientBuilder.newClient();
|
Client client = ClientBuilder.newClient();
|
||||||
|
@ -274,8 +292,12 @@ public class UserInfoTest extends AbstractKeycloakTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private AccessTokenResponse executeGrantAccessTokenRequest(Client client) {
|
private AccessTokenResponse executeGrantAccessTokenRequest(Client client) {
|
||||||
|
return executeGrantAccessTokenRequest(client, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AccessTokenResponse executeGrantAccessTokenRequest(Client client, boolean requestOfflineToken) {
|
||||||
UriBuilder builder = UriBuilder.fromUri(AUTH_SERVER_ROOT);
|
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);
|
WebTarget grantTarget = client.target(grantUri);
|
||||||
|
|
||||||
String header = BasicAuthHelper.createHeader("test-app", "password");
|
String header = BasicAuthHelper.createHeader("test-app", "password");
|
||||||
|
@ -283,6 +305,9 @@ public class UserInfoTest extends AbstractKeycloakTest {
|
||||||
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD)
|
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD)
|
||||||
.param("username", "test-user@localhost")
|
.param("username", "test-user@localhost")
|
||||||
.param("password", "password");
|
.param("password", "password");
|
||||||
|
if( requestOfflineToken) {
|
||||||
|
form.param("scope", "offline_access");
|
||||||
|
}
|
||||||
|
|
||||||
Response response = grantTarget.request()
|
Response response = grantTarget.request()
|
||||||
.header(HttpHeaders.AUTHORIZATION, header)
|
.header(HttpHeaders.AUTHORIZATION, header)
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
package org.keycloak.testsuite.console.authorization;
|
package org.keycloak.testsuite.console.authorization;
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
@ -25,10 +27,13 @@ import org.junit.Test;
|
||||||
*/
|
*/
|
||||||
public class DisableAuthorizationSettingsTest extends AbstractAuthorizationSettingsTest {
|
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
|
@Test
|
||||||
public void testDisableAuthorization() throws InterruptedException {
|
public void testDisableAuthorization() throws InterruptedException {
|
||||||
clientSettingsPage.navigateTo();
|
clientSettingsPage.navigateTo();
|
||||||
clientSettingsPage.form().setAuthorizationSettingsEnabled(false);
|
clientSettingsPage.form().setAuthorizationSettingsEnabled(false);
|
||||||
|
waitUntilElement(modalDialog.getMessage()).text().contains(WARNING_MESSAGE);
|
||||||
clientSettingsPage.form().confirmDisableAuthorizationSettings();
|
clientSettingsPage.form().confirmDisableAuthorizationSettings();
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
clientSettingsPage.form().save();
|
clientSettingsPage.form().save();
|
||||||
|
@ -37,4 +42,14 @@ public class DisableAuthorizationSettingsTest extends AbstractAuthorizationSetti
|
||||||
clientSettingsPage.navigateTo();
|
clientSettingsPage.navigateTo();
|
||||||
assertFalse(clientSettingsPage.form().isAuthorizationSettingsEnabled());
|
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>
|
<version>${undertow-embedded.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
|
||||||
<artifactId>async-http-servlet-3.0</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
<artifactId>resteasy-client</artifactId>
|
<artifactId>resteasy-client</artifactId>
|
||||||
|
|
|
@ -72,10 +72,6 @@
|
||||||
<groupId>org.jboss.spec.javax.transaction</groupId>
|
<groupId>org.jboss.spec.javax.transaction</groupId>
|
||||||
<artifactId>jboss-transaction-api_1.2_spec</artifactId>
|
<artifactId>jboss-transaction-api_1.2_spec</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
|
||||||
<artifactId>async-http-servlet-3.0</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
<artifactId>resteasy-jaxrs</artifactId>
|
<artifactId>resteasy-jaxrs</artifactId>
|
||||||
|
|
|
@ -78,7 +78,7 @@ public class ValidationTest {
|
||||||
public void testBrokerExportDescriptor() throws Exception {
|
public void testBrokerExportDescriptor() throws Exception {
|
||||||
URL schemaFile = getClass().getResource("/schema/saml/v2/saml-schema-metadata-2.0.xsd");
|
URL schemaFile = getClass().getResource("/schema/saml/v2/saml-schema-metadata-2.0.xsd");
|
||||||
Source xmlFile = new StreamSource(new ByteArrayInputStream(SPMetadataDescriptor.getSPDescriptor(
|
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");
|
).getBytes()), "SP Descriptor");
|
||||||
SchemaFactory schemaFactory = SchemaFactory
|
SchemaFactory schemaFactory = SchemaFactory
|
||||||
.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
|
.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=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.
|
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=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
|
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.
|
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
|
validate-signature=Validate Signature
|
||||||
|
|
|
@ -1943,9 +1943,13 @@ module.factory('errorInterceptor', function($q, $window, $rootScope, $location,
|
||||||
} else if (response.status) {
|
} else if (response.status) {
|
||||||
if (response.data && response.data.errorMessage) {
|
if (response.data && response.data.errorMessage) {
|
||||||
Notifications.error(response.data.errorMessage);
|
Notifications.error(response.data.errorMessage);
|
||||||
|
} else if (response.data && response.data.error_description) {
|
||||||
|
Notifications.error(response.data.error_description);
|
||||||
} else {
|
} else {
|
||||||
Notifications.error("An unexpected server error has occurred");
|
Notifications.error("An unexpected server error has occurred");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Notifications.error("No response from server.");
|
||||||
}
|
}
|
||||||
return $q.reject(response);
|
return $q.reject(response);
|
||||||
}
|
}
|
||||||
|
|
|
@ -437,9 +437,6 @@ module.controller('ClientCertificateImportCtrl', function($scope, $location, $ht
|
||||||
}).success(function(data, status, headers) {
|
}).success(function(data, status, headers) {
|
||||||
Notifications.success("Keystore uploaded successfully.");
|
Notifications.success("Keystore uploaded successfully.");
|
||||||
$location.url(redirectLocation);
|
$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);
|
//.then(success, error, progress);
|
||||||
}
|
}
|
||||||
|
@ -1229,12 +1226,6 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
|
||||||
}, $scope.clientEdit, function() {
|
}, $scope.clientEdit, function() {
|
||||||
$route.reload();
|
$route.reload();
|
||||||
Notifications.success("Your changes have been saved to the client.");
|
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);
|
var id = l.substring(l.lastIndexOf("/") + 1);
|
||||||
$location.url("/realms/" + realm.realm + "/clients/" + id);
|
$location.url("/realms/" + realm.realm + "/clients/" + id);
|
||||||
Notifications.success("The client has been created.");
|
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);
|
mapper = angular.copy($scope.mapper);
|
||||||
$location.url("/realms/" + realm.realm + '/clients/' + client.id + "/mappers/" + $scope.model.mapper.id);
|
$location.url("/realms/" + realm.realm + '/clients/' + client.id + "/mappers/" + $scope.model.mapper.id);
|
||||||
Notifications.success("Your changes have been saved.");
|
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);
|
var id = l.substring(l.lastIndexOf("/") + 1);
|
||||||
$location.url("/realms/" + realm.realm + '/clients/' + client.id + "/mappers/" + id);
|
$location.url("/realms/" + realm.realm + '/clients/' + client.id + "/mappers/" + id);
|
||||||
Notifications.success("Mapper has been created.");
|
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);
|
$location.url("/realms/" + realm.realm + "/keys/providers/" + $scope.instance.providerId + "/" + id);
|
||||||
Notifications.success("The provider has been created.");
|
Notifications.success("The provider has been created.");
|
||||||
}, function (errorResponse) {
|
|
||||||
if (errorResponse.data && errorResponse.data['error_description']) {
|
|
||||||
Notifications.error(errorResponse.data['error_description']);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Components.update({realm: realm.realm,
|
Components.update({realm: realm.realm,
|
||||||
|
@ -1232,10 +1228,6 @@ module.controller('GenericKeystoreCtrl', function($scope, $location, Notificatio
|
||||||
$scope.instance, function () {
|
$scope.instance, function () {
|
||||||
$route.reload();
|
$route.reload();
|
||||||
Notifications.success("The provider has been updated.");
|
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);
|
var id = l.substring(l.lastIndexOf("/") + 1);
|
||||||
$location.url("/realms/" + realm.realm + "/client-registration/client-reg-policies/" + $scope.instance.providerId + "/" + id);
|
$location.url("/realms/" + realm.realm + "/client-registration/client-reg-policies/" + $scope.instance.providerId + "/" + id);
|
||||||
Notifications.success("The policy has been created.");
|
Notifications.success("The policy has been created.");
|
||||||
}, function (errorResponse) {
|
|
||||||
if (errorResponse.data && errorResponse.data['error_description']) {
|
|
||||||
Notifications.error(errorResponse.data['error_description']);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Components.update({realm: realm.realm,
|
Components.update({realm: realm.realm,
|
||||||
|
@ -2490,10 +2478,6 @@ module.controller('ClientRegPolicyDetailCtrl', function($scope, realm, clientReg
|
||||||
$scope.instance, function () {
|
$scope.instance, function () {
|
||||||
$route.reload();
|
$route.reload();
|
||||||
Notifications.success("The policy has been updated.");
|
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 = [];
|
$scope.selectedClientMappings = [];
|
||||||
}
|
}
|
||||||
Notifications.success("Role mappings updated.");
|
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.realmComposite = CompositeRealmRoleMapping.query({realm : realm.realm, userId : user.id});
|
||||||
$scope.realmRoles = AvailableRealmRoleMapping.query({realm : realm.realm, userId : user.id});
|
$scope.realmRoles = AvailableRealmRoleMapping.query({realm : realm.realm, userId : user.id});
|
||||||
Notifications.success("Role mappings updated.");
|
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");
|
Notifications.success("The password has been reset");
|
||||||
$scope.password = null;
|
$scope.password = null;
|
||||||
$scope.confirmPassword = 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() {
|
}, function() {
|
||||||
$scope.password = null;
|
$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);
|
$location.url("/realms/" + realm.realm + "/user-storage/providers/" + $scope.instance.providerId + "/" + id);
|
||||||
Notifications.success("The provider has been created.");
|
Notifications.success("The provider has been created.");
|
||||||
}, function (errorResponse) {
|
|
||||||
if (errorResponse.data && errorResponse.data['error_description']) {
|
|
||||||
Notifications.error(errorResponse.data['error_description']);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log('update existing provider');
|
console.log('update existing provider');
|
||||||
|
@ -835,10 +813,6 @@ module.controller('GenericUserStorageCtrl', function($scope, $location, Notifica
|
||||||
$scope.instance, function () {
|
$scope.instance, function () {
|
||||||
$route.reload();
|
$route.reload();
|
||||||
Notifications.success("The provider has been updated.");
|
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() {
|
UserGroupMapping.remove({realm: realm.realm, userId: user.id, groupId: $scope.selectedGroup.id}, function() {
|
||||||
Notifications.success('Removed group membership');
|
Notifications.success('Removed group membership');
|
||||||
$route.reload();
|
$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);
|
$location.url("/realms/" + realm.realm + "/user-storage/providers/" + $scope.instance.providerId + "/" + id);
|
||||||
Notifications.success("The provider has been created.");
|
Notifications.success("The provider has been created.");
|
||||||
}, function (errorResponse) {
|
|
||||||
if (errorResponse.data && errorResponse.data['error_description']) {
|
|
||||||
Notifications.error(errorResponse.data['error_description']);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Components.update({realm: realm.realm,
|
Components.update({realm: realm.realm,
|
||||||
|
@ -1243,10 +1207,6 @@ module.controller('LDAPUserStorageCtrl', function($scope, $location, Notificatio
|
||||||
$scope.instance, function () {
|
$scope.instance, function () {
|
||||||
$route.reload();
|
$route.reload();
|
||||||
Notifications.success("The provider has been updated.");
|
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 () {
|
$scope.mapper, function () {
|
||||||
$route.reload();
|
$route.reload();
|
||||||
Notifications.success("The mapper has been updated.");
|
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);
|
$location.url("/realms/" + realm.realm + "/ldap-mappers/" + $scope.mapper.parentId + "/mappers/" + id);
|
||||||
Notifications.success("The mapper has been created.");
|
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>
|
</div>
|
||||||
<kc-tooltip>{{:: 'want-authn-requests-signed.tooltip' | translate}}</kc-tooltip>
|
<kc-tooltip>{{:: 'want-authn-requests-signed.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</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'">
|
<div class="form-group" data-ng-show="identityProvider.config.wantAuthnRequestsSigned == 'true'">
|
||||||
<label class="col-md-2 control-label" for="signatureAlgorithm">{{:: 'signature-algorithm' | translate}}</label>
|
<label class="col-md-2 control-label" for="signatureAlgorithm">{{:: 'signature-algorithm' | translate}}</label>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
<caption class="hidden">{{:: 'table-of-identity-providers' | translate}}</caption>
|
<caption class="hidden">{{:: 'table-of-identity-providers' | translate}}</caption>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<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">
|
<div class="dropdown pull-right" data-ng-show="access.manageIdentityProviders">
|
||||||
<select class="form-control" ng-model="provider"
|
<select class="form-control" ng-model="provider"
|
||||||
ng-options="p.name group by p.groupName for p in allProviders track by p.id"
|
ng-options="p.name group by p.groupName for p in allProviders track by p.id"
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#!/bin/bash -e
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
mvn install --no-snapshot-updates -DskipTests=true -f testsuite
|
||||||
|
|
||||||
if [ $1 == "old" ]; then
|
if [ $1 == "old" ]; then
|
||||||
mvn test -B --no-snapshot-updates -f testsuite/integration
|
mvn test -B --no-snapshot-updates -f testsuite/integration
|
||||||
mvn test -B --no-snapshot-updates -f testsuite/jetty
|
mvn test -B --no-snapshot-updates -f testsuite/jetty
|
||||||
|
|
Loading…
Reference in a new issue