Merge remote-tracking branch 'upstream/master'
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
|
|
5
dependencies/server-all/pom.xml
vendored
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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"/>
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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"/>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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/<theme type></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/<realm name>/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 (<#import ...>) add <literal><h1>HELLO WORLD!</h1></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>
|
||||
|
|
1
examples/cors/angular-product-app/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/.externalToolBuilders/*
|
|
@ -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.
|
||||
|
||||
|
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 344 KiB After Width: | Height: | Size: 344 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
@ -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<>();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ public class FreeMarkerUtil {
|
|||
|
||||
public FreeMarkerUtil() {
|
||||
if (Config.scope("theme").getBoolean("cacheTemplates", true)) {
|
||||
cache = new ConcurrentHashMap<String, Template>();
|
||||
cache = new ConcurrentHashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
@ -1,3 +1,18 @@
|
|||
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
|
||||
|
@ -12,6 +27,36 @@ postal_code=PLZ
|
|||
locality=Stadt oder Ortschaft
|
||||
country=Land
|
||||
|
||||
requiredFields=Erforderliche Felder
|
||||
allFieldsRequired=Alle Felder sind Erforderlich
|
||||
|
||||
backToApplication=« 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.
|
||||
|
@ -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.
|
|
@ -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=« 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
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|