Merge remote-tracking branch 'upstream/master'

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

View file

@ -25,7 +25,7 @@ before_script:
- export MAVEN_SKIP_RC=true - 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

View file

@ -41,10 +41,10 @@ To start Keycloak during development first build as specified above, then run:
mvn -f testsuite/integration/pom.xml exec:java -Pkeycloak-server 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`.

View file

@ -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());
} }

View file

@ -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";
} }

View file

@ -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;
} }

View file

@ -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

View file

@ -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());

View file

@ -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,

View file

@ -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);

View file

@ -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);

View file

@ -39,6 +39,7 @@ keycloak.realm.client-key-password=n/a
keycloak.realm.cors-max-age=CORS max-age header keycloak.realm.cors-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

View file

@ -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"/>

View file

@ -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);

View file

@ -39,6 +39,7 @@ keycloak.realm.client-key-password=n/a
keycloak.realm.cors-max-age=CORS max-age header keycloak.realm.cors-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

View file

@ -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"/>

View file

@ -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",

View file

@ -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;
} }

View file

@ -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" />

View file

@ -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>

View file

@ -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>

View file

@ -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
View file

@ -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>

View file

@ -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;

View file

@ -32,6 +32,7 @@ import org.keycloak.dom.saml.v2.assertion.StatementAbstractType;
import org.keycloak.dom.saml.v2.assertion.SubjectType; import org.keycloak.dom.saml.v2.assertion.SubjectType;
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;
} }
} }

View file

@ -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 {
} }
} }
} }
} }

View file

@ -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());
} }

View file

@ -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();

View file

@ -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();
} }

View file

@ -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"));
} }

View file

@ -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());
} }

View file

@ -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);

View file

@ -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

View file

@ -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));

View file

@ -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;
}
} }

View file

@ -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");

View file

@ -249,6 +249,24 @@ public class UserInfoTest extends AbstractKeycloakTest {
} }
} }
@Test
public void testSessionExpiredOfflineAccess() throws Exception {
Client client = ClientBuilder.newClient();
try {
AccessTokenResponse accessTokenResponse = executeGrantAccessTokenRequest(client, true);
testingClient.testing().removeUserSessions("test");
Response response = UserInfoClientUtil.executeUserInfoRequest_getMethod(client, accessTokenResponse.getToken());
testSuccessfulUserInfoResponse(response);
response.close();
} finally {
client.close();
}
}
@Test @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)

View file

@ -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());
}
} }

View file

@ -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>

View file

@ -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>

View file

@ -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);

View file

@ -551,7 +551,11 @@ http-post-binding-for-authn-request.tooltip=Indicates whether the AuthnRequest m
http-post-binding-logout=HTTP-POST Binding Logout http-post-binding-logout=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

View file

@ -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);
} }

View file

@ -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');
}
}); });
}; };

View file

@ -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']);
}
}); });
} }
}; };

View file

@ -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']);
}
}); });
}; };

View file

@ -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">

View file

@ -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"

View file

@ -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