Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Bill Burke 2015-03-23 13:21:28 -04:00
commit 5a7cefb198
390 changed files with 968 additions and 290 deletions

View file

@ -9,7 +9,7 @@ It can be used for social applications as well as enterprise applications. It i
Here's some of the features:
* SSO and Single Log Out for browser applications
* Social Broker. Enable Google, Facebook, Yahoo, Twitter social login with no code required.
* Social Broker. Enable Google, Facebook, Yahoo, Twitter, GitHub, LinkedIn social login with no code required.
* Optional LDAP/Active Directory integration
* Optional User Registration
* Password and TOTP support (via Google Authenticator or FreeOTP). Client cert auth coming soon.

View file

@ -87,10 +87,13 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
boolean clustered = config.getBoolean("clustered", false);
boolean async = config.getBoolean("async", true);
boolean allowDuplicateJMXDomains = config.getBoolean("allowDuplicateJMXDomains", true);
if (clustered) {
gcb.transport().defaultTransport();
}
gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
cacheManager = new DefaultCacheManager(gcb.build());
containerManaged = false;

View file

@ -1,6 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<changeSet author="psilva@redhat.com" id="1.2.0.Beta1">
<delete tableName="CLIENT_SESSION_ROLE"/>
<delete tableName="CLIENT_SESSION_NOTE"/>
<delete tableName="CLIENT_SESSION"/>
<delete tableName="USER_SESSION"/>
<createTable tableName="PROTOCOL_MAPPER">
<column name="ID" type="VARCHAR(36)">
<constraints nullable="false"/>
@ -14,8 +19,12 @@
<column name="PROTOCOL_MAPPER_NAME" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="APPLIED_BY_DEFAULT" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="CONSENT_REQUIRED" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="APPLIED_BY_DEFAULT" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="CONSENT_REQUIRED" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="CONSENT_TEXT" type="VARCHAR(255)"/>
<column name="CLIENT_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
@ -46,12 +55,20 @@
<column name="INTERNAL_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="ENABLED" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="ENABLED" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="PROVIDER_ALIAS" type="VARCHAR(255)"/>
<column name="PROVIDER_ID" type="VARCHAR(255)"/>
<column name="UPDATE_PROFILE_FIRST_LOGIN" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="STORE_TOKEN" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="AUTHENTICATE_BY_DEFAULT" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="UPDATE_PROFILE_FIRST_LOGIN" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="STORE_TOKEN" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="AUTHENTICATE_BY_DEFAULT" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="REALM_ID" type="VARCHAR(36)"/>
</createTable>
<createTable tableName="IDENTITY_PROVIDER_CONFIG">
@ -63,14 +80,16 @@
<constraints nullable="false"/>
</column>
</createTable>
<createTable tableName="CLIENT_IDENTITY_PROVIDER_MAPPING">
<createTable tableName="CLIENT_IDENTITY_PROV_MAPPING">
<column name="CLIENT_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="IDENTITY_PROVIDER_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="RETRIEVE_TOKEN" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="RETRIEVE_TOKEN" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
</createTable>
<createTable tableName="REALM_SUPPORTED_LOCALES">
<column name="REALM_ID" type="VARCHAR(36)">
@ -78,30 +97,61 @@
</column>
<column name="VALUE" type="VARCHAR(255)"/>
</createTable>
<createTable tableName="USER_SESSION_NOTE">
<column name="USER_SESSION" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="NAME" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="VALUE" type="VARCHAR(2048)"/>
</createTable>
<addColumn tableName="CLIENT">
<column name="FRONTCHANNEL_LOGOUT" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="FRONTCHANNEL_LOGOUT" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
</addColumn>
<addColumn tableName="USER_SESSION">
<column name="USER_SESSION_STATE" type="INT" />
</addColumn>
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_PCM" tableName="PROTOCOL_MAPPER"/>
<addPrimaryKey columnNames="INTERNAL_ID" constraintName="CONSTRAINT_2B" tableName="IDENTITY_PROVIDER"/>
<addPrimaryKey columnNames="IDENTITY_PROVIDER, USER_ID" constraintName="CONSTRAINT_40" tableName="FEDERATED_IDENTITY"/>
<addPrimaryKey columnNames="IDENTITY_PROVIDER_ID, NAME" constraintName="CONSTRAINT_D" tableName="IDENTITY_PROVIDER_CONFIG"/>
<addPrimaryKey columnNames="PROTOCOL_MAPPER_ID, NAME" constraintName="CONSTRAINT_PMConfig" tableName="PROTOCOL_MAPPER_CONFIG"/>
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="IDENTITY_PROVIDER" constraintName="FK2B4EBC52AE5C3B34" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="REALM"/>
<addForeignKeyConstraint baseColumnNames="CLIENT_ID" baseTableName="PROTOCOL_MAPPER" constraintName="FK_PCM_REALM" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="CLIENT"/>
<addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="FEDERATED_IDENTITY" constraintName="FK404288B92EF007A6" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
<addForeignKeyConstraint baseColumnNames="IDENTITY_PROVIDER_ID" baseTableName="IDENTITY_PROVIDER_CONFIG" constraintName="FKDC4897CF864C4E43" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="INTERNAL_ID" referencedTableName="IDENTITY_PROVIDER"/>
<addForeignKeyConstraint baseColumnNames="PROTOCOL_MAPPER_ID" baseTableName="PROTOCOL_MAPPER_CONFIG" constraintName="FK_PMConfig" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="PROTOCOL_MAPPER"/>
<addForeignKeyConstraint baseColumnNames="IDENTITY_PROVIDER_ID" baseTableName="CLIENT_IDENTITY_PROVIDER_MAPPING" constraintName="FK_7CELWNIBJI49AVXSRTUF6XJ12" referencedColumnNames="INTERNAL_ID" referencedTableName="IDENTITY_PROVIDER"/>
<addForeignKeyConstraint baseColumnNames="CLIENT_ID" baseTableName="CLIENT_IDENTITY_PROVIDER_MAPPING" constraintName="FK_56ELWNIBJI49AVXSRTUF6XJ23" referencedColumnNames="ID" referencedTableName="CLIENT"/>
<addPrimaryKey columnNames="USER_SESSION, NAME" constraintName="CONSTRAINT_USN_PK" tableName="USER_SESSION_NOTE"/>
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="IDENTITY_PROVIDER" constraintName="FK2B4EBC52AE5C3B34" referencedColumnNames="ID" referencedTableName="REALM"/>
<addForeignKeyConstraint baseColumnNames="CLIENT_ID" baseTableName="PROTOCOL_MAPPER" constraintName="FK_PCM_REALM" referencedColumnNames="ID" referencedTableName="CLIENT"/>
<addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="FEDERATED_IDENTITY" constraintName="FK404288B92EF007A6" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
<addForeignKeyConstraint baseColumnNames="IDENTITY_PROVIDER_ID" baseTableName="IDENTITY_PROVIDER_CONFIG" constraintName="FKDC4897CF864C4E43" referencedColumnNames="INTERNAL_ID" referencedTableName="IDENTITY_PROVIDER"/>
<addForeignKeyConstraint baseColumnNames="PROTOCOL_MAPPER_ID" baseTableName="PROTOCOL_MAPPER_CONFIG" constraintName="FK_PMConfig" referencedColumnNames="ID" referencedTableName="PROTOCOL_MAPPER"/>
<addForeignKeyConstraint baseColumnNames="IDENTITY_PROVIDER_ID" baseTableName="CLIENT_IDENTITY_PROV_MAPPING" constraintName="FK_7CELWNIBJI49AVXSRTUF6XJ12" referencedColumnNames="INTERNAL_ID" referencedTableName="IDENTITY_PROVIDER"/>
<addForeignKeyConstraint baseColumnNames="CLIENT_ID" baseTableName="CLIENT_IDENTITY_PROV_MAPPING" constraintName="FK_56ELWNIBJI49AVXSRTUF6XJ23" referencedColumnNames="ID" referencedTableName="CLIENT"/>
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_SUPPORTED_LOCALES" constraintName="FK_SUPPORTED_LOCALES_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
<addForeignKeyConstraint baseColumnNames="USER_SESSION" baseTableName="USER_SESSION_NOTE" constraintName="FK5EDFB00FF51D3472" referencedColumnNames="ID" referencedTableName="USER_SESSION"/>
<addUniqueConstraint columnNames="PROVIDER_ALIAS, REALM_ID" constraintName="UK_2DAELWNIBJI49AVXSRTUF6XJ33" tableName="IDENTITY_PROVIDER"/>
<addUniqueConstraint columnNames="IDENTITY_PROVIDER_ID,CLIENT_ID" constraintName="UK_7CAELWNIBJI49AVXSRTUF6XJ12" tableName="CLIENT_IDENTITY_PROVIDER_MAPPING"/>
<addUniqueConstraint columnNames="IDENTITY_PROVIDER_ID,CLIENT_ID" constraintName="UK_7CAELWNIBJI49AVXSRTUF6XJ12" tableName="CLIENT_IDENTITY_PROV_MAPPING"/>
<addColumn tableName="REALM">
<column name="LOGIN_LIFESPAN" type="INT"/>
<column name="INTERNATIONALIZATION_ENABLED" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="INTERNATIONALIZATION_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="DEFAULT_LOCALE" type="VARCHAR(255)" />
<column name="REGISTRATION_EMAIL_AS_USERNAME" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="REG_EMAIL_AS_USERNAME" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
</addColumn>
<!-- KEYCLOAK-1106 APPLICATION_ID and REALM_ID switched in REALM_APPLICATION table -->
<dropForeignKeyConstraint baseTableName="REALM_APPLICATION" constraintName="FK_71S3P0DIUXAWWQQSA528UBY2Q" />
<dropForeignKeyConstraint baseTableName="REALM_APPLICATION" constraintName="FK_L5QGA3RFME47335JY8JXYXH3I" />
<dropUniqueConstraint tableName="REALM_APPLICATION" constraintName="UK_L5QGA3RFME47335JY8JXYXH3I" />
<renameColumn tableName="REALM_APPLICATION" oldColumnName="APPLICATION_ID" newColumnName="APPLICATION_ID_TMP" columnDataType="VARCHAR(36)"/>
<renameColumn tableName="REALM_APPLICATION" oldColumnName="REALM_ID" newColumnName="APPLICATION_ID" columnDataType="VARCHAR(36)"/>
<renameColumn tableName="REALM_APPLICATION" oldColumnName="APPLICATION_ID_TMP" newColumnName="REALM_ID" columnDataType="VARCHAR(36)"/>
<addUniqueConstraint columnNames="APPLICATION_ID" constraintName="UK_M6QGA3RFME47335JY8JXYXH3I" tableName="REALM_APPLICATION"/>
<addForeignKeyConstraint baseColumnNames="APPLICATION_ID" baseTableName="REALM_APPLICATION" constraintName="FK_82S3P0DIUXAWWQQSA528UBY2Q" referencedColumnNames="ID" referencedTableName="CLIENT"/>
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_APPLICATION" constraintName="FK_M6QGA3RFME47335JY8JXYXH3I" referencedColumnNames="ID" referencedTableName="REALM"/>
</changeSet>
</databaseChangeLog>

View file

@ -33,6 +33,9 @@ public class Update1_2_0_Beta1 extends Update {
@Override
public void update(KeycloakSession session) {
deleteEntries("clientSessions");
deleteEntries("sessions");
convertSocialToIdFedRealms();
convertSocialToIdFedUsers();
addAccessCodeLoginTimeout();

View file

@ -122,6 +122,11 @@
<artifactId>keycloak-social-facebook</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-social-linkedin</artifactId>
<version>${project.version}</version>
</dependency>
<!-- ldap federation api -->
<dependency>

View file

@ -236,6 +236,10 @@
<maven-resource group="org.keycloak" artifact="keycloak-social-facebook"/>
</module-def>
<module-def name="org.keycloak.keycloak-social-linkedin">
<maven-resource group="org.keycloak" artifact="keycloak-social-linkedin"/>
</module-def>
<module-def name="org.keycloak.keycloak-kerberos-federation">
<maven-resource group="org.keycloak" artifact="keycloak-kerberos-federation"/>
</module-def>

View file

@ -57,6 +57,7 @@
<module name="org.keycloak.keycloak-social-github" services="import"/>
<module name="org.keycloak.keycloak-social-google" services="import"/>
<module name="org.keycloak.keycloak-social-twitter" services="import"/>
<module name="org.keycloak.keycloak-social-linkedin" services="import"/>
<module name="org.keycloak.keycloak-subsystem" services="import"/>
<module name="org.keycloak.keycloak-timer-api" services="import"/>
<module name="org.keycloak.keycloak-timer-basic" services="import"/>

View file

@ -60,6 +60,7 @@
<module name="org.keycloak.keycloak-social-github" services="import"/>
<module name="org.keycloak.keycloak-social-google" services="import"/>
<module name="org.keycloak.keycloak-social-twitter" services="import"/>
<module name="org.keycloak.keycloak-social-linkedin" services="import"/>
<module name="org.keycloak.keycloak-timer-api" services="import"/>
<module name="org.keycloak.keycloak-timer-basic" services="import"/>

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-social-linkedin">
<resources>
<!-- Insert resources here -->
</resources>
<dependencies>
<module name="org.keycloak.keycloak-core"/>
<module name="org.keycloak.keycloak-social-core"/>
<module name="org.keycloak.keycloak-broker-core"/>
<module name="org.keycloak.keycloak-broker-oidc"/>
<module name="org.keycloak.keycloak-model-api"/>
<module name="org.jboss.logging"/>
<module name="javax.api"/>
<module name="org.codehaus.jackson.jackson-core-asl"/>
<module name="org.codehaus.jackson.jackson-mapper-asl"/>
<module name="org.codehaus.jackson.jackson-xc"/>
</dependencies>
</module>

View file

@ -50,6 +50,7 @@
<module name="org.keycloak.keycloak-social-github" services="import"/>
<module name="org.keycloak.keycloak-social-google" services="import"/>
<module name="org.keycloak.keycloak-social-twitter" services="import"/>
<module name="org.keycloak.keycloak-social-linkedin" services="import"/>
<module name="org.keycloak.keycloak-timer-api" services="import"/>
<module name="org.keycloak.keycloak-timer-basic" services="import"/>
<module name="org.hibernate" services="import"/>

View file

@ -715,6 +715,99 @@
</tgroup>
</table>
</section>
<section>
<title>LinkedIn</title>
<para>
To enable login with LinkedIn you first have to create an application in
<ulink url="https://www.linkedin.com/secure/developer">LinkedIn Developer Network</ulink>. Then you need to copy
the client id and secret into the Keycloak Admin Console.
</para>
<para>
Let's see first how to create an application with LinkedIn.
</para>
<orderedlist>
<listitem>
<para>
Log in to <ulink url="https://www.linkedin.com/secure/developer">LinkedIn Developer Network</ulink>. Click the
<literal>Add New Application</literal> link. Use any value for <literal>Application Name</literal>,
<literal>Website URL</literal>, <literal>Description</literal>, <literal>Developer Contact Email</literal> and <literal>Phone</literal> you want.
Select <literal>r_basicprofile</literal> and <literal>r_emailaddress</literal> in the <literal>Default Scope</literal> section.
Click the <literal>Add Application</literal> button.
</para>
</listitem>
<listitem>
<para>
Copy <literal>Consumer Key / API Key</literal> and <literal>Consumer Secret / Secret Key</literal> from the shown page.
</para>
</listitem>
</orderedlist>
<para>
Now that you have the client id and secret, you can proceed with the creation of a LinkedIn Identity Provider in Keycloak. As follows:
</para>
<orderedlist>
<listitem>
<para>
Select the <literal>LinkedIn</literal> identity provider from the drop-down box on the top right corner of the identity providers table in Keycloak's Admin Console. You should be presented with a specific page to configure the selected provided.
</para>
</listitem>
<listitem>
<para>
Copy the client id and secret to their corresponding fields in the Keycloak Admin Console. Click <literal>Save</literal>.
</para>
</listitem>
</orderedlist>
<para>
Once you create the identity provider in Keycloak, you must update your LinkedIn application with the redirect url that was
generated to your identity provider.
</para>
<orderedlist>
<listitem>
<para>
Open the LinkedIn Developer Network and select your application. In <literal>OAuth 2.0 Redirect URLs</literal>
insert the redirect uri created by Keycloak. The redirect uri
usually have the following format: <literal>http://{host}:{port}/auth/realms/{realm}/broker/{provider_alias}/endpoint</literal>.
</para>
</listitem>
</orderedlist>
<note>
<para>
You can always get the redirect url for a specific identity provider from the table presented when you
click on the 'Identity Provider' tab in <emphasis>Realm > Settings</emphasis>.
</para>
</note>
<para>
That is it! This pretty much what you need to do in order to setup this identity provider.
</para>
<para>
The table below lists some additional configuration options you may use when configuring this provider.
</para>
<table>
<title>Configuration Options</title>
<tgroup align="left" cols="2">
<thead>
<row>
<entry>
Configuration
</entry>
<entry>
Description
</entry>
</row>
</thead>
<tbody valign="top">
<row>
<entry>
<literal>Default Scopes</literal>
</entry>
<entry>
Allows you to manually specify the scopes that users must authorize when authenticating with this provider.
For a complete list of scopes, please take a look at application configuration in <ulink url="https://www.linkedin.com/secure/developer">LinkedIn Developer Network</ulink>. By default, Keycloak uses the following scopes: <literal>r_basicprofile r_emailaddress</literal>
</entry>
</row>
</tbody>
</tgroup>
</table>
</section>
</section>
<section>

View file

@ -2,18 +2,18 @@
<title>Themes</title>
<para>
Keycloak provides theme support for login forms and account management. This allows customizing the look
and feel of end-user facing pages so they can be integrated with your brand and applications.
Keycloak provides theme support for web pages and emails. This allows customizing the look
and feel of end-user facing pages so they can be integrated with your applications.
</para>
<section>
<title>Theme types</title>
<para>
There are several types of themes in Keycloak:
A theme can support several types to customize different aspects of Keycloak. The types currently available
are:
<itemizedlist>
<listitem>Account - Account management</listitem>
<listitem>Admin - Admin console</listitem>
<listitem>Common - Shared resources for themes</listitem>
<listitem>Email - Emails</listitem>
<listitem>Login - Login forms</listitem>
<listitem>Welcome - Welcome pages</listitem>
@ -28,6 +28,11 @@
the theme used for a realm open the <literal>Keycloak Admin Console</literal>, select your realm
from the drop-down box in the top left corner. Under <literal>Settings</literal> click on <literal>Theme</literal>.
</para>
<para>
To set the theme for the <literal>master</literal> Keycloak admin console set the admin console theme for
the <literal>master</literal> realm. To set the theme for per realm admin access control set the admin console
theme for the corresponding realm.
</para>
<para>
To change the welcome theme you need to edit <literal>standalone/configuration/keycloak-server.json</literal>
and add <literal>welcomeTheme</literal> to the theme element, for example:
@ -43,9 +48,8 @@
<section>
<title>Default themes</title>
<para>
Keycloak comes bundled with default themes in <literal>standalone/configuration/themes</literal>. It is
not recommended to edit these themes directly. Instead you should create a new theme to extend a default
theme. A good reference is to copy the keycloak themes as these extend the base theme to add styling.
Keycloak comes bundled with default themes in <literal>standalone/configuration/themes</literal>. You should
not edit the bundled themes directly. Instead create a new theme that extends a bundled theme.
</para>
</section>
@ -65,24 +69,63 @@
<para>
A theme can extend another theme. When extending a theme you can override individual files (templates, stylesheets, etc.).
The recommended way to create a theme is to extend the base theme. The base theme provides templates
and a default message bundle. It should be possible to achieve the customization required by styling these
templates.
and a default message bundle. If you decide to override templates bear in mind that you may need to update
your templates when upgrading to a new release to include any changes made to the original template.
</para>
<para>
To create a new theme, create a folder in <literal>.../standalone/configuration/themes/&lt;theme type&gt;</literal>.
The name of the folder is the name of the theme. Then create a file <literal>theme.properties</literal> inside the theme folder.
The contents of the file should be:
Before creating a theme it's a good idea to disable caching as this makes it possible to edit theme resources
without restarting the server. To do this open <literal>../standalone/configuration/keycloak-server.json</literal>
for <literal>theme</literal> set <literal>staticMaxAge</literal> to <literal>-1</literal> and
<literal>cacheTemplates</literal> and <literal>cacheThemes</literal> to <literal>false</literal>. For example:
<programlisting>[<![CDATA[
"theme": {
"default": "keycloak",
"staticMaxAge": 01,
"cacheTemplates": false,
"cacheThemes": false,
"folder": {
"dir": "${jboss.server.config.dir}/themes"
}
},
]]></programlisting>
Remember to re-enable caching in production as it will significantly impact performance.
</para>
<programlisting>parent=base</programlisting>
<para>
You have now created your theme. Check that it works by configuring it for a realm. It should look the same
as the base theme as you've not added anything to it yet. The next sections will describe how to modify
the theme.</para>
To create a new theme create a directory for the theme in <literal>.../standalone/configuration/themes</literal>.
The name of the directory should be the name of the theme. For example to create a theme called <literal>example-theme</literal>
create the directory <literal>.../standalone/configuration/themes/example-theme</literal>. Inside the theme
directory you then need to create a directory for each of the types your theme is going to provide. For example
to add the login type to the <literal>example-theme</literal> theme create the directory
<literal>.../standalone/configuration/themes/example-theme/login</literal>.
</para>
<para>
For each type create a file <literal>theme.properties</literal> which allows setting some configuration for
the theme, for example what theme it overrides and if it should import any themes. For the above example we
want to override the base theme and import common resources from the Keycloak theme. To do this create the
file <literal>.../standalone/configuration/themes/example-theme/login/theme.properties</literal> with the
following contents:
<programlisting>[<![CDATA[
parent=base
import=common/keycloak
]]></programlisting>
</para>
<para>
You have now created a theme with support for the login type. To check that it works open the admin console.
Select your realm and click on <literal>Themes</literal>. For <literal>Login Theme</literal> select
<literal>example-theme</literal> and click <literal>Save</literal>. Then open the login page for the realm.
You can do this either by login through your application or by opening <literal>http://localhost:8080/realms/&lt;realm name&gt;/account</literal>.
</para>
<para>
To see the effect of changing the parent theme, set <literal>parent=keycloak</literal> in <literal>theme.properties</literal>
and refresh the login page. To follow the rest of the documentation set it back to <literal>parent=base</literal>
before continuing.
</para>
<section>
<title>Stylesheets</title>
<para>
A theme can have one or more stylesheets, to add a stylesheet create a file inside <literal>resources/css</literal> (for example <literal>resources/css/styles.css</literal>)
inside your theme folder. Then registering it in <literal>theme.properties</literal> by adding:
A theme can have one or more stylesheets, to add a stylesheet create a file inside <literal>resources/css</literal>
(for example <literal>resources/css/styles.css</literal>) inside your theme folder. Then registering it
in <literal>theme.properties</literal> by adding:
</para>
<programlisting>styles=css/styles.css</programlisting>
<para>
@ -90,6 +133,17 @@
as you want. For example:
</para>
<programlisting>styles=css/styles.css css/more-styles.css</programlisting>
For the example-theme above add <literal>example-theme/login/resources/css/styles.css</literal> with the
following content:
<programlisting>[<![CDATA[
#kc-form {
background-color: #000;
color: #fff;
padding: 20px;
}]]></programlisting>
Then edit <literal>example-theme/login/theme.properties</literal> and add <programlisting>styles=css/styles.css</programlisting>.
Refresh the login page to see your changes. It's not pretty, but you can see how easily you can modify the
styles for your theme.
</section>
<section>
<title>Scripts</title>
@ -121,9 +175,8 @@
<section>
<title>Messages</title>
<para>
Text in the templates are loaded from message bundles. Currently internationalization isn't supported,
but that will be added in a later release. A theme that extends another theme will inherit all messages
from the parents message bundle, but can override individual messages. For example to replace
Text in the templates are loaded from message bundles. A theme that extends another theme will inherit
all messages from the parents message bundle, but can override individual messages. For example to replace
<literal>Username</literal> on the login form with <literal>Your Username</literal> create the file
<literal>messages/messages.properties</literal> inside your theme folder and add the following content:
</para>
@ -134,30 +187,79 @@
<para>
Keycloak uses <ulink url="http://freemarker.org">Freemarker Templates</ulink> in order to generate HTML.
These templates are defined in <literal>.ftl</literal> files and can be overriden from the base theme.
Check out the Freemarker website on how to form a template file.
Check out the Freemarker website on how to form a template file. To override the login template for the
<literal>example-theme</literal> copy <literal>../standalone/configuration/themes/base/login/login.ftl</literal>
to <literal>../standalone/configuration/themes/example-theme/login</literal> and open it in an editor. After
the first line (&lt;#import ...&gt;) add <literal>&lt;h1&gt;HELLO WORLD!&lt;/h1&gt;</literal> then refresh
the page.
</para>
</section>
</section>
<section>
<title>Deploying themes</title>
<para>
Themes can be deployed to Keycloak by copying the theme directory to <literal>../standalone/configuration/themes</literal>
or it can be deployed as a module. For a single server or during development just copying the theme is fine, but
in a cluster or domain it's recommended to deploy as a module.
</para>
<para>
To deploy a theme as a module you need to create an jar (it's basically just a zip with jar extension) with
the theme resources and a file <literal>META/keycloak-server.json</literal> that describes the themes contained
in the archive. For example <literal>example-theme.jar</literal> with the contents:
<itemizedlist>
<listitem>META-INF/keycloak-themes.json</listitem>
<listitem>theme/example-theme/login/theme.properties</listitem>
<listitem>theme/example-theme/login/login.ftl</listitem>
<listitem>theme/example-theme/login/resources/css/styles.css</listitem>
</itemizedlist>
The contents of META-INF/keycloak-server.json in this case would be:
<programlisting>[<![CDATA[
{
"themes": [{
"name" : "example-theme",
"types": [ "login" ]
}]
}
]]></programlisting>
As you can see a single jar can contain multiple themes and each theme can support one or more types.
</para>
<para>
The deploy the jar as a module to Keycloak you can either manually create the module or use <literal>jboss-cli</literal>.
It's simplest to use <literal>jboss-cli</literal> as it creates the required directories and module descriptor
for you. To deploy the above jar <literal>jboss-cli</literal> run:
<programlisting>[<![CDATA[
KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.example.exampletheme --resources=example-theme.jar"
]]></programlisting>
If you're on windows run <programlisting>KEYCLOAK_HOME/bin/jboss-cli.bat</programlisting>.
</para>
<para>
This command creates <literal>modules/org/example/exampletheme/main</literal> containing <literal>example-theme.jar</literal>
and <literal>module.xml</literal>.
</para>
<para>
Once you've created the module you need to register it with Keycloak do this by editing
<literal>../standalone/configuration/keycloak-server.json</literal> and adding the module to <literal>theme/module/modules</literal>. For example:
<programlisting>[<![CDATA[
"theme": {
...
"module": {
"modules": [ "org.example.exampletheme" ]
}
}
]]></programlisting>
</para>
<para>
If a theme is deployed to <literal>../standalone/configuration/themes</literal> and as a module the first
is used.
</para>
</section>
<section>
<title>SPIs</title>
<para>
For full control of login forms and account management Keycloak provides a number of SPIs.
</para>
<section>
<title>Theme SPI</title>
<para>
The Theme SPI allows creating different mechanisms to load themes for the default FreeMarker based
implementations of login forms and account management. To create a theme provider you will need to implement
<literal>org.keycloak.freemarker.ThemeProviderFactory</literal> and <literal>org.keycloak.freemarker.ThemeProvider</literal>.
</para>
<para>
Keycloak comes with two theme providers, one that loads themes from the classpath (used by default themes)
and another that loads themes from a folder (used by custom themes). Looking at these
would be a good place to start to create your own theme provider. You can find them inside
<literal>forms/common-themes</literal> on GitHub or the source download.
</para>
</section>
<section>
<title>Account SPI</title>
<para>

View file

@ -0,0 +1 @@
/.externalToolBuilders/*

View file

@ -21,7 +21,7 @@ Then open $KEYCLOAK_HOME/standalone/configuration/keycloak-server.json and regis
}
}
Alternatively you can copy `src/main/resources/theme/login` to `standalone/configuration/themes/login/`.
Alternatively you can copy `src/main/resources/theme/sunrise` to `standalone/configuration/themes/`.
Once you've added the theme open the admin console, select your realm, click on `Theme`. In the dropdown for `Login Theme` select `sunrise`. Click `Save` and login to the realm to see the new theme in action.
@ -46,13 +46,7 @@ Then open $KEYCLOAK_HOME/standalone/configuration/keycloak-server.json and regis
}
}
Alternatively you can copy:
* `account/logo-example` to `standalone/configuration/themes/account/`
* `login/logo-example` to `standalone/configuration/themes/login/`
* `admin/logo-example` to `standalone/configuration/themes/admin/`
* `welcome/logo-example` to `standalone/configuration/themes/welcome/`
Alternatively you can copy `src/main/resources/theme/logo-example` to `standalone/configuration/themes/`.
Once you've added the theme open the admin console, select your realm, click on `Theme`. In the dropdowns for `Login Theme`, `Account Theme` and `Admin Console Theme` select `logo-example`. Click `Save` and login to the realm to see the new theme in action.

View file

@ -11,9 +11,7 @@ import java.util.concurrent.ConcurrentHashMap;
*/
public class ExtendingThemeManagerFactory implements ThemeProviderFactory {
private ConcurrentHashMap<ThemeKey, Theme> themeCache = new ConcurrentHashMap<ThemeKey, Theme>();
private ExtendingThemeManager themeManager;
private ConcurrentHashMap<ThemeKey, Theme> themeCache;
@Override
public ThemeProvider create(KeycloakSession session) {
@ -23,7 +21,7 @@ public class ExtendingThemeManagerFactory implements ThemeProviderFactory {
@Override
public void init(Config.Scope config) {
if(Config.scope("theme").getBoolean("cacheThemes", true)) {
themeCache = new ConcurrentHashMap<ThemeKey, Theme>();
themeCache = new ConcurrentHashMap<>();
}
}

View file

@ -20,7 +20,7 @@ public class FreeMarkerUtil {
public FreeMarkerUtil() {
if (Config.scope("theme").getBoolean("cacheTemplates", true)) {
cache = new ConcurrentHashMap<String, Template>();
cache = new ConcurrentHashMap<>();
}
}

View file

@ -0,0 +1,36 @@
package org.keycloak.freemarker.beans;
import freemarker.template.TemplateMethodModelEx;
import freemarker.template.TemplateModelException;
import java.text.MessageFormat;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
/**
* @author <a href="mailto:gerbermichi@me.com">Michael Gerber</a>
*/
public class AdvancedMessageFormatterMethod implements TemplateMethodModelEx {
private final Properties messages;
private final Locale locale;
public AdvancedMessageFormatterMethod(Locale locale, Properties messages) {
this.locale = locale;
this.messages = messages;
}
@Override
public Object exec(List list) throws TemplateModelException {
if (list.size() >= 1) {
String key = list.get(0).toString();
if (key.startsWith("${") && key.endsWith("}")) {
key = key.substring(2, key.length() - 1);
return new MessageFormat(messages.getProperty(key, key), locale).format(list.subList(1, list.size()).toArray());
} else {
return key;
}
}
return null;
}
}

View file

@ -22,8 +22,8 @@ public class MessageFormatterMethod implements TemplateMethodModelEx {
@Override
public Object exec(List list) throws TemplateModelException {
String key = list.get(0).toString();
if (list.size() >= 1) {
String key = list.get(0).toString();
return new MessageFormat(messages.getProperty(key,key),locale).format(list.subList(1, list.size()).toArray());
} else {
return null;

View file

@ -2,13 +2,11 @@ package org.keycloak.theme;
import org.keycloak.freemarker.Theme;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Locale;
import java.util.Properties;
import java.util.ResourceBundle;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -42,7 +40,7 @@ public class ClassLoaderTheme implements Theme {
this.type = type;
this.classLoader = classLoader;
String themeRoot = "theme/" + type.toString().toLowerCase() + "/" + name + "/";
String themeRoot = "theme/" + name + "/" + type.toString().toLowerCase() + "/";
this.templateRoot = themeRoot;
this.resourceRoot = themeRoot + "resources/";
@ -60,10 +58,6 @@ public class ClassLoaderTheme implements Theme {
}
}
public ClassLoaderTheme() {
}
@Override
public String getName() {
return name;

View file

@ -18,11 +18,13 @@ public class FolderTheme implements Theme {
private String parentName;
private String importName;
private File themeDir;
private String name;
private Type type;
private final Properties properties;
public FolderTheme(File themeDir, Type type) throws IOException {
public FolderTheme(File themeDir, String name, Type type) throws IOException {
this.themeDir = themeDir;
this.name = name;
this.type = type;
this.properties = new Properties();
@ -36,7 +38,7 @@ public class FolderTheme implements Theme {
@Override
public String getName() {
return themeDir.getName();
return name;
}
@Override

View file

@ -15,10 +15,10 @@ import java.util.Set;
*/
public class FolderThemeProvider implements ThemeProvider {
private File rootDir;
private File themesDir;
public FolderThemeProvider(File rootDir) {
this.rootDir = rootDir;
public FolderThemeProvider(File themesDir) {
this.themesDir = themesDir;
}
@Override
@ -28,51 +28,41 @@ public class FolderThemeProvider implements ThemeProvider {
@Override
public Theme getTheme(String name, Theme.Type type) throws IOException {
if (hasTheme(name, type)) {
return new FolderTheme(new File(getTypeDir(type), name), type);
}
return null;
File themeDir = getThemeDir(name, type);
return themeDir.isDirectory() ? new FolderTheme(themeDir, name, type) : null;
}
@Override
public Set<String> nameSet(Theme.Type type) {
File typeDir = getTypeDir(type);
if (typeDir != null) {
File[] themes = typeDir.listFiles(new FileFilter() {
final String typeName = type.name().toLowerCase();
File[] themeDirs = themesDir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.isDirectory();
return pathname.isDirectory() && new File(pathname, typeName).isDirectory();
}
});
if (themeDirs != null) {
Set<String> names = new HashSet<String>();
for (File t : themes) {
names.add(t.getName());
for (File themeDir : themeDirs) {
names.add(themeDir.getName());
}
return names;
}
} else {
return Collections.emptySet();
}
private File getTypeDir(Theme.Type type) {
if (rootDir != null && rootDir.isDirectory()) {
File typeDir = new File(rootDir, type.name().toLowerCase());
if (typeDir.isDirectory()) {
return typeDir;
}
}
return null;
}
@Override
public boolean hasTheme(String name, Theme.Type type) {
File typeDir = getTypeDir(type);
return typeDir != null && new File(typeDir, name).isDirectory();
return getThemeDir(name, type).isDirectory();
}
@Override
public void close() {
}
private File getThemeDir(String name, Theme.Type type) {
return new File(themesDir, name + File.separator + type.name().toLowerCase());
}
}

View file

@ -1,57 +0,0 @@
authenticatorCode=One-time code
email=Email
firstName=First name
lastName=Last name
password=Password
passwordConfirm=Confirmation
passwordNew=New Password
username=Username
street=Street
locality=City or Locality
region=State, Province, or Region
postal_code=Zip or Postal code
country=Country
missingFirstNameMessage=Please specify first name.
invalidEmailMessage=Invalid email address.
missingLastNameMessage=Please specify last name.
missingEmailMessage=Please specify email.
missingPasswordMessage=Please specify password.
notMatchPasswordMessage=Passwords don''t match.
missingTotpMessage=Please specify authenticator code
invalidPasswordExistingMessage=Invalid existing password
invalidPasswordConfirmMessage=Password confirmation doesn''t match
invalidTotpMessage=Invalid authenticator code
readOnlyUserMessage=You can''t update your account as it is read only
readOnlyPasswordMessage=You can''t update your password as your account is read only
successTotpMessage=Mobile authenticator configured.
successTotpRemovedMessage=Mobile authenticator removed.
accountUpdatedMessage=Your account has been updated
accountPasswordUpdatedMessage=Your password has been updated
missingIdentityProviderMessage=Identity provider not specified
invalidFederatedIdentityActionMessage=Invalid or missing action
identityProviderNotFoundMessage=Specified identity provider not found
federatedIdentityLinkNotActiveMessage=This identity is not active anymore
federatedIdentityRemovingLastProviderMessage=You can''t remove last federated identity as you don''t have password
identityProviderRedirectErrorMessage=Failed to redirect to identity provider
identityProviderRemovedMessage=Identity provider removed successfully
accountDisabledMessage=Account is disabled, contact admin
doLogOutAllSessions=Log out all sessions
accountTemporarilyDisabledMessage=Account is temporarily disabled, contact admin or try again later
invalidPasswordMinLengthMessage=Invalid password: minimum length {0}
invalidPasswordMinLowerCaseCharsMessage=Invalid password: must contain at least {0} lower case characters
invalidPasswordMinDigitsMessage=Invalid password: must contain at least {0} numerical digits
invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least {0} upper case characters
invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters
invalidPasswordNotUsernameMessage=Invalid password\: must not be equal to the username
locale_de=German
locale_en=English

View file

@ -3,10 +3,10 @@
<div class="row">
<div class="col-md-10">
<h2>Edit Account</h2>
<h2>${msg("editAccountHtmlTtile")}</h2>
</div>
<div class="col-md-2 subtitle">
<span class="subtitle"><span class="required">*</span> Required fields</span>
<span class="subtitle"><span class="required">*</span> ${msg("requiredFields")}</span>
</div>
</div>
@ -104,9 +104,9 @@
<div class="form-group">
<div id="kc-form-buttons" class="col-md-offset-2 col-md-10 submit">
<div class="">
<#if url.referrerURI??><a href="${url.referrerURI}">Back to application</a></#if>
<button type="submit" class="btn btn-primary btn-lg" name="submitAction" value="Save">Save</button>
<button type="submit" class="btn btn-default btn-lg" name="submitAction" value="Cancel">Cancel</button>
<#if url.referrerURI??><a href="${url.referrerURI}">${msg("backToApplication")}/a></#if>
<button type="submit" class="btn btn-primary btn-lg" name="submitAction" value="Save">${msg("doSave")}</button>
<button type="submit" class="btn btn-default btn-lg" name="submitAction" value="Cancel">${msg("doCancel")}</button>
</div>
</div>
</div>

View file

@ -3,7 +3,7 @@
<div class="row">
<div class="col-md-10">
<h2>Federated Identities</h2>
<h2>${msg("federatedIdentitiesHtmlTitle")}</h2>
</div>
</div>
@ -19,10 +19,10 @@
<div class="col-sm-5 col-md-5">
<#if identity.connected>
<#if federatedIdentity.removeLinkPossible>
<a href="${identity.actionUrl}" type="submit" id="remove-${identity.providerId!}" class="btn btn-primary btn-lg">Remove ${identity.providerName!}</a>
<a href="${identity.actionUrl}" type="submit" id="remove-${identity.providerId!}" class="btn btn-primary btn-lg">${msg("doRemove")}</a>
</#if>
<#else>
<a href="${identity.actionUrl}" type="submit" id="add-${identity.providerId!}" class="btn btn-primary btn-lg">Add ${identity.providerName!}</a>
<a href="${identity.actionUrl}" type="submit" id="add-${identity.providerId!}" class="btn btn-primary btn-lg">${msg("doAdd")}</a>
</#if>
</div>
</div>

View file

@ -3,18 +3,18 @@
<div class="row">
<div class="col-md-10">
<h2>Account Log</h2>
<h2>${msg("accountLogHtmlTitle")}</h2>
</div>
</div>
<table class="table table-striped table-bordered">
<thead>
<tr>
<td>Date</td>
<td>Event</td>
<td>IP</td>
<td>Client</td>
<td>Details</td>
<td>${msg("date")}</td>
<td>${msg("event")}</td>
<td>${msg("ip")}</td>
<td>${msg("client")}</td>
<td>${msg("details")}</td>
</tr>
</thead>

View file

@ -1,9 +1,24 @@
doLogOutAllSessions=Alle Sessionen abmelden
doSave=Speichern
doCancel=Abbrechen
doRemove=Entfernen
doAdd=Hinzufügen
doSignOut=Abmelden
editAccountHtmlTtile=Benutzerkonto Bearbeiten
federatedIdentitiesHtmlTitle=Federated Identities
accountLogHtmlTitle=Benutzerkonto Log
changePasswordHtmlTitle=Passwort Ändern
sessionsHtmlTitle=Sessions
accountManagementTitle=Keycloak Benutzerkontoverwaltung
authenticatorTitle=Authenticator
authenticatorCode=One-time code
email=E-Mail
firstName=Vorname
lastName=Nachname
password=Passwort
passwordConfirm=Passwort bestätigung
passwordConfirm=Passwortbestätigung
passwordNew=Neues Passwort
username=Benutzernamen
street=Strasse
@ -12,6 +27,36 @@ postal_code=PLZ
locality=Stadt oder Ortschaft
country=Land
requiredFields=Erforderliche Felder
allFieldsRequired=Alle Felder sind Erforderlich
backToApplication=&laquo; Zurück zur Applikation
backTo=Zurück zu {0}
date=Datum
event=Ereignis
ip=IP
client=Client
clients=Clients
details=Details
started=Startdatum
lastAccess=Letzter Zugriff
expires=Ablaufdatum
applications=
account=Benutzerkonto
federatedIdentity=Federated Identity
authenticator=Authenticator
sessions=Sessions
log=Log
configureAuthenticators=Authenticators konfigurieren
mobile=Mobile
totpStep1=Installieren Sie <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> oder <a href="http://code.google.com/p/google-authenticator/" target="_blank">Google Authenticator</a> auf Ihrem Smartphone.
totpStep2=Öffnen Sie die Applikation und scannen Sie den Barcode oder geben sie den Code ein.
totpStep3=Geben Sie den One-time Code welcher die Applikation generiert hat ein und klicken Sie auf Speichern.
missingFirstNameMessage=Bitte geben Sie einen Vornamen ein.
missingEmailMessage=Bitte geben Sie eine E-Mail Adresse ein.
missingLastNameMessage=Bitte geben Sie einen Nachnamen ein.
@ -20,7 +65,7 @@ notMatchPasswordMessage=Passw
missingTotpMessage=Bitte geben Sie den One-time Code ein.
invalidPasswordExistingMessage=Das aktuelle Passwort is ungültig.
invalidPasswordConfirmMessage=Die Passwort bestätigung ist nicht identisch.
invalidPasswordConfirmMessage=Die Passwortbestätigung ist nicht identisch.
invalidTotpMessage=Ungültiger One-time Code.
invalidEmailMessage=Ungültige E-Mail Adresse.
@ -37,15 +82,13 @@ missingIdentityProviderMessage=Identity Provider nicht angegeben.
invalidFederatedIdentityActionMessage=Ungültige oder fehlende Aktion.
identityProviderNotFoundMessage=Angegebener Identity Provider nicht gefunden.
federatedIdentityLinkNotActiveMessage=Diese Identität ist nicht mehr aktiv.
federatedIdentityRemovingLastProviderMessage=Sie können den letzen Eintrag nicht enfernen, da Sie kein Passwort haben.
identityProviderRedirectErrorMessage=Fehler bei der Weiterleitung zum Identity Proivder.
federatedIdentityRemovingLastProviderMessage=Sie können den letzen Eintrag nicht entfernen, da Sie kein Passwort haben.
identityProviderRedirectErrorMessage=Fehler bei der Weiterleitung zum Identity Provider.
identityProviderRemovedMessage=Identity Provider erfolgreich entfernt.
accountDisabledMessage=Benutzerkonto ist gesperrt, bitte kontaktieren Sie den Admin.
doLogOutAllSessions=Alle Sessionen abmelden
accountTemporarilyDisabledMessage=Benutzerkonto ist temporär gespert, bitte kontaktieren Sie den Admin oder versuchen Sie es später nocheinmal.
accountTemporarilyDisabledMessage=Benutzerkonto ist temporär gesperrt, bitte kontaktieren Sie den Admin oder versuchen Sie es später noch einmal.
invalidPasswordMinLengthMessage=Ungültiges Passwort: minimum länge {0}.
invalidPasswordMinDigitsMessage=Ungültiges Passwort: muss mindestens {0} Zahl(en) beinhalten.
invalidPasswordMinLowerCaseCharsMessage=Ungültiges Passwort: muss mindestens {0} Kleinbuchstaben beinhalten.

View file

@ -0,0 +1,99 @@
doSave=Save
doCancel=Cancel
doLogOutAllSessions=Log out all sessions
doRemove=Remove
doAdd=Add
doSignOut=Sign Out
editAccountHtmlTtile=Edit Account
federatedIdentitiesHtmlTitle=Federated Identities
accountLogHtmlTitle=Account Log
changePasswordHtmlTitle=Change Password
sessionsHtmlTitle=Sessions
accountManagementTitle=Keycloak Account Management
authenticatorTitle=Authenticator
authenticatorCode=One-time code
email=Email
firstName=First name
lastName=Last name
password=Password
passwordConfirm=Confirmation
passwordNew=New Password
username=Username
street=Street
locality=City or Locality
region=State, Province, or Region
postal_code=Zip or Postal code
country=Country
requiredFields=Required fields
allFieldsRequired=All fields required
backToApplication=&laquo; Back to application
backTo=Back to {0}
date=Date
event=Event
ip=IP
client=Client
clients=Clients
details=Details
started=Started
lastAccess=Last Access
expires=Expires
applications=Applications
account=Account
federatedIdentity=Federated Identity
authenticator=Authenticator
sessions=Sessions
log=Log
configureAuthenticators=Configured Authenticators
mobile=Mobile
totpStep1=Install <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> or <a href="http://code.google.com/p/google-authenticator/" target="_blank">Google Authenticator</a> on your mobile.
totpStep2=Open the application and scan the barcode or enter the key.
totpStep3=Enter the one-time code provided by the application and click Save to finish the setup.
missingFirstNameMessage=Please specify first name.
invalidEmailMessage=Invalid email address.
missingLastNameMessage=Please specify last name.
missingEmailMessage=Please specify email.
missingPasswordMessage=Please specify password.
notMatchPasswordMessage=Passwords don''t match.
missingTotpMessage=Please specify authenticator code.
invalidPasswordExistingMessage=Invalid existing password.
invalidPasswordConfirmMessage=Password confirmation doesn''t match.
invalidTotpMessage=Invalid authenticator code.
readOnlyUserMessage=You can''t update your account as it is read only.
readOnlyPasswordMessage=You can''t update your password as your account is read only.
successTotpMessage=Mobile authenticator configured.
successTotpRemovedMessage=Mobile authenticator removed.
accountUpdatedMessage=Your account has been updated.
accountPasswordUpdatedMessage=Your password has been updated.
missingIdentityProviderMessage=Identity provider not specified.
invalidFederatedIdentityActionMessage=Invalid or missing action.
identityProviderNotFoundMessage=Specified identity provider not found.
federatedIdentityLinkNotActiveMessage=This identity is not active anymore.
federatedIdentityRemovingLastProviderMessage=You can''t remove last federated identity as you don''t have password.
identityProviderRedirectErrorMessage=Failed to redirect to identity provider.
identityProviderRemovedMessage=Identity provider removed successfully.
accountDisabledMessage=Account is disabled, contact admin.
accountTemporarilyDisabledMessage=Account is temporarily disabled, contact admin or try again later.
invalidPasswordMinLengthMessage=Invalid password: minimum length {0}.
invalidPasswordMinLowerCaseCharsMessage=Invalid password: must contain at least {0} lower case characters.
invalidPasswordMinDigitsMessage=Invalid password: must contain at least {0} numerical digits.
invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least {0} upper case characters.
invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters.
invalidPasswordNotUsernameMessage=Invalid password\: must not be equal to the username.
locale_de=German
locale_en=English

View file

@ -3,10 +3,10 @@
<div class="row">
<div class="col-md-10">
<h2>Change Password</h2>
<h2>${msg("changePasswordHtmlTitle")}</h2>
</div>
<div class="col-md-2 subtitle">
<span class="subtitle">All fields required</span>
<span class="subtitle">${msg("allFieldsRequired")}</span>
</div>
</div>
@ -48,8 +48,8 @@
<div class="form-group">
<div id="kc-form-buttons" class="col-md-offset-2 col-md-10 submit">
<div class="">
<button type="submit" class="btn btn-primary btn-lg" name="submitAction" value="Save">Save</button>
<button type="submit" class="btn btn-default btn-lg" name="submitAction" value="Cancel">Cancel</button>
<button type="submit" class="btn btn-primary btn-lg" name="submitAction" value="Save">${msg("doSave")}</button>
<button type="submit" class="btn btn-default btn-lg" name="submitAction" value="Cancel">${msg("doCancel")}</button>
</div>
</div>
</div>

View file

@ -3,19 +3,19 @@
<div class="row">
<div class="col-md-10">
<h2>Sessions</h2>
<h2>${msg("sessionsHtmlTitle")}</h2>
</div>
</div>
<table class="table table-striped table-bordered">
<thead>
<tr>
<td>IP</td>
<td>Started</td>
<td>Last Access</td>
<td>Expires</td>
<td>Applications</td>
<td>Clients</td>
<td>${msg("ip")}</td>
<td>${msg("started")}</td>
<td>${msg("lastAccess")}</td>
<td>${msg("expires")}</td>
<td>${msg("applications")}</td>
<td>${msg("clients")}</td>
</tr>
</thead>

View file

@ -3,7 +3,7 @@
<html>
<head>
<meta charset="utf-8">
<title>Keycloak Account Management</title>
<title>${msg("accountManagementTitle")}</title>
<link rel="icon" href="${url.resourcesPath}/img/favicon.ico">
<#if properties.styles?has_content>
<#list properties.styles?split(' ') as style>
@ -40,8 +40,8 @@
</div>
<li>
</#if>
<#if referrer?has_content && referrer.url?has_content><li><a href="${referrer.url}" id="referrer">Back to ${referrer.name}</a></li></#if>
<li><a href="${url.logoutUrl}">Sign Out</a></li>
<#if referrer?has_content && referrer.url?has_content><li><a href="${referrer.url}" id="referrer">${msg("backTo",referrer.name)}</a></li></#if>
<li><a href="${url.logoutUrl}">${msg("doSignOut")}</a></li>
</ul>
</div>
</div>
@ -51,12 +51,12 @@
<div class="container">
<div class="bs-sidebar col-sm-3 ng-scope">
<ul>
<li class="<#if active=='account'>active</#if>"><a href="${url.accountUrl}">Account</a></li>
<#if features.passwordUpdateSupported><li class="<#if active=='password'>active</#if>"><a href="${url.passwordUrl}">Password</a></li></#if>
<li class="<#if active=='totp'>active</#if>"><a href="${url.totpUrl}">Authenticator</a></li>
<#if features.identityFederation><li class="<#if active=='social'>active</#if>"><a href="${url.socialUrl}">Federated Identity</a></li></#if>
<li class="<#if active=='sessions'>active</#if>"><a href="${url.sessionsUrl}">Sessions</a></li>
<#if features.log><li class="<#if active=='log'>active</#if>"><a href="${url.logUrl}">Log</a></li></#if>
<li class="<#if active=='account'>active</#if>"><a href="${url.accountUrl}">${msg("account")}</a></li>
<#if features.passwordUpdateSupported><li class="<#if active=='password'>active</#if>"><a href="${url.passwordUrl}">${msg("password")}</a></li></#if>
<li class="<#if active=='totp'>active</#if>"><a href="${url.totpUrl}">${msg("authenticator")}</a></li>
<#if features.identityFederation><li class="<#if active=='social'>active</#if>"><a href="${url.socialUrl}">${msg("federatedIdentity")}</a></li></#if>
<li class="<#if active=='sessions'>active</#if>"><a href="${url.sessionsUrl}">${msg("sessions")}</a></li>
<#if features.log><li class="<#if active=='log'>active</#if>"><a href="${url.logUrl}">${msg("log")}</a></li></#if>
</ul>
</div>

View file

@ -2,17 +2,17 @@
<@layout.mainLayout active='totp' bodyClass='totp'; section>
<#if totp.enabled>
<h2>Authenticators</h2>
<h2>${msg("authenticatorTitle")}</h2>
<table class="table table-bordered table-striped">
<thead
<tr>
<th colspan="2">Configured Authenticators</th>
<th colspan="2">${msg("configureAuthenticators")}/th>
</tr>
</thead>
<tbody>
<tr>
<td class="provider">Mobile</td>
<td class="provider">${msg("mobile")}</td>
<td class="action">
<a id="remove-mobile" href="${url.totpRemoveUrl}"><i class="pficon pficon-delete"></i></a>
</td>
@ -20,23 +20,21 @@
</tbody>
</table>
<#else>
<h2>Authenticator</h2>
<h2>${msg("authenticatorTitle")}</h2>
<hr/>
<ol>
<li>
Install <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> or
<a href="http://code.google.com/p/google-authenticator/" target="_blank">Google Authenticator</a>
on your mobile
<p>${msg("totpStep1")}</p>
</li>
<li>
Open the application and scan the barcode or enter the key<br/>
<p>${msg("totpStep2")}</p>
<img src="${totp.totpSecretQrCodeUrl}" alt="Figure: Barcode"><br/>
<span class="code">${totp.totpSecretEncoded}</span>
</li>
<li>
Enter the one-time code provided by the application and click Submit to finish the setup.
<p>${msg("totpStep3")}</p>
</li>
</ol>
@ -58,8 +56,8 @@
<div class="form-group">
<div id="kc-form-buttons" class="col-md-offset-2 col-md-10 submit">
<div class="">
<button type="submit" class="btn btn-primary btn-lg" name="submitAction" value="Save">Save</button>
<button type="submit" class="btn btn-default btn-lg" name="submitAction" value="Cancel">Cancel</button>
<button type="submit" class="btn btn-primary btn-lg" name="submitAction" value="Save">${msg("doSave")}</button>
<button type="submit" class="btn btn-default btn-lg" name="submitAction" value="Cancel">${msg("doCancel")}</button>
</div>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show more