Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Bill Burke 2016-02-09 12:26:11 -05:00
commit 1c97b9c41d
32 changed files with 1079 additions and 247 deletions

View file

@ -85,42 +85,38 @@ public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticat
private AccessTokenResponse getToken(String username, String password) throws Exception {
AccessTokenResponse tokenResponse=null;
HttpClient client = new HttpClientBuilder().disableTrustManager().build();
HttpClient client = deployment.getClient();
try {
HttpPost post = new HttpPost(
KeycloakUriBuilder.fromUri(deployment.getAuthServerBaseUrl())
.path(ServiceUrlConstants.TOKEN_PATH).build(deployment.getRealm()));
java.util.List <NameValuePair> formparams = new java.util.ArrayList <NameValuePair>();
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD));
formparams.add(new BasicNameValuePair("username", username));
formparams.add(new BasicNameValuePair("password", password));
HttpPost post = new HttpPost(
KeycloakUriBuilder.fromUri(deployment.getAuthServerBaseUrl())
.path(ServiceUrlConstants.TOKEN_PATH).build(deployment.getRealm()));
java.util.List <NameValuePair> formparams = new java.util.ArrayList <NameValuePair>();
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD));
formparams.add(new BasicNameValuePair("username", username));
formparams.add(new BasicNameValuePair("password", password));
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
post.setEntity(form);
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
post.setEntity(form);
HttpResponse response = client.execute(post);
int status = response.getStatusLine().getStatusCode();
HttpEntity entity = response.getEntity();
if (status != 200) {
throw new java.io.IOException("Bad status: " + status);
}
if (entity == null) {
throw new java.io.IOException("No Entity");
}
java.io.InputStream is = entity.getContent();
try {
tokenResponse = JsonSerialization.readValue(is, AccessTokenResponse.class);
} finally {
try {
is.close();
} catch (java.io.IOException ignored) { }
}
} finally {
client.getConnectionManager().shutdown();
}
HttpResponse response = client.execute(post);
int status = response.getStatusLine().getStatusCode();
HttpEntity entity = response.getEntity();
if (status != 200) {
throw new java.io.IOException("Bad status: " + status);
}
if (entity == null) {
throw new java.io.IOException("No Entity");
}
java.io.InputStream is = entity.getContent();
try {
tokenResponse = JsonSerialization.readValue(is, AccessTokenResponse.class);
} finally {
try {
is.close();
} catch (java.io.IOException ignored) { }
}
return (tokenResponse);
}

View file

@ -98,7 +98,7 @@ public class KeycloakDeploymentBuilder {
if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) {
throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url");
}
if (realmKeyPem == null || !deployment.isBearerOnly() || deployment.isRegisterNodeAtStartup() || deployment.getRegisterNodePeriod() != -1) {
if (realmKeyPem == null || !deployment.isBearerOnly() || deployment.isEnableBasicAuth() || deployment.isRegisterNodeAtStartup() || deployment.getRegisterNodePeriod() != -1) {
deployment.setClient(new HttpClientBuilder().build(adapterConfig));
}
if (adapterConfig.getAuthServerUrl() == null && (!deployment.isBearerOnly() || realmKeyPem == null)) {

View file

@ -45,14 +45,10 @@ public class Version {
Version.VERSION = props.getProperty("version");
Version.BUILD_TIME = props.getProperty("build-time");
Version.RESOURCES_VERSION = Version.VERSION.toLowerCase();
if (Version.RESOURCES_VERSION.endsWith("-snapshot")) {
Version.RESOURCES_VERSION = Version.RESOURCES_VERSION.replace("-snapshot", "-" + Time.currentTime());
}
} catch (IOException e) {
Version.VERSION= Version.UNKNOWN;
Version.BUILD_TIME= Version.UNKNOWN;
Version.VERSION = Version.UNKNOWN;
Version.BUILD_TIME = Version.UNKNOWN;
}
}
}

View file

@ -50,5 +50,9 @@
<source>../../shared-cli/adapter-install.cli</source>
<outputDirectory>bin</outputDirectory>
</file>
<file>
<source>cli/adapter-install-offline.cli</source>
<outputDirectory>bin</outputDirectory>
</file>
</files>
</assembly>

View file

@ -0,0 +1,5 @@
embed-server --server-config=standalone.xml
/subsystem=security/security-domain=keycloak/:add
/subsystem=security/security-domain=keycloak/authentication=classic/:add(login-modules=[{ "code" => "org.keycloak.adapters.jboss.KeycloakLoginModule","flag" => "required"}])
/extension=org.keycloak.keycloak-adapter-subsystem/:add(module=org.keycloak.keycloak-adapter-subsystem)
/subsystem=keycloak:add

View file

@ -25,61 +25,46 @@
<section>
<title>Disabling Caches</title>
<para>
The realm and user caches can be disabled through configuration or through the management console. To
manally disable the realm or user cache, you must edit the <literal>keycloak-server.json</literal> file
The realm and user caches can be cleared through the management console. To
disable the realm or user cache, you must edit the <literal>keycloak-server.json</literal> file
in your distribution. Here's what the config looks like initially.
</para>
<para>
<programlisting><![CDATA[
"realmCache": {
"provider": "${keycloak.realm.cache.provider:mem}"
"userCache": {
"infinispan" : {
"enabled": true
}
},
"userCache": {
"provider": "${keycloak.user.cache.provider:mem}",
"mem": {
"maxSize": 20000
"realmCache": {
"infinispan" : {
"enabled": true
}
},
]]></programlisting>
</para>
<para>You must then change it to:
<programlisting><![CDATA[
"realmCache": {
"provider": "${keycloak.realm.cache.provider:none}"
"userCache": {
"infinispan" : {
"enabled": false
}
},
"userCache": {
"provider": "${keycloak.user.cache.provider:none}"
"realmCache": {
"infinispan" : {
"enabled": false
}
},
]]></programlisting>
</para>
<para>
You can also disable either of the caches at runtime through the Keycloak admin console Realm Settings page.
This will not permanently disable the cache. If you reboot the server, the cache will be re-enabled unless
you manualy disable the cache in the <literal>keycloak-server.json</literal> file.
</para>
</section>
<section>
<title>Clear Caches</title>
<para>
To clear the realm or user cache, go to the Keycloak admin console Realm Settings->Cache Config page. Disable the cache
you want. Save the settings. Then re-enable the cache. This will cause the cache to be cleared.
you want. This will cause the cache to be cleared.
</para>
</section>
<section>
<title>Cache Config</title>
<para>
Cache configuration is done within <literal>keycloak-server.json</literal>. Changes to this file will not
be seen by the server until you reboot. Currently you can only configure the max size of the user cache.
<programlisting><![CDATA[
"userCache": {
"provider": "${keycloak.user.cache.provider:mem}",
"mem": {
"maxSize": 20000
}
},
]]></programlisting>
</para>
</section>
</chapter>
</chapter>

View file

@ -27,7 +27,7 @@
<section id="jboss-adapter-installation">
<title>Adapter Installation</title>
<para>
Adapters are no longer included with the appliance or war distribution.Each adapter is a separate download on
Adapters are no longer included with the appliance or war distribution. Each adapter is a separate download on
the Keycloak download site. They are also available as a maven artifact.
</para>
<para>
@ -74,6 +74,13 @@ $ jboss-cli.sh -c --file=adapter-install.cli
</programlisting>
The script will add the extension, subsystem, and optional security-domain as described below.
</para>
<para>
For more recent versions of WildFly there's also a offline CLI script that can be used to install the adapter while the server is not running:
<programlisting>
$ cd $JBOSS_HOME/bin
$ jboss-cli.sh -c --file=adapter-install-offline.cli
</programlisting>
</para>
<para>
<programlisting><![CDATA[
<server xmlns="urn:jboss:domain:1.4">

View file

@ -18,16 +18,20 @@
<chapter id="saml">
<title>SAML SSO</title>
<para>
Keycloak supports SAML 2.0 for registered applications. Both POST and Redirect bindings are supported. You can choose
to require client signature validation and can have the server sign and/or encrypt responses as well. We do not
yet support logout via redirects. All logouts happen via a background POST binding request to the application
that will be logged out. We do not support SAML 1.1 either. If you want support for either of those, please
Keycloak supports SAML 2.0 for registered applications. Both POST and Redirect bindings are supported. You can
choose
to require client signature validation and can have the server sign and/or encrypt responses as well. We do not
yet support logout via redirects. All logouts happen via a background POST binding request to the application
that will be logged out. We do not support SAML 1.1 either. If you want support for either of those, please
log a JIRA request and we'll schedule it.
</para>
<para>
When you create an application in the admin console, you can choose which protocol the application will log in with.
In the application create screen, choose <literal>saml</literal> from the protocol list. After that there
are a bunch of configuration options. Here is a description of each item:
When you create an application in the admin console, you can choose which protocol the application will log in
with.
In the application create screen, choose
<literal>saml</literal>
from the protocol list. After that there
are a bunch of configuration options. Here is a description of each item:
</para>
<para>
<variablelist>
@ -35,7 +39,7 @@
<term>Client ID</term>
<listitem>
<para>
This value must match the issuer value sent with AuthNRequests. Keycloak will pull the issuer
This value must match the issuer value sent with AuthNRequests. Keycloak will pull the issuer
from the Authn SAML request and match it to a client by this value.
</para>
</listitem>
@ -45,7 +49,8 @@
<listitem>
<para>
SAML login responses may specify the authentication method used (password, etc.) as well as
a timestamp of the login. Setting this to on will include that statement in the response document.
a timestamp of the login. Setting this to on will include that statement in the response
document.
</para>
</listitem>
</varlistentry>
@ -53,7 +58,8 @@
<term>Multi-valued Roles</term>
<listitem>
<para>
If this switch is off, any user role mappings will have a corresponding attribute created for it.
If this switch is off, any user role mappings will have a corresponding attribute created for
it.
If this switch is turn on, only one role attribute will be created, but it will have
multiple values within in.
</para>
@ -71,7 +77,9 @@
<term>Sign Assertions</term>
<listitem>
<para>
With the <literal>Sign Documents</literal> switch signs the whole document. With this setting
With the
<literal>Sign Documents</literal>
switch signs the whole document. With this setting
you just assign the assertions of the document.
</para>
</listitem>
@ -88,7 +96,7 @@
<term>Encrypt Assertions</term>
<listitem>
<para>
Encrypt assertions in SAML documents with the realm's private key. The AES algorithm is used
Encrypt assertions in SAML documents with the realm's private key. The AES algorithm is used
with a key size of 128 bits.
</para>
</listitem>
@ -97,8 +105,10 @@
<term>Client Signature Required</term>
<listitem>
<para>
Expect that documents coming from a client are signed. Keycloak will validate this signature
using the client keys set up in the <literal>Application Keys</literal> submenu item.
Expect that documents coming from a client are signed. Keycloak will validate this signature
using the client keys set up in the
<literal>Application Keys</literal>
submenu item.
</para>
</listitem>
</varlistentry>
@ -106,8 +116,10 @@
<term>Force POST Binding</term>
<listitem>
<para>
By default, Keycloak will respond using the initial SAML binding of the original request. By turning
on this switch, you will force Keycloak to always respond using the SAML POST Binding even if the
By default, Keycloak will respond using the initial SAML binding of the original request. By
turning
on this switch, you will force Keycloak to always respond using the SAML POST Binding even if
the
original request was the Redirect binding.
</para>
</listitem>
@ -116,9 +128,12 @@
<term>Front Channel Logout</term>
<listitem>
<para>
If true, this application requires a browser redirect to be able to perform a logout. For example,
the application may require a cookie to be reset which could only be done by a done via a redirect.
If this switch is false, then Keycloak will invoke a background SAML request to logout the application.
If true, this application requires a browser redirect to be able to perform a logout. For
example,
the application may require a cookie to be reset which could only be done by a done via a
redirect.
If this switch is false, then Keycloak will invoke a background SAML request to logout the
application.
</para>
</listitem>
</varlistentry>
@ -126,7 +141,8 @@
<term>Force Name ID Format</term>
<listitem>
<para>
If the request has a name ID policy, ignore it and used the value configured in the admin console
If the request has a name ID policy, ignore it and used the value configured in the admin
console
under Name ID Format
</para>
</listitem>
@ -135,18 +151,118 @@
<term>Name ID Format</term>
<listitem>
<para>
Name ID Format for the subject. If no name ID policy is specified in the request or if the
Force Name ID Format attribute is true, this value is used.
Name ID Format for the subject. If no name ID policy is specified in the request or if the
Force Name ID Format attribute is true, this value is used. Properties used for each of the
respective formats are defined below.
</para>
<variablelist>
<varlistentry>
<term>username</term>
<listitem>
<para>
Format:
<literal>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</literal>
</para>
<para>
Source:
<literal>UserModel.userName</literal>
property
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>email</term>
<listitem>
<para>
Format:
<literal>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</literal>
</para>
<para>
Source:
<literal>UserModel.email</literal>
property
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>transient</term>
<listitem>
<para>
Format:
<literal>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</literal>
</para>
<para>
Source:<literal>G-$randomUuid</literal>, I.E.
<literal>G-5ef5b38f-864f-41ad-82a0-04ade9139500</literal>
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>persistent</term>
<listitem>
<para>
Format:
<literal>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</literal>
</para>
<para>
The persistent identifier will be evaluated in the following order. If one step
does not yield a value, processing will continue on to the next until a value is
found.
</para>
<variablelist>
<varlistentry>
<term>
1) saml.persistent.name.id.for.$clientId
<literal>UserModel</literal> attribute
</term>
<listitem>
<para>
identifier unique to this client/user pair.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
2) saml.persistent.name.id.for.*
<literal>UserModel</literal> attribute
</term>
<listitem>
<para>
user identifier for all clients, unless otherwise overridden by a
$clientId attribute.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
3) G-$randomUuid
</term>
<listitem>
<para>
I.E.<literal>G-5ef5b38f-864f-41ad-82a0-04ade9139500</literal>. If
neither a $clientId or wildcard user attribute is found, a persistent
identifier will be generated for the given client. Note that once this
identifier has been generated, a user attribute with the key
<emphasis>saml.persistent.name.id.for.$clientId</emphasis>
will be persisted and used on all subsequent requests against the given
client.
</para>
</listitem>
</varlistentry>
</variablelist>
</listitem>
</varlistentry>
</variablelist>
</listitem>
</varlistentry>
<varlistentry>
<term>Master SAML Processing URL</term>
<listitem>
<para>
This URL will be used for all SAML requests and responsed directed to the SP. It will be used
as the Assertion Consumer Service URL and the Single Logout Service URL. If a login request
contains the Assertion Consumer Service URL, that will take precedence, but this URL must be valided
This URL will be used for all SAML requests and responsed directed to the SP. It will be used
as the Assertion Consumer Service URL and the Single Logout Service URL. If a login request
contains the Assertion Consumer Service URL, that will take precedence, but this URL must be
valided
by a registered Valid Redirect URI pattern
</para>
</listitem>
@ -186,9 +302,10 @@
</variablelist>
</para>
<para>
For login to work, Keycloak needs to be able to resolve the URL for the Assertion Consumer Service of the SP. If
you are relying on the SP to provide this URL in the login request, then you must register valid redirect uri patterns
so that this URL can be validated. You can set the Master SAML Processing URL as well, or alternatively, you can
For login to work, Keycloak needs to be able to resolve the URL for the Assertion Consumer Service of the SP. If
you are relying on the SP to provide this URL in the login request, then you must register valid redirect uri
patterns
so that this URL can be validated. You can set the Master SAML Processing URL as well, or alternatively, you can
specify the Assertion Consumer Service URL per binding.
</para>
<para>
@ -196,32 +313,43 @@
you want Keycloak to use.
</para>
<para>
One thing to note is that roles are not treated as a hierarchy. So, any role mappings will just be added
to the role attributes in the SAML document using their basic name. So, if you have multiple application roles
you might have name collisions. You can use the Scope Mapping menu item to control which role mappings are set
One thing to note is that roles are not treated as a hierarchy. So, any role mappings will just be added
to the role attributes in the SAML document using their basic name. So, if you have multiple application roles
you might have name collisions. You can use the Scope Mapping menu item to control which role mappings are set
in the response.
</para>
<section>
<title>SAML Entity Descriptor</title>
<para>
If you go into the admin console in the application list menu page you will see an <literal>Import</literal>
button. If you click on that you can import SAML Service Provider definitions using the <ulink url="http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf">Entity Descriptor</ulink>
format described in SAML 2.0. You should review all the information there to make sure everything is set up correctly.
If you go into the admin console in the application list menu page you will see an
<literal>Import</literal>
button. If you click on that you can import SAML Service Provider definitions using the
<ulink url="http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf">Entity Descriptor
</ulink>
format described in SAML 2.0. You should review all the information there to make sure everything is set up
correctly.
</para>
<para>
Each realm has a URL where you can view the XML entity descriptor for the IDP. <literal>root/auth/realms/{realm}/protocol/saml/descriptor</literal>
Each realm has a URL where you can view the XML entity descriptor for the IDP.
<literal>root/auth/realms/{realm}/protocol/saml/descriptor</literal>
</para>
</section>
<section>
<title>IDP Initiated Login</title>
<para>
IDP Initiated Login is a feature that where you can set up a URL on the Keycloak server that will log you into a specific application/client. To set this up
go to the client page in the admin console of the client you want to set this up for. Specify the <literal>IDP Initiated SSO URL Name</literal>. This is a simple string
with no whitespace in it. After this you can reference your client at the following URL: <literal>root/auth/realms/{realm}/protocol/saml/clients/{url-name}</literal>
IDP Initiated Login is a feature that where you can set up a URL on the Keycloak server that will log you
into a specific application/client. To set this up
go to the client page in the admin console of the client you want to set this up for. Specify the<literal>
IDP Initiated SSO URL Name</literal>. This is a simple string
with no whitespace in it. After this you can reference your client at the following URL:
<literal>root/auth/realms/{realm}/protocol/saml/clients/{url-name}</literal>
</para>
<para>
If your client requires a special relay state, you can also configure this in the admin console. Alternatively, you can specify the relay state in a
<literal>RelayState</literal> query parameter, i.e. : <literal>root/auth/realms/{realm}/protocol/saml/clients/{url-name}?RelayState=thestate</literal>
If your client requires a special relay state, you can also configure this in the admin console.
Alternatively, you can specify the relay state in a
<literal>RelayState</literal>
query parameter, i.e. :
<literal>root/auth/realms/{realm}/protocol/saml/clients/{url-name}?RelayState=thestate</literal>
</para>
</section>
</chapter>

View file

@ -53,7 +53,7 @@ public class ClientTemplateAdapter implements ClientTemplateModel {
private void getDelegateForUpdate() {
if (updated == null) {
cacheSession.registerApplicationInvalidation(getId());
cacheSession.registerClientTemplateInvalidation(getId());
updated = cacheSession.getDelegate().getClientTemplateById(getId(), cachedRealm);
if (updated == null) throw new IllegalStateException("Not found in database");
}

View file

@ -43,7 +43,7 @@
<apacheds.version>2.0.0-M17</apacheds.version>
<apacheds.codec.version>1.0.0-M23</apacheds.codec.version>
<org.apache.james.apache-mime4j.version>0.6</org.apache.james.apache-mime4j.version>
<bouncycastle.crypto.version>1.50</bouncycastle.crypto.version>
<bouncycastle.crypto.version>1.52</bouncycastle.crypto.version>
<jackson.version>2.5.4</jackson.version>
<apache.httpcomponents.version>4.3.6</apache.httpcomponents.version>
<apache.httpcomponents.httpcore.version>4.3.3</apache.httpcomponents.httpcore.version>

View file

@ -98,7 +98,7 @@ public abstract class AuthorizationEndpointBase {
List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
for (IdentityProviderModel identityProvider : identityProviders) {
if (identityProvider.isAuthenticateByDefault()) {
if (identityProvider.isEnabled() && identityProvider.isAuthenticateByDefault()) {
// TODO if we are isPassive we should propagate this flag to default identity provider also if possible
return buildRedirectToIdentityProvider(identityProvider.getAlias(), new ClientSessionCode(realm, clientSession).getCode());
}

View file

@ -281,16 +281,7 @@ public class SamlProtocol implements LoginProtocol {
// "G-" stands for "generated" Add this for the slight possibility of collisions.
return "G-" + UUID.randomUUID().toString();
} else if (nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get())) {
// generate a persistent user id specifically for each client.
UserModel user = userSession.getUser();
String name = SAML_PERSISTENT_NAME_ID_FOR + "." + clientSession.getClient().getClientId();
String samlPersistentId = user.getFirstAttribute(name);
if (samlPersistentId != null)
return samlPersistentId;
// "G-" stands for "generated"
samlPersistentId = "G-" + UUID.randomUUID().toString();
user.setSingleAttribute(name, samlPersistentId);
return samlPersistentId;
return getPersistentNameId(clientSession, userSession);
} else if (nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get())) {
// TODO: Support for persistent NameID (pseudo-random identifier persisted in user object)
return userSession.getUser().getUsername();
@ -299,6 +290,43 @@ public class SamlProtocol implements LoginProtocol {
}
}
/**
* Attempts to retrieve the persistent type NameId as follows:
*
* <ol>
* <li>saml.persistent.name.id.for.$clientId user attribute</li>
* <li>saml.persistent.name.id.for.* user attribute</li>
* <li>G-$randomUuid</li>
* </ol>
*
* If a randomUuid is generated, an attribute for the given saml.persistent.name.id.for.$clientId will be generated,
* otherwise no state change will occur with respect to the user's attributes.
*
* @return the user's persistent NameId
*/
protected String getPersistentNameId(final ClientSessionModel clientSession, final UserSessionModel userSession) {
// attempt to retrieve the UserID for the client-specific attribute
final UserModel user = userSession.getUser();
final String clientNameId = String.format("%s.%s", SAML_PERSISTENT_NAME_ID_FOR,
clientSession.getClient().getClientId());
String samlPersistentNameId = user.getFirstAttribute(clientNameId);
if (samlPersistentNameId != null) {
return samlPersistentNameId;
}
// check for a wildcard attribute
final String wildcardNameId = String.format("%s.*", SAML_PERSISTENT_NAME_ID_FOR);
samlPersistentNameId = user.getFirstAttribute(wildcardNameId);
if (samlPersistentNameId != null) {
return samlPersistentNameId;
}
// default to generated. "G-" stands for "generated"
samlPersistentNameId = "G-" + UUID.randomUUID().toString();
user.setSingleAttribute(clientNameId, samlPersistentNameId);
return samlPersistentNameId;
}
@Override
public Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode) {
ClientSessionModel clientSession = accessCode.getClientSession();

View file

@ -40,6 +40,14 @@
<module>wildfly</module>
</modules>
</profile>
<profile>
<id>auth-server-wildfly-cluster</id>
<modules>
<module>wildfly</module>
<module>wildfly-balancer</module>
</modules>
</profile>
<profile>
<id>auth-server-eap7</id>
<modules>

View file

@ -0,0 +1,46 @@
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<assembly>
<id>wildfly-balancer</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>${wildfly.balancer.home}</directory>
<outputDirectory>wildfly-balancer-${project.version}</outputDirectory>
<excludes>
<exclude>**/*.sh</exclude>
</excludes>
</fileSet>
<fileSet>
<directory>${wildfly.balancer.home}</directory>
<outputDirectory>wildfly-balancer-${project.version}</outputDirectory>
<includes>
<include>**/*.sh</include>
</includes>
<fileMode>0755</fileMode>
</fileSet>
</fileSets>
</assembly>

View file

@ -0,0 +1,118 @@
<?xml version="1.0"?>
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<parent>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-servers</artifactId>
<version>1.9.0.Final-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>integration-arquillian-server-wildfly-balancer</artifactId>
<packaging>pom</packaging>
<name>Wildfly Load Balancer</name>
<properties>
<wildfly.balancer.home>${project.build.directory}/unpacked/wildfly-${wildfly.version}</wildfly.balancer.home>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>unpack-wildfly</id>
<phase>generate-resources</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.wildfly</groupId>
<artifactId>wildfly-dist</artifactId>
<version>${wildfly.version}</version>
<type>zip</type>
<outputDirectory>${project.build.directory}/unpacked</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>xml-maven-plugin</artifactId>
<executions>
<execution>
<id>configure-mod-cluster</id>
<phase>process-resources</phase>
<goals>
<goal>transform</goal>
</goals>
<configuration>
<transformationSets>
<transformationSet>
<dir>${wildfly.balancer.home}/standalone/configuration</dir>
<includes>
<include>standalone.xml</include>
</includes>
<stylesheet>src/main/xslt/mod_cluster.xsl</stylesheet>
<outputDir>${wildfly.balancer.home}/standalone/configuration</outputDir>
</transformationSet>
</transformationSets>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>create-zip</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>assembly.xml</descriptor>
</descriptors>
<appendAssemblyId>false</appendAssemblyId>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,73 @@
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xalan="http://xml.apache.org/xalan"
xmlns:s="urn:jboss:domain:4.0"
xmlns:u="urn:jboss:domain:undertow:3.0"
version="2.0"
exclude-result-prefixes="xalan j u">
<xsl:param name="config"/>
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
<xsl:strip-space elements="*"/>
<!--enable mod_cluster extension-->
<xsl:template match="//s:extensions">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
<extension module="org.jboss.as.modcluster"/>
</xsl:copy>
</xsl:template>
<!--add filter-ref-->
<xsl:template match="//u:server[@name='default-server']/u:host[@name='default-host']">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
<filter-ref name="modcluster"/>
</xsl:copy>
</xsl:template>
<!--add filter-->
<xsl:template match="//u:filters">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
<mod-cluster
name="modcluster"
advertise-socket-binding="modcluster"
management-socket-binding="http"
enable-http2="true"
/>
</xsl:copy>
</xsl:template>
<!--add socket binding-->
<xsl:template match="//s:socket-binding-group[@name='standard-sockets']">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
<socket-binding name="modcluster" port="23364" multicast-address="224.0.1.105"/>
</xsl:copy>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

View file

@ -1,20 +1,20 @@
<?xml version="1.0"?>
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
@ -254,16 +254,16 @@
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${keycloak.server.home}/standalone/configuration</outputDirectory>
<resources>
<resource>
<directory>src/main/keystore</directory>
<includes>
<include>keycloak.jks</include>
<include>keycloak.truststore</include>
</includes>
</resource>
</resources>
<outputDirectory>${keycloak.server.home}/standalone/configuration</outputDirectory>
<resources>
<resource>
<directory>src/main/keystore</directory>
<includes>
<include>keycloak.jks</include>
<include>keycloak.truststore</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
@ -412,5 +412,50 @@
</plugins>
</build>
</profile>
<profile>
<id>auth-server-wildfly-cluster</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>xml-maven-plugin</artifactId>
<executions>
<execution>
<id>configure-wildfly-datasource</id>
<phase>process-resources</phase>
<goals>
<goal>transform</goal>
</goals>
<configuration>
<transformationSets>
<!-- point KeycloakDS datasource to H2 running on TCP port -->
<transformationSet>
<dir>${keycloak.server.home}/standalone/configuration</dir>
<includes>
<include>standalone-ha.xml</include>
</includes>
<stylesheet>src/main/xslt/datasource-jdbc-url.xsl</stylesheet>
<outputDir>${keycloak.server.home}/standalone/configuration</outputDir>
<parameters>
<parameter>
<name>pool.name</name>
<value>KeycloakDS</value>
</parameter>
<parameter>
<name>jdbc.url</name>
<value>jdbc:h2:tcp://${jboss.bind.address:localhost}:9092/mem:keycloak;DB_CLOSE_DELAY=-1</value>
</parameter>
</parameters>
</transformationSet>
</transformationSets>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View file

@ -0,0 +1,36 @@
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xalan="http://xml.apache.org/xalan"
xmlns:j="urn:jboss:domain:4.0"
xmlns:ds="urn:jboss:domain:datasources:4.0"
xmlns:k="urn:jboss:domain:keycloak:1.1"
xmlns:sec="urn:jboss:domain:security:1.2"
version="2.0"
exclude-result-prefixes="xalan j ds k sec">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="nsDS" select="'urn:jboss:domain:datasources:'"/>
<xsl:param name="pool.name" select="'KeycloakDS'"/>
<xsl:param name="jdbc.url" />
<!-- replace JDBC URL -->
<xsl:template match="//*[local-name()='subsystem' and starts-with(namespace-uri(), $nsDS)]
/*[local-name()='datasources' and starts-with(namespace-uri(), $nsDS)]
/*[local-name()='datasource' and starts-with(namespace-uri(), $nsDS) and @pool-name=$pool.name]
/*[local-name()='connection-url' and starts-with(namespace-uri(), $nsDS)]">
<connection-url>
<xsl:value-of select="$jdbc.url"/>
</connection-url>
</xsl:template>
<!-- Copy everything else. -->
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

View file

@ -35,7 +35,10 @@
<exclude.console>-</exclude.console>
<exclude.account>-</exclude.account>
<exclude.client>-</exclude.client>
<exclude.migration>-</exclude.migration>
<!--exclude migration tests by default, enabled by 'migration' profile in tests/pom.xml-->
<exclude.migration>**/migration/**/*Test.java</exclude.migration>
<!--exclude cluster tests by default, enabled by 'auth-server-*-cluster' profiles in tests/pom.xml-->
<exclude.cluster>**/cluster/**/*Test.java</exclude.cluster>
</properties>
<dependencies>
@ -75,6 +78,7 @@
<exclude>${exclude.account}</exclude>
<exclude>${exclude.client}</exclude>
<exclude>${exclude.migration}</exclude>
<exclude>${exclude.cluster}</exclude>
</excludes>
</configuration>
</plugin>
@ -108,29 +112,4 @@
</build>
<profiles>
<profile>
<id>no-account</id>
<properties>
<!-- Exclude all account management tests. -->
<exclude.account>**/account/**/*Test.java</exclude.account>
</properties>
</profile>
<profile>
<id>no-client</id>
<properties>
<!-- Exclude all client tests. -->
<exclude.client>**/client/**/*Test.java</exclude.client>
</properties>
</profile>
<profile>
<id>adapters-only</id>
<properties>
<exclude.account>**/account/**/*Test.java</exclude.account>
<exclude.client>**/client/**/*Test.java</exclude.client>
<exclude.migration>**/migration/**/*Test.java</exclude.migration>
</properties>
</profile>
</profiles>
</project>

View file

@ -40,9 +40,10 @@ public class AppServerTestEnricher {
String appServerQ = (annotatedClass == null ? null
: annotatedClass.getAnnotation(AppServerContainer.class).value());
return appServerQ == null || appServerQ.isEmpty()
? getAuthServerQualifier() // app server == auth server
: appServerQ;
return annotatedClass == null ? null // no @AppServerContainer annotation --> no adapter test
: (appServerQ == null || appServerQ.isEmpty() // @AppServerContainer annotation present but qualifier not set --> relative adapter test
? getAuthServerQualifier() // app server == auth server
: appServerQ);
}
public static String getAppServerContextRoot() {
@ -129,7 +130,7 @@ public class AppServerTestEnricher {
String jbossHomePath = appServerInfo.getProperties().get("jbossHome");
File bin = new File(jbossHomePath + "/bin");
File clientJar = new File(jbossHomePath + "/bin/client/jboss-cli-client.jar");
if (!clientJar.exists()) {
clientJar = new File(jbossHomePath + "/bin/client/jboss-client.jar"); // AS7
@ -137,7 +138,7 @@ public class AppServerTestEnricher {
if (!clientJar.exists()) {
throw new IOException("JBoss CLI client JAR not found.");
}
String command = "java -jar " + clientJar.getAbsolutePath();
String adapterScript = "adapter-install.cli";
String samlAdapterScript = "adapter-install-saml.cli";

View file

@ -110,7 +110,10 @@ public class AuthServerTestEnricher {
boolean authServerCluster = authServerQualifier.endsWith("-cluster");
String authServerType = authServerQualifier.replaceAll("^auth-server-", "").replaceAll("-cluster$", "");
String authServerType = authServerQualifier.replaceAll("auth-server-", "").replaceAll("-cluster", "");
log.info("authServerType:" + authServerType);
String authServerFrontend = authServerCluster
? "auth-server-" + authServerType + "-balancer" // in cluster mode the load-balancer container serves as auth server frontend
: authServerQualifier; // single-node mode
@ -133,7 +136,7 @@ public class AuthServerTestEnricher {
if (suiteContext.getAuthServerInfo() == null) {
throw new RuntimeException(String.format("No auth server activated. A container matching '%s' needs to be enabled in arquillian.xml.", authServerFrontend));
}
if (authServerCluster && !suiteContext.getAuthServerBackendsInfo().isEmpty()) {
if (authServerCluster && suiteContext.getAuthServerBackendsInfo().isEmpty()) {
throw new RuntimeException(String.format("No cluster backend nodes activated. Containers matching '%sN' need to be enabled in arquillian.xml.", authServerBackend));
}

View file

@ -26,6 +26,7 @@ import org.jboss.arquillian.core.spi.LoadableExtension;
import org.jboss.arquillian.graphene.location.CustomizableURLResourceProvider;
import org.jboss.arquillian.test.spi.enricher.resource.ResourceProvider;
import org.jboss.arquillian.test.spi.execution.TestExecutionDecider;
import org.keycloak.testsuite.arquillian.h2.H2TestEnricher;
import org.keycloak.testsuite.arquillian.migration.MigrationTestExecutionDecider;
import org.keycloak.testsuite.arquillian.undertow.CustomUndertowContainer;
@ -48,7 +49,8 @@ public class KeycloakArquillianExtension implements LoadableExtension {
.service(DeploymentScenarioGenerator.class, DeploymentTargetModifier.class)
.service(ApplicationArchiveProcessor.class, DeploymentArchiveProcessor.class)
.observer(AuthServerTestEnricher.class)
.observer(AppServerTestEnricher.class);
.observer(AppServerTestEnricher.class)
.observer(H2TestEnricher.class);
builder
.service(DeployableContainer.class, CustomUndertowContainer.class);

View file

@ -93,9 +93,13 @@ public final class SuiteContext {
@Override
public String toString() {
String containers = "Auth server: " + (isAuthServerCluster() ? "\nFrontend: " : "")
+ authServerInfo.getQualifier() + "\n";
for (ContainerInfo bInfo : getAuthServerBackendsInfo()) {
containers += "Backend: " + bInfo + "\n";
}
return "SUITE CONTEXT:\n"
+ "Auth server: " + authServerInfo.getQualifier() + "\n"
+(isAuthServerCluster() ? "Auth server cluster: " + getAuthServerBackendsInfo().size() + " nodes+\n" : "");
+ containers;
}
}

View file

@ -0,0 +1,40 @@
package org.keycloak.testsuite.arquillian.h2;
import java.sql.SQLException;
import org.jboss.arquillian.core.api.annotation.Observes;
import org.jboss.arquillian.test.spi.event.suite.AfterSuite;
import org.jboss.arquillian.test.spi.event.suite.BeforeSuite;
import org.jboss.logging.Logger;
import org.h2.tools.Server;
/**
* Starts H2 before suite and stops it after.
*
* @author tkyjovsk
*/
public class H2TestEnricher {
protected final Logger log = Logger.getLogger(this.getClass());
boolean runH2 = Boolean.parseBoolean(System.getProperty("run.h2", "false"));
private Server server = null;
public void startH2(@Observes(precedence = 2) BeforeSuite event) throws SQLException {
if (runH2) {
log.info("Starting H2 database.");
server = Server.createTcpServer();
server.start();
log.info(String.format("URL: %s", server.getURL()));
}
}
public void stopH2(@Observes(precedence = -2) AfterSuite event) {
if (runH2 && server.isRunning(false)) {
log.info("Stopping H2 database.");
server.stop();
assert !server.isRunning(false);
}
}
}

View file

@ -1,34 +0,0 @@
package org.keycloak.testsuite;
import java.util.List;
import org.jboss.arquillian.container.test.api.ContainerController;
import org.jboss.arquillian.test.api.ArquillianResource;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
/**
*
* @author tkyjovsk
*/
public class ContainersTest extends AbstractKeycloakTest {
@ArquillianResource
ContainerController controller;
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
}
@Test
public void testAuthServer() {
log.info("AUTH SERVER should be started.");
assertTrue(controller.isStarted(AuthServerTestEnricher.getAuthServerQualifier()));
}
}

View file

@ -0,0 +1,59 @@
package org.keycloak.testsuite.cluster;
import java.util.ArrayList;
import java.util.List;
import org.jboss.arquillian.container.test.api.ContainerController;
import org.jboss.arquillian.test.api.ArquillianResource;
import static org.junit.Assert.assertTrue;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.models.Constants;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.arquillian.ContainerInfo;
import static org.keycloak.testsuite.auth.page.AuthRealm.ADMIN;
import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER;
/**
*
* @author tkyjovsk
*/
public abstract class AbstractClusterTest extends AbstractKeycloakTest {
@ArquillianResource
protected ContainerController controller;
protected List<Keycloak> backendAdminClients = new ArrayList<>();
public void startBackendNodes(int count) {
if (count < 0 || count > 10) {
throw new IllegalArgumentException();
}
assertTrue(suiteContext.getAuthServerBackendsInfo().size() >= count);
for (int i = 0; i < count; i++) {
ContainerInfo backendNode = suiteContext.getAuthServerBackendsInfo().get(i);
controller.start(backendNode.getQualifier());
assertTrue(controller.isStarted(backendNode.getQualifier()));
log.info("Initializing admin client for: '" + backendNode.getContextRoot() + "/auth'");
backendAdminClients.add(Keycloak.getInstance(backendNode.getContextRoot() + "/auth",
MASTER, ADMIN, ADMIN, Constants.ADMIN_CLI_CLIENT_ID));
}
}
protected ContainerInfo backendInfo(int i) {
return suiteContext.getAuthServerBackendsInfo().get(i);
}
protected void startBackendNode(int i) {
String container = backendInfo(i).getQualifier();
if (!controller.isStarted(container)) {
controller.start(container);
}
}
protected void stopBackendNode(int i) {
controller.kill(backendInfo(i).getQualifier());
}
}

View file

@ -0,0 +1,131 @@
package org.keycloak.testsuite.cluster;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.arquillian.ContainerInfo;
import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
import static org.keycloak.testsuite.util.WaitUtils.pause;
/**
*
* @author tkyjovsk
*/
public class TwoNodeClusterTest extends AbstractClusterTest {
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
}
@Before
public void beforeTwoNodeClusterTest() {
startBackendNodes(2);
pause(3000);
}
@Test
public void testRealm() {
testRealm(TEST, false);
}
@Test
public void testRealmWithFailover() {
testRealm(TEST + "_fo", true);
}
public void testRealm(String realm, boolean containerFailover) {
RealmRepresentation testRealm = new RealmRepresentation();
testRealm.setRealm(realm);
testRealm.setEnabled(true);
// CREATE on node1
log.info("Creating test realm via node1.");
backend1AdminClient().realms().create(testRealm);
log.info("Test realm created.");
// check if created on node1
RealmRepresentation testRealmOnBackend1 = backend1AdminClient().realms().realm(realm).toRepresentation();
assertEquals(testRealmOnBackend1.getRealm(), testRealm.getRealm());
if (containerFailover) {
stopBackend1();
}
// check if created on node2
RealmRepresentation testRealmOnBackend2 = backend2AdminClient().realms().realm(realm).toRepresentation();
assertEquals(testRealmOnBackend1.getId(), testRealmOnBackend2.getId());
assertEquals(testRealmOnBackend1.getRealm(), testRealmOnBackend2.getRealm());
failback();
// UPDATE on node2
testRealmOnBackend2.setRealm(realm + "_updated");
backend2AdminClient().realms().realm(realm).update(testRealmOnBackend2);
if (containerFailover) {
stopBackend2();
}
// check if updated on node1
testRealmOnBackend1 = backend1AdminClient().realms().realm(realm).toRepresentation();
assertEquals(testRealmOnBackend1.getId(), testRealmOnBackend2.getId());
assertEquals(testRealmOnBackend1.getRealm(), testRealmOnBackend2.getRealm());
failback();
// DELETE on node1
backend1AdminClient().realms().realm(realm).remove();
if (containerFailover) {
stopBackend1();
}
// check if deleted on node2
boolean testRealmOnBackend2Exists = false;
for (RealmRepresentation realmOnBackend2 : backend2AdminClient().realms().findAll()) {
if (realm.equals(realmOnBackend2.getRealm())
|| testRealmOnBackend1.getId().equals(realmOnBackend2.getId())) {
testRealmOnBackend2Exists = true;
break;
}
}
assertFalse(testRealmOnBackend2Exists);
}
protected ContainerInfo backend1Info() {
return backendInfo(0);
}
protected ContainerInfo backend2Info() {
return backendInfo(1);
}
protected Keycloak backend1AdminClient() {
return backendAdminClients.get(0);
}
protected Keycloak backend2AdminClient() {
return backendAdminClients.get(1);
}
protected void startBackend1() {
startBackendNode(0);
}
protected void startBackend2() {
startBackendNode(1);
}
protected void failback() {
startBackend1();
startBackend2();
}
protected void stopBackend1() {
stopBackendNode(0);
}
protected void stopBackend2() {
stopBackendNode(1);
}
}

View file

@ -71,7 +71,7 @@
<configuration>
<property name="enabled">${auth.server.wildfly.cluster}</property>
<property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
<property name="jbossHome">${wildfly.home}</property>
<property name="jbossHome">${keycloak.balancer.home}</property>
<property name="javaVmArguments">
-Djboss.socket.binding.port-offset=${auth.server.port.offset}
-Xms64m -Xmx512m -XX:MaxPermSize=256m
@ -86,15 +86,35 @@
<property name="enabled">${auth.server.wildfly.cluster}</property>
<property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
<property name="jbossHome">${keycloak.backend1.home}</property>
<property name="serverConfig">standalone-ha.xml</property>
<property name="javaVmArguments">
-Djboss.socket.binding.port-offset=${auth.server.backend1.port.offset}
-Xms64m -Xmx512m -XX:MaxPermSize=256m
${adapter.test.props}
-Djboss.node.name=node1
</property>
<!--<property name="outputToConsole">false</property>-->
<property name="managementPort">${auth.server.backend1.management.port}</property>
<property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
</configuration>
</container>
<container qualifier="auth-server-wildfly-backend2" mode="manual" >
<configuration>
<property name="enabled">${auth.server.wildfly.cluster}</property>
<property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
<property name="jbossHome">${keycloak.backend2.home}</property>
<property name="serverConfig">standalone-ha.xml</property>
<property name="javaVmArguments">
-Djboss.socket.binding.port-offset=${auth.server.backend2.port.offset}
-Xms64m -Xmx512m -XX:MaxPermSize=256m
${adapter.test.props}
-Djboss.node.name=node2
</property>
<!--<property name="outputToConsole">false</property>-->
<property name="managementPort">${auth.server.backend2.management.port}</property>
<property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
</configuration>
</container>
</group>
<container qualifier="auth-server-eap7" mode="suite" >

View file

@ -159,7 +159,27 @@
</build>
<profiles>
<profile>
<id>no-account</id>
<properties>
<exclude.account>**/account/**/*Test.java</exclude.account>
</properties>
</profile>
<profile>
<id>no-client</id>
<properties>
<exclude.client>**/client/**/*Test.java</exclude.client>
</properties>
</profile>
<profile>
<id>no-base</id>
<properties>
<exclude.account>**/account/**/*Test.java</exclude.account>
<exclude.client>**/client/**/*Test.java</exclude.client>
</properties>
</profile>
<profile>
<id>common-test-dependencies</id>
<activation>
@ -348,6 +368,7 @@
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
@ -398,6 +419,7 @@
<adapter.test.props/>
<keycloak.home>${containers.home}/keycloak-${project.version}</keycloak.home>
<jboss.server.config.dir>${keycloak.home}/standalone/configuration</jboss.server.config.dir>
<h2.version>1.3.173</h2.version>
</properties>
<dependencies>
<dependency>
@ -450,6 +472,111 @@
</build>
</profile>
<profile>
<id>auth-server-wildfly-cluster</id>
<properties>
<!--disable exclusion pattern for cluster test which is enabled by default in base/pom.xml-->
<exclude.cluster>-</exclude.cluster>
<auth.server.container>auth-server-wildfly-cluster</auth.server.container>
<startup.timeout.sec>300</startup.timeout.sec>
<adapter.test.props/>
<h2.version>1.3.173</h2.version>
<keycloak.balancer.home>${containers.home}/balancer/wildfly-balancer-${project.version}</keycloak.balancer.home>
<keycloak.backend1.home>${containers.home}/node1/keycloak-${project.version}</keycloak.backend1.home>
<keycloak.backend2.home>${containers.home}/node2/keycloak-${project.version}</keycloak.backend2.home>
<keycloak.home>${keycloak.backend1.home}</keycloak.home>
<jboss.server.config.dir>${keycloak.home}/standalone/configuration</jboss.server.config.dir>
</properties>
<dependencies>
<dependency>
<groupId>org.wildfly</groupId>
<artifactId>wildfly-arquillian-container-managed</artifactId>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<run.h2>true</run.h2>
<auth.server.wildfly.cluster>true</auth.server.wildfly.cluster>
<auth.server.undertow>false</auth.server.undertow>
<adapter.test.props>${adapter.test.props}</adapter.test.props>
<keycloak.balancer.home>${keycloak.balancer.home}</keycloak.balancer.home>
<keycloak.backend1.home>${keycloak.backend1.home}</keycloak.backend1.home>
<keycloak.backend2.home>${keycloak.backend2.home}</keycloak.backend2.home>
<!--100-->
<auth.server.backend1.port.offset>101</auth.server.backend1.port.offset>
<auth.server.backend2.port.offset>102</auth.server.backend2.port.offset>
<!--8180-->
<auth.server.backend1.http.port>8181</auth.server.backend1.http.port>
<auth.server.backend2.http.port>8182</auth.server.backend2.http.port>
<!--8543-->
<auth.server.backend1.https.port>8544</auth.server.backend1.https.port>
<auth.server.backend2.https.port>8545</auth.server.backend2.https.port>
<!--10090-->
<auth.server.backend1.management.port>10091</auth.server.backend1.management.port>
<auth.server.backend2.management.port>10092</auth.server.backend2.management.port>
<!--10099-->
<auth.server.backend1.management.port.jmx>10100</auth.server.backend1.management.port.jmx>
<auth.server.backend2.management.port.jmx>10101</auth.server.backend2.management.port.jmx>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>unpack-auth-server-wildfly</id>
<phase>generate-test-resources</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-server-wildfly-balancer</artifactId>
<version>${project.version}</version>
<type>zip</type>
<outputDirectory>${containers.home}/balancer</outputDirectory>
</artifactItem>
<artifactItem>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-server-wildfly</artifactId>
<version>${project.version}</version>
<type>zip</type>
<outputDirectory>${containers.home}/node1</outputDirectory>
</artifactItem>
<artifactItem>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-server-wildfly</artifactId>
<version>${project.version}</version>
<type>zip</type>
<outputDirectory>${containers.home}/node2</outputDirectory>
</artifactItem>
</artifactItems>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
</profile>
<profile>
<id>auth-server-eap7</id>
<properties>
@ -538,6 +665,10 @@
<name>migrated.auth.server.version</name>
</property>
</activation>
<properties>
<!--diable exclusion pattern for migration tests, which is enabled by default in the base/pom.xml-->
<exclude.migration>-</exclude.migration>
</properties>
<build>
<plugins>
<plugin>
@ -743,7 +874,7 @@
</pluginManagement>
</build>
</profile>
</profiles>
</project>

View file

@ -831,6 +831,7 @@ additional-grants=Additional Grants
revoke=Revoke
new-password=New Password
password-confirmation=Password Confirmation
reset-password=Reset Password
credentials.temporary.tooltip=If enabled user is required to change password on next login
remove-totp=Remove TOTP
credentials.remove-totp.tooltip=Remove one time password generator for user.

View file

@ -6,6 +6,8 @@ consoleBaseUrl = consoleBaseUrl + "/console";
var configUrl = consoleBaseUrl + "/config";
var auth = {};
var resourceBundle;
var locale = 'en';
var module = angular.module('keycloak', [ 'keycloak.services', 'keycloak.loaders', 'ui.bootstrap', 'ui.select2', 'angularFileUpload', 'angularTreeview', 'pascalprecht.translate', 'ngCookies', 'ngSanitize']);
var resourceRequests = 0;
@ -34,6 +36,25 @@ angular.element(document).ready(function () {
req.send();
}
function loadResourceBundle(success, error) {
var req = new XMLHttpRequest();
req.open('GET', consoleBaseUrl + '/messages.json?lang=' + locale, true);
req.setRequestHeader('Accept', 'application/json');
req.onreadystatechange = function () {
if (req.readyState == 4) {
if (req.status == 200) {
var data = JSON.parse(req.responseText);
success && success(data);
} else {
error && error();
}
}
}
req.send();
}
function hasAnyAccess(user) {
return user && user['realm_access'];
}
@ -45,6 +66,10 @@ angular.element(document).ready(function () {
keycloakAuth.init({ onLoad: 'login-required' }).success(function () {
auth.authz = keycloakAuth;
if (auth.authz.idTokenParsed.locale) {
locale = auth.authz.idTokenParsed.locale;
}
auth.refreshPermissions = function(success, error) {
whoAmI(function(data) {
auth.user = data;
@ -57,17 +82,19 @@ angular.element(document).ready(function () {
});
};
auth.refreshPermissions(function() {
module.factory('Auth', function() {
return auth;
});
var injector = angular.bootstrap(document, ["keycloak"]);
loadResourceBundle(function(data) {
resourceBundle = data;
injector.get('$translate')('consoleTitle').then(function(consoleTitle) {
document.title=consoleTitle;
auth.refreshPermissions(function () {
module.factory('Auth', function () {
return auth;
});
var injector = angular.bootstrap(document, ["keycloak"]);
injector.get('$translate')('consoleTitle').then(function (consoleTitle) {
document.title = consoleTitle;
});
});
}, function() {
window.location.reload();
});
}).error(function () {
window.location.reload();
@ -99,15 +126,8 @@ module.factory('authInterceptor', function($q, Auth) {
module.config(['$translateProvider', function($translateProvider) {
$translateProvider.useSanitizeValueStrategy('sanitizeParameters');
var locale = auth.authz.idTokenParsed.locale;
if (locale !== undefined) {
$translateProvider.preferredLanguage(locale);
} else {
$translateProvider.preferredLanguage('en');
}
$translateProvider.useUrlLoader('messages.json');
$translateProvider.preferredLanguage(locale);
$translateProvider.translations(locale, resourceBundle);
}]);
module.config([ '$routeProvider', function($routeProvider) {

View file

@ -25,19 +25,19 @@ module.controller('GlobalCtrl', function($scope, $http, Auth, Current, $location
},
get viewRealm() {
return getAccess('view-realm') || this.manageRealm;
return getAccess('view-realm') || getAccess('manage-realm') || this.manageRealm;
},
get viewClients() {
return getAccess('view-clients') || this.manageClients;
return getAccess('view-clients') || getAccess('manage-clients') || this.manageClients;
},
get viewUsers() {
return getAccess('view-users') || this.manageClients;
return getAccess('view-users') || getAccess('manage-users') || this.manageClients;
},
get viewEvents() {
return getAccess('view-events') || this.manageClients;
return getAccess('view-events') || getAccess('manage-events') || this.manageClients;
},
get manageRealm() {