Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
e675ce44d5
101 changed files with 5070 additions and 1002 deletions
|
@ -8,6 +8,10 @@
|
||||||
</column>
|
</column>
|
||||||
</addColumn>
|
</addColumn>
|
||||||
|
|
||||||
|
<addColumn tableName="CLIENT">
|
||||||
|
<column name="ROOT_URL" type="VARCHAR(255)"/>
|
||||||
|
</addColumn>
|
||||||
|
|
||||||
<createTable tableName="OFFLINE_USER_SESSION">
|
<createTable tableName="OFFLINE_USER_SESSION">
|
||||||
<column name="USER_ID" type="VARCHAR(36)">
|
<column name="USER_ID" type="VARCHAR(36)">
|
||||||
<constraints nullable="false"/>
|
<constraints nullable="false"/>
|
||||||
|
|
|
@ -11,6 +11,7 @@ public class ClientRepresentation {
|
||||||
protected String id;
|
protected String id;
|
||||||
protected String clientId;
|
protected String clientId;
|
||||||
protected String name;
|
protected String name;
|
||||||
|
protected String rootUrl;
|
||||||
protected String adminUrl;
|
protected String adminUrl;
|
||||||
protected String baseUrl;
|
protected String baseUrl;
|
||||||
protected Boolean surrogateAuthRequired;
|
protected Boolean surrogateAuthRequired;
|
||||||
|
@ -74,6 +75,14 @@ public class ClientRepresentation {
|
||||||
this.surrogateAuthRequired = surrogateAuthRequired;
|
this.surrogateAuthRequired = surrogateAuthRequired;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRootUrl() {
|
||||||
|
return rootUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRootUrl(String rootUrl) {
|
||||||
|
this.rootUrl = rootUrl;
|
||||||
|
}
|
||||||
|
|
||||||
public String getAdminUrl() {
|
public String getAdminUrl() {
|
||||||
return adminUrl;
|
return adminUrl;
|
||||||
}
|
}
|
||||||
|
|
|
@ -843,27 +843,25 @@ All configuration options are optional. Default value for directory is <literal>
|
||||||
<section>
|
<section>
|
||||||
<title>Installing Keycloak Server as Root Context</title>
|
<title>Installing Keycloak Server as Root Context</title>
|
||||||
<para>
|
<para>
|
||||||
The Keycloak server can be installed as the default web application. This way, instead of referencing
|
The Keycloak server can be installed as the default web application. In doing so, the server can be referenced at <literal>http://mydomain.com/</literal> instead of <literal>http://mydomain.com/auth</literal>.
|
||||||
the server as <literal>http://mydomain.com/auth</literal>, it would be
|
|
||||||
<literal>http://mydomain.com/</literal>.
|
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
To do this, you need to add a <literal>default-web-module</literal> attribute in the Undertow subystem in standalone.xml.
|
To do this, add the <literal>default-web-module</literal> attribute in the Undertow subystem in standalone.xml.
|
||||||
<programlisting><![CDATA[
|
<programlisting><![CDATA[
|
||||||
<subsystem xmlns="urn:jboss:domain:undertow:1.2">
|
<subsystem xmlns="urn:jboss:domain:undertow:2.0">
|
||||||
<server name="default-server">
|
<server name="default-server">
|
||||||
<host name="default-host" alias="localhost" default-web-module="main-auth-server.war">
|
<host name="default-host" alias="localhost" default-web-module="keycloak-server.war">
|
||||||
<location name="/" handler="welcome-content"/>
|
<location name="/" handler="welcome-content"/>
|
||||||
</host>
|
</host>
|
||||||
]]></programlisting>
|
]]></programlisting>
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
<literal>main-auth-server</literal> is the name of the Keycloak server as defined in the Keycloak subsystem.
|
<literal>keycloak-server.war</literal> is the runtime name of the Keycloak server application. Note that the WAR file does not exist as a file. If its name changes (ie. <literal>keycloak-server.war</literal>) in the future, find its new name from the Keycloak log entry with <literal>runtime-name:</literal>.
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
<note>
|
<note>
|
||||||
If you have already run your server before changing to the root context then your database
|
If you have run your server before altering the root context, your database
|
||||||
will contain references to the old /auth context. And, your clients may also have incorrect
|
will contain references to the old /auth context. Your clients may also have incorrect
|
||||||
references. To fix this on the server side, you will need to <link linkend="export-import">export
|
references. To fix this on the server side, you will need to <link linkend="export-import">export
|
||||||
your database to json, make corrections, and then import.</link> Client-side keycloak.json
|
your database to json, make corrections, and then import.</link> Client-side keycloak.json
|
||||||
files will need to be updated manually as well.
|
files will need to be updated manually as well.
|
||||||
|
|
|
@ -181,6 +181,12 @@ import=common/keycloak
|
||||||
<literal>messages/messages.properties</literal> inside your theme folder and add the following content:
|
<literal>messages/messages.properties</literal> inside your theme folder and add the following content:
|
||||||
</para>
|
</para>
|
||||||
<programlisting>username=Your Username</programlisting>
|
<programlisting>username=Your Username</programlisting>
|
||||||
|
<para>
|
||||||
|
For the admin console, there is a second resource bundle named <literal>admin-messages.properties</literal>.
|
||||||
|
This resource bundle is converted to JSON and shipped to the console to be processed by
|
||||||
|
angular-translate. It is found in the same directory as messages.properties and can be overridden
|
||||||
|
in the same way as described above.
|
||||||
|
</para>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<title>Modifying HTML</title>
|
<title>Modifying HTML</title>
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
package org.keycloak.account.freemarker;
|
package org.keycloak.account.freemarker;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -197,7 +213,10 @@ public class FreeMarkerAccountProvider implements AccountProvider {
|
||||||
String result = freeMarker.processTemplate(attributes, Templates.getTemplate(page), theme);
|
String result = freeMarker.processTemplate(attributes, Templates.getTemplate(page), theme);
|
||||||
Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML).entity(result);
|
Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML).entity(result);
|
||||||
BrowserSecurityHeaderSetup.headers(builder, realm);
|
BrowserSecurityHeaderSetup.headers(builder, realm);
|
||||||
LocaleHelper.updateLocaleCookie(builder, locale, realm, uriInfo, Urls.localeCookiePath(baseUri,realm.getName()));
|
|
||||||
|
String keycloakLocaleCookiePath = Urls.localeCookiePath(baseUri, realm.getName());
|
||||||
|
|
||||||
|
LocaleHelper.updateLocaleCookie(builder, locale, realm, uriInfo, keycloakLocaleCookiePath);
|
||||||
return builder.build();
|
return builder.build();
|
||||||
} catch (FreeMarkerException e) {
|
} catch (FreeMarkerException e) {
|
||||||
logger.error("Failed to process template", e);
|
logger.error("Failed to process template", e);
|
||||||
|
|
|
@ -224,10 +224,15 @@ public class ExtendingThemeManager implements ThemeProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Properties getMessages(Locale locale) throws IOException {
|
public Properties getMessages(Locale locale) throws IOException {
|
||||||
|
return getMessages("messages", locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Properties getMessages(String baseBundlename, Locale locale) throws IOException {
|
||||||
Properties messages = new Properties();
|
Properties messages = new Properties();
|
||||||
ListIterator<Theme> itr = themes.listIterator(themes.size());
|
ListIterator<Theme> itr = themes.listIterator(themes.size());
|
||||||
while (itr.hasPrevious()) {
|
while (itr.hasPrevious()) {
|
||||||
Properties m = itr.previous().getMessages(locale);
|
Properties m = itr.previous().getMessages(baseBundlename, locale);
|
||||||
if (m != null) {
|
if (m != null) {
|
||||||
messages.putAll(m);
|
messages.putAll(m);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
package org.keycloak.freemarker;
|
package org.keycloak.freemarker;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
@ -89,7 +104,11 @@ public class LocaleHelper {
|
||||||
return Locale.ENGLISH;
|
return Locale.ENGLISH;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void updateLocaleCookie(Response.ResponseBuilder builder, Locale locale, RealmModel realm, UriInfo uriInfo, String path) {
|
public static void updateLocaleCookie(Response.ResponseBuilder builder,
|
||||||
|
Locale locale,
|
||||||
|
RealmModel realm,
|
||||||
|
UriInfo uriInfo,
|
||||||
|
String path) {
|
||||||
if (locale == null) {
|
if (locale == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,8 +29,27 @@ public interface Theme {
|
||||||
|
|
||||||
public InputStream getResourceAsStream(String path) throws IOException;
|
public InputStream getResourceAsStream(String path) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as getMessages(baseBundlename, locale), but uses a default baseBundlename
|
||||||
|
* such as "messages".
|
||||||
|
*
|
||||||
|
* @param locale The locale of the desired message bundle.
|
||||||
|
* @return The localized messages from the bundle.
|
||||||
|
* @throws IOException If bundle can not be read.
|
||||||
|
*/
|
||||||
public Properties getMessages(Locale locale) throws IOException;
|
public Properties getMessages(Locale locale) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve localized messages from a message bundle.
|
||||||
|
*
|
||||||
|
* @param baseBundlename The base name of the bundle, such as "messages" in
|
||||||
|
* messages_en.properties.
|
||||||
|
* @param locale The locale of the desired message bundle.
|
||||||
|
* @return The localized messages from the bundle.
|
||||||
|
* @throws IOException If bundle can not be read.
|
||||||
|
*/
|
||||||
|
public Properties getMessages(String baseBundlename, Locale locale) throws IOException;
|
||||||
|
|
||||||
public Properties getProperties() throws IOException;
|
public Properties getProperties() throws IOException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,12 +100,17 @@ public class ClassLoaderTheme implements Theme {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Properties getMessages(Locale locale) throws IOException {
|
public Properties getMessages(Locale locale) throws IOException {
|
||||||
|
return getMessages("messages", locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Properties getMessages(String baseBundlename, Locale locale) throws IOException {
|
||||||
if(locale == null){
|
if(locale == null){
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Properties m = new Properties();
|
Properties m = new Properties();
|
||||||
|
|
||||||
URL url = classLoader.getResource(this.messageRoot + "messages_" + locale.toString() + ".properties");
|
URL url = classLoader.getResource(this.messageRoot + baseBundlename + "_" + locale.toString() + ".properties");
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
m.load(url.openStream());
|
m.load(url.openStream());
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,13 +93,18 @@ public class FolderTheme implements Theme {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Properties getMessages(Locale locale) throws IOException {
|
public Properties getMessages(Locale locale) throws IOException {
|
||||||
|
return getMessages("messages", locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Properties getMessages(String baseBundlename, Locale locale) throws IOException {
|
||||||
if(locale == null){
|
if(locale == null){
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Properties m = new Properties();
|
Properties m = new Properties();
|
||||||
|
|
||||||
File file = new File(themeDir, "messages" + File.separator + "messages_" + locale.toString() + ".properties");
|
File file = new File(themeDir, "messages" + File.separator + baseBundlename + "_" + locale.toString() + ".properties");
|
||||||
if (file.isFile()) {
|
if (file.isFile()) {
|
||||||
m.load(new FileInputStream(file));
|
m.load(new FileInputStream(file));
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,10 @@
|
||||||
<script src="${resourceUrl}/lib/angular/angular.js"></script>
|
<script src="${resourceUrl}/lib/angular/angular.js"></script>
|
||||||
<script src="${resourceUrl}/lib/angular/angular-resource.js"></script>
|
<script src="${resourceUrl}/lib/angular/angular-resource.js"></script>
|
||||||
<script src="${resourceUrl}/lib/angular/angular-route.js"></script>
|
<script src="${resourceUrl}/lib/angular/angular-route.js"></script>
|
||||||
|
<script src="${resourceUrl}/lib/angular/angular-cookies.js"></script>
|
||||||
|
<script src="${resourceUrl}/lib/angular/angular-sanitize.js"></script>
|
||||||
|
<script src="${resourceUrl}/lib/angular/angular-translate.js"></script>
|
||||||
|
<script src="${resourceUrl}/lib/angular/angular-translate-loader-url.js"></script>
|
||||||
<script src="${resourceUrl}/lib/angular/ui-bootstrap-tpls-0.11.0.js"></script>
|
<script src="${resourceUrl}/lib/angular/ui-bootstrap-tpls-0.11.0.js"></script>
|
||||||
|
|
||||||
<script src="${resourceUrl}/lib/angular/select2.js" type="text/javascript"></script>
|
<script src="${resourceUrl}/lib/angular/select2.js" type="text/javascript"></script>
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
# Common messages
|
||||||
|
enabled=de Enabled
|
||||||
|
name=de Name
|
||||||
|
save=de Save
|
||||||
|
cancel=de Cancel
|
||||||
|
onText=AN
|
||||||
|
offText=AUS
|
||||||
|
client=de Client
|
||||||
|
clear=de Clear
|
||||||
|
|
||||||
|
# Realm settings
|
||||||
|
realm-detail.enabled.tooltip=de Users and clients can only access a realm if it's enabled
|
||||||
|
registrationAllowed=de User registration
|
||||||
|
registrationAllowed.tooltip=de Enable/disable the registration page. A link for registration will show on login page too.
|
||||||
|
registrationEmailAsUsername=de Email as username
|
||||||
|
registrationEmailAsUsername.tooltip=de If enabled then username field is hidden from registration form and email is used as username for new user.
|
||||||
|
editUsernameAllowed=de Edit username
|
||||||
|
editUsernameAllowed.tooltip=de If enabled, the username field is editable, readonly otherwise.
|
||||||
|
resetPasswordAllowed=de Forget password
|
||||||
|
resetPasswordAllowed.tooltip=de Show a link on login page for user to click on when they have forgotten their credentials.
|
||||||
|
rememberMe=de Remember Me
|
||||||
|
rememberMe.tooltip=de Show checkbox on login page to allow user to remain logged in between browser restarts until session expires.
|
||||||
|
verifyEmail=de Verify email
|
||||||
|
verifyEmail.tooltip=de Require the user to verify their email address the first time they login.
|
||||||
|
sslRequired=de Require SSL
|
||||||
|
sslRequired.option.all=de all requests
|
||||||
|
sslRequired.option.external=de external requests
|
||||||
|
sslRequired.option.none=de none
|
||||||
|
sslRequired.tooltip=de Is HTTPS required? 'None' means HTTPS is not required for any client IP address. 'External requests' means localhost and private IP addresses can access without HTTPS. 'All requests' means HTTPS is required for all IP addresses.
|
||||||
|
publicKey=de Public key
|
||||||
|
gen-new-keys=de Generate new keys
|
||||||
|
certificate=de Certificate
|
||||||
|
host=de Host
|
||||||
|
smtp-host=de SMTP Host
|
||||||
|
port=de Port
|
||||||
|
smtp-port=de SMTP Port (defaults to 25)
|
||||||
|
from=de From
|
||||||
|
sender-email-addr=de Sender Email Address
|
||||||
|
enable-ssl=de Enable SSL
|
||||||
|
enable-start-tls=de Enable StartTLS
|
||||||
|
enable-auth=de Enable Authentication
|
||||||
|
username=de Username
|
||||||
|
login-username=de Login Username
|
||||||
|
password=de Password
|
||||||
|
login-password=de Login Password
|
||||||
|
login-theme=de Login Theme
|
||||||
|
select-one=de Select one...
|
||||||
|
login-theme.tooltip=de Select theme for login, TOTP, grant, registration, and forgot password pages.
|
||||||
|
account-theme=de Account Theme
|
||||||
|
account-theme.tooltip=de Select theme for user account management pages.
|
||||||
|
admin-console-theme=de Admin Console Theme
|
||||||
|
select-theme-admin-console=de Select theme for admin console.
|
||||||
|
email-theme=de Email Theme
|
||||||
|
select-theme-email=de Select theme for emails that are sent by the server.
|
||||||
|
i18n-enabled=de Internationalization Enabled
|
||||||
|
supported-locales=de Supported Locales
|
||||||
|
supported-locales.placeholder=de Type a locale and enter
|
||||||
|
default-locale=de Default Locale
|
||||||
|
realm-cache-enabled=de Realm Cache Enabled
|
||||||
|
realm-cache-enabled.tooltip=de Enable/disable cache for realm, client and role data.
|
||||||
|
user-cache-enabled=de User Cache Enabled
|
||||||
|
user-cache-enabled.tooltip=de Enable/disable user and user role mapping cache.
|
||||||
|
sso-session-idle=de SSO Session Idle
|
||||||
|
seconds=de Seconds
|
||||||
|
minutes=de Minutes
|
||||||
|
hours=de Hours
|
||||||
|
days=de Days
|
||||||
|
sso-session-max=de SSO Session Max
|
||||||
|
sso-session-idle.tooltip=de Time a session is allowed to be idle before it expires. Tokens and browser sessions are invalidated when a session is expired.
|
||||||
|
sso-session-max.tooltip=de Max time before a session is expired. Tokens and browser sessions are invalidated when a session is expired.
|
||||||
|
access-token-lifespan=de Access Token Lifespan
|
||||||
|
access-token-lifespan.tooltip=de Max time before an access token is expired. This value is recommended to be short relative to the SSO timeout.
|
||||||
|
client-login-timeout=de Client login timeout
|
||||||
|
client-login-timeout.tooltip=de Max time an client has to finish the access token protocol. This should normally be 1 minute.
|
||||||
|
login-timeout=de Login timeout
|
||||||
|
login-timeout.tooltip=de Max time a user has to complete a login. This is recommended to be relatively long. 30 minutes or more.
|
||||||
|
login-action-timeout=de Login action timeout
|
||||||
|
login-action-timeout.tooltip=de Max time a user has to complete login related actions like update password or configure totp. This is recommended to be relatively long. 5 minutes or more.
|
||||||
|
headers=de Headers
|
||||||
|
brute-force-detection=de Brute Force Detection
|
||||||
|
x-frame-options=de X-Frame-Options
|
||||||
|
click-label-for-info=de Click on label link for more information. The default value prevents pages from being included via non-origin iframes.
|
||||||
|
content-sec-policy=de Content-Security-Policy
|
||||||
|
max-login-failures=de Max Login Failures
|
||||||
|
max-login-failures.tooltip=de How many failures before wait is triggered.
|
||||||
|
wait-increment=de Wait Increment
|
||||||
|
wait-increment.tooltip=de When failure threshold has been met, how much time should the user be locked out?
|
||||||
|
quick-login-check-millis=de Quick Login Check Milli Seconds
|
||||||
|
quick-login-check-millis.tooltip=de If a failure happens concurrently too quickly, lock out the user.
|
||||||
|
min-quick-login-wait=de Minimum Quick Login Wait
|
||||||
|
min-quick-login-wait.tooltip=de How long to wait after a quick login failure.
|
||||||
|
max-wait=de Max Wait
|
||||||
|
max-wait.tooltip=de Max time a user will be locked out.
|
||||||
|
failure-reset-time=de Failure Reset Time
|
||||||
|
failure-reset-time.tooltip=de When will failure count be reset?
|
||||||
|
realm-tab-login=de Login
|
||||||
|
realm-tab-keys=de Keys
|
||||||
|
realm-tab-email=de Email
|
||||||
|
realm-tab-themes=de Themes
|
||||||
|
realm-tab-cache=de Cache
|
||||||
|
realm-tab-tokens=de Tokens
|
||||||
|
realm-tab-security-defenses=de Security Defenses
|
||||||
|
realm-tab-general=de General
|
||||||
|
add-realm=de Add Realm
|
||||||
|
|
||||||
|
#Session settings
|
||||||
|
realm-sessions=de Realm Sessions
|
||||||
|
revocation=de Revocation
|
||||||
|
logout-all=de Logout All
|
||||||
|
active-sessions=de Active Sessions
|
||||||
|
sessions=de Sessions
|
||||||
|
not-before=de Not Before
|
||||||
|
not-before.tooltip=de Revoke any tokens issued before this date.
|
||||||
|
set-to-now=de Set To Now
|
||||||
|
push=de Push
|
||||||
|
push.tooltip=de For every client that has an admin URL, notify them of the new revocation policy.
|
|
@ -0,0 +1,116 @@
|
||||||
|
# Common messages
|
||||||
|
enabled=Enabled
|
||||||
|
name=Name
|
||||||
|
save=Save
|
||||||
|
cancel=Cancel
|
||||||
|
onText=ON
|
||||||
|
offText=OFF
|
||||||
|
client=Client
|
||||||
|
clear=Clear
|
||||||
|
|
||||||
|
# Realm settings
|
||||||
|
realm-detail.enabled.tooltip=Users and clients can only access a realm if it's enabled
|
||||||
|
registrationAllowed=User registration
|
||||||
|
registrationAllowed.tooltip=Enable/disable the registration page. A link for registration will show on login page too.
|
||||||
|
registrationEmailAsUsername=Email as username
|
||||||
|
registrationEmailAsUsername.tooltip=If enabled then username field is hidden from registration form and email is used as username for new user.
|
||||||
|
editUsernameAllowed=Edit username
|
||||||
|
editUsernameAllowed.tooltip=If enabled, the username field is editable, readonly otherwise.
|
||||||
|
resetPasswordAllowed=Forget password
|
||||||
|
resetPasswordAllowed.tooltip=Show a link on login page for user to click on when they have forgotten their credentials.
|
||||||
|
rememberMe=Remember Me
|
||||||
|
rememberMe.tooltip=Show checkbox on login page to allow user to remain logged in between browser restarts until session expires.
|
||||||
|
verifyEmail=Verify email
|
||||||
|
verifyEmail.tooltip=Require the user to verify their email address the first time they login.
|
||||||
|
sslRequired=Require SSL
|
||||||
|
sslRequired.option.all=all requests
|
||||||
|
sslRequired.option.external=external requests
|
||||||
|
sslRequired.option.none=none
|
||||||
|
sslRequired.tooltip=Is HTTPS required? 'None' means HTTPS is not required for any client IP address. 'External requests' means localhost and private IP addresses can access without HTTPS. 'All requests' means HTTPS is required for all IP addresses.
|
||||||
|
publicKey=Public key
|
||||||
|
gen-new-keys=Generate new keys
|
||||||
|
certificate=Certificate
|
||||||
|
host=Host
|
||||||
|
smtp-host=SMTP Host
|
||||||
|
port=Port
|
||||||
|
smtp-port=SMTP Port (defaults to 25)
|
||||||
|
from=From
|
||||||
|
sender-email-addr=Sender Email Address
|
||||||
|
enable-ssl=Enable SSL
|
||||||
|
enable-start-tls=Enable StartTLS
|
||||||
|
enable-auth=Enable Authentication
|
||||||
|
username=Username
|
||||||
|
login-username=Login Username
|
||||||
|
password=Password
|
||||||
|
login-password=Login Password
|
||||||
|
login-theme=Login Theme
|
||||||
|
select-one=Select one...
|
||||||
|
login-theme.tooltip=Select theme for login, TOTP, grant, registration, and forgot password pages.
|
||||||
|
account-theme=Account Theme
|
||||||
|
account-theme.tooltip=Select theme for user account management pages.
|
||||||
|
admin-console-theme=Admin Console Theme
|
||||||
|
select-theme-admin-console=Select theme for admin console.
|
||||||
|
email-theme=Email Theme
|
||||||
|
select-theme-email=Select theme for emails that are sent by the server.
|
||||||
|
i18n-enabled=Internationalization Enabled
|
||||||
|
supported-locales=Supported Locales
|
||||||
|
supported-locales.placeholder=Type a locale and enter
|
||||||
|
default-locale=Default Locale
|
||||||
|
realm-cache-enabled=Realm Cache Enabled
|
||||||
|
realm-cache-enabled.tooltip=Enable/disable cache for realm, client and role data.
|
||||||
|
user-cache-enabled=User Cache Enabled
|
||||||
|
user-cache-enabled.tooltip=Enable/disable user and user role mapping cache.
|
||||||
|
sso-session-idle=SSO Session Idle
|
||||||
|
seconds=Seconds
|
||||||
|
minutes=Minutes
|
||||||
|
hours=Hours
|
||||||
|
days=Days
|
||||||
|
sso-session-max=SSO Session Max
|
||||||
|
sso-session-idle.tooltip=Time a session is allowed to be idle before it expires. Tokens and browser sessions are invalidated when a session is expired.
|
||||||
|
sso-session-max.tooltip=Max time before a session is expired. Tokens and browser sessions are invalidated when a session is expired.
|
||||||
|
access-token-lifespan=Access Token Lifespan
|
||||||
|
access-token-lifespan.tooltip=Max time before an access token is expired. This value is recommended to be short relative to the SSO timeout.
|
||||||
|
client-login-timeout=Client login timeout
|
||||||
|
client-login-timeout.tooltip=Max time an client has to finish the access token protocol. This should normally be 1 minute.
|
||||||
|
login-timeout=Login timeout
|
||||||
|
login-timeout.tooltip=Max time a user has to complete a login. This is recommended to be relatively long. 30 minutes or more.
|
||||||
|
login-action-timeout=Login action timeout
|
||||||
|
login-action-timeout.tooltip=Max time a user has to complete login related actions like update password or configure totp. This is recommended to be relatively long. 5 minutes or more.
|
||||||
|
headers=Headers
|
||||||
|
brute-force-detection=Brute Force Detection
|
||||||
|
x-frame-options=X-Frame-Options
|
||||||
|
click-label-for-info=Click on label link for more information. The default value prevents pages from being included via non-origin iframes.
|
||||||
|
content-sec-policy=Content-Security-Policy
|
||||||
|
max-login-failures=Max Login Failures
|
||||||
|
max-login-failures.tooltip=How many failures before wait is triggered.
|
||||||
|
wait-increment=Wait Increment
|
||||||
|
wait-increment.tooltip=When failure threshold has been met, how much time should the user be locked out?
|
||||||
|
quick-login-check-millis=Quick Login Check Milli Seconds
|
||||||
|
quick-login-check-millis.tooltip=If a failure happens concurrently too quickly, lock out the user.
|
||||||
|
min-quick-login-wait=Minimum Quick Login Wait
|
||||||
|
min-quick-login-wait.tooltip=How long to wait after a quick login failure.
|
||||||
|
max-wait=Max Wait
|
||||||
|
max-wait.tooltip=Max time a user will be locked out.
|
||||||
|
failure-reset-time=Failure Reset Time
|
||||||
|
failure-reset-time.tooltip=When will failure count be reset?
|
||||||
|
realm-tab-login=Login
|
||||||
|
realm-tab-keys=Keys
|
||||||
|
realm-tab-email=Email
|
||||||
|
realm-tab-themes=Themes
|
||||||
|
realm-tab-cache=Cache
|
||||||
|
realm-tab-tokens=Tokens
|
||||||
|
realm-tab-security-defenses=Security Defenses
|
||||||
|
realm-tab-general=General
|
||||||
|
add-realm=Add Realm
|
||||||
|
|
||||||
|
#Session settings
|
||||||
|
realm-sessions=Realm Sessions
|
||||||
|
revocation=Revocation
|
||||||
|
logout-all=Logout All
|
||||||
|
active-sessions=Active Sessions
|
||||||
|
sessions=Sessions
|
||||||
|
not-before=Not Before
|
||||||
|
not-before.tooltip=Revoke any tokens issued before this date.
|
||||||
|
set-to-now=Set To Now
|
||||||
|
push=Push
|
||||||
|
push.tooltip=For every client that has an admin URL, notify them of the new revocation policy.
|
|
@ -7,7 +7,7 @@ var configUrl = consoleBaseUrl + "/config";
|
||||||
|
|
||||||
var auth = {};
|
var auth = {};
|
||||||
|
|
||||||
var module = angular.module('keycloak', [ 'keycloak.services', 'keycloak.loaders', 'ui.bootstrap', 'ui.select2', 'angularFileUpload' ]);
|
var module = angular.module('keycloak', [ 'keycloak.services', 'keycloak.loaders', 'ui.bootstrap', 'ui.select2', 'angularFileUpload', 'pascalprecht.translate', 'ngCookies', 'ngSanitize']);
|
||||||
var resourceRequests = 0;
|
var resourceRequests = 0;
|
||||||
var loadingTimer = -1;
|
var loadingTimer = -1;
|
||||||
|
|
||||||
|
@ -52,8 +52,18 @@ module.factory('authInterceptor', function($q, Auth) {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
module.config(['$translateProvider', function($translateProvider) {
|
||||||
|
$translateProvider.useSanitizeValueStrategy('sanitizeParameters');
|
||||||
|
|
||||||
|
var locale = auth.authz.idTokenParsed.locale;
|
||||||
|
if (locale !== undefined) {
|
||||||
|
$translateProvider.preferredLanguage(locale);
|
||||||
|
} else {
|
||||||
|
$translateProvider.preferredLanguage('en');
|
||||||
|
}
|
||||||
|
|
||||||
|
$translateProvider.useUrlLoader('messages.json');
|
||||||
|
}]);
|
||||||
|
|
||||||
module.config([ '$routeProvider', function($routeProvider) {
|
module.config([ '$routeProvider', function($routeProvider) {
|
||||||
$routeProvider
|
$routeProvider
|
||||||
|
|
|
@ -564,8 +564,6 @@ module.controller('ClientRoleDetailCtrl', function($scope, realm, client, role,
|
||||||
module.controller('ClientImportCtrl', function($scope, $location, $upload, realm, serverInfo, Notifications) {
|
module.controller('ClientImportCtrl', function($scope, $location, $upload, realm, serverInfo, Notifications) {
|
||||||
|
|
||||||
$scope.realm = realm;
|
$scope.realm = realm;
|
||||||
$scope.configFormats = serverInfo.clientImporters;
|
|
||||||
$scope.configFormat = null;
|
|
||||||
|
|
||||||
$scope.files = [];
|
$scope.files = [];
|
||||||
|
|
||||||
|
@ -614,7 +612,6 @@ module.controller('ClientImportCtrl', function($scope, $location, $upload, realm
|
||||||
module.controller('ClientListCtrl', function($scope, realm, clients, Client, serverInfo, $route, Dialog, Notifications) {
|
module.controller('ClientListCtrl', function($scope, realm, clients, Client, serverInfo, $route, Dialog, Notifications) {
|
||||||
$scope.realm = realm;
|
$scope.realm = realm;
|
||||||
$scope.clients = clients;
|
$scope.clients = clients;
|
||||||
$scope.importButton = serverInfo.clientImporters.length > 0;
|
|
||||||
|
|
||||||
$scope.removeClient = function(client) {
|
$scope.removeClient = function(client) {
|
||||||
Dialog.confirmDelete(client.clientId, 'client', function() {
|
Dialog.confirmDelete(client.clientId, 'client', function() {
|
||||||
|
@ -670,7 +667,7 @@ module.controller('ClientInstallationCtrl', function($scope, realm, client, Clie
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
module.controller('ClientDetailCtrl', function($scope, realm, client, $route, serverInfo, Client, $location, Dialog, Notifications) {
|
module.controller('ClientDetailCtrl', function($scope, realm, client, $route, serverInfo, Client, ClientDescriptionConverter, $location, $modal, Dialog, Notifications) {
|
||||||
$scope.accessTypes = [
|
$scope.accessTypes = [
|
||||||
"confidential",
|
"confidential",
|
||||||
"public",
|
"public",
|
||||||
|
@ -709,40 +706,45 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, $route, se
|
||||||
$scope.samlEncrypt = false;
|
$scope.samlEncrypt = false;
|
||||||
$scope.samlForcePostBinding = false;
|
$scope.samlForcePostBinding = false;
|
||||||
$scope.samlForceNameIdFormat = false;
|
$scope.samlForceNameIdFormat = false;
|
||||||
if (!$scope.create) {
|
|
||||||
if (!client.attributes) {
|
function updateProperties() {
|
||||||
client.attributes = {};
|
if (!$scope.client.attributes) {
|
||||||
|
$scope.client.attributes = {};
|
||||||
}
|
}
|
||||||
$scope.client= angular.copy(client);
|
|
||||||
$scope.accessType = $scope.accessTypes[0];
|
$scope.accessType = $scope.accessTypes[0];
|
||||||
if (client.bearerOnly) {
|
if ($scope.client.bearerOnly) {
|
||||||
$scope.accessType = $scope.accessTypes[2];
|
$scope.accessType = $scope.accessTypes[2];
|
||||||
} else if (client.publicClient) {
|
} else if ($scope.client.publicClient) {
|
||||||
$scope.accessType = $scope.accessTypes[1];
|
$scope.accessType = $scope.accessTypes[1];
|
||||||
}
|
}
|
||||||
if (client.protocol) {
|
if ($scope.client.protocol) {
|
||||||
$scope.protocol = $scope.protocols[$scope.protocols.indexOf(client.protocol)];
|
$scope.protocol = $scope.protocols[$scope.protocols.indexOf($scope.client.protocol)];
|
||||||
} else {
|
} else {
|
||||||
$scope.protocol = $scope.protocols[0];
|
$scope.protocol = $scope.protocols[0];
|
||||||
}
|
}
|
||||||
if (client.attributes['saml.signature.algorithm'] == 'RSA_SHA1') {
|
if ($scope.client.attributes['saml.signature.algorithm'] == 'RSA_SHA1') {
|
||||||
$scope.signatureAlgorithm = $scope.signatureAlgorithms[0];
|
$scope.signatureAlgorithm = $scope.signatureAlgorithms[0];
|
||||||
} else if (client.attributes['saml.signature.algorithm'] == 'RSA_SHA256') {
|
} else if ($scope.client.attributes['saml.signature.algorithm'] == 'RSA_SHA256') {
|
||||||
$scope.signatureAlgorithm = $scope.signatureAlgorithms[1];
|
$scope.signatureAlgorithm = $scope.signatureAlgorithms[1];
|
||||||
} else if (client.attributes['saml.signature.algorithm'] == 'RSA_SHA512') {
|
} else if ($scope.client.attributes['saml.signature.algorithm'] == 'RSA_SHA512') {
|
||||||
$scope.signatureAlgorithm = $scope.signatureAlgorithms[2];
|
$scope.signatureAlgorithm = $scope.signatureAlgorithms[2];
|
||||||
} else if (client.attributes['saml.signature.algorithm'] == 'DSA_SHA1') {
|
} else if ($scope.client.attributes['saml.signature.algorithm'] == 'DSA_SHA1') {
|
||||||
$scope.signatureAlgorithm = $scope.signatureAlgorithms[3];
|
$scope.signatureAlgorithm = $scope.signatureAlgorithms[3];
|
||||||
}
|
}
|
||||||
if (client.attributes['saml_name_id_format'] == 'unspecified') {
|
if ($scope.client.attributes['saml_name_id_format'] == 'unspecified') {
|
||||||
$scope.nameIdFormat = $scope.nameIdFormats[0];
|
$scope.nameIdFormat = $scope.nameIdFormats[0];
|
||||||
} else if (client.attributes['saml_name_id_format'] == 'email') {
|
} else if ($scope.client.attributes['saml_name_id_format'] == 'email') {
|
||||||
$scope.nameIdFormat = $scope.nameIdFormats[1];
|
$scope.nameIdFormat = $scope.nameIdFormats[1];
|
||||||
} else if (client.attributes['saml_name_id_format'] == 'transient') {
|
} else if ($scope.client.attributes['saml_name_id_format'] == 'transient') {
|
||||||
$scope.nameIdFormat = $scope.nameIdFormats[2];
|
$scope.nameIdFormat = $scope.nameIdFormats[2];
|
||||||
} else if (client.attributes['saml_name_id_format'] == 'persistent') {
|
} else if ($scope.client.attributes['saml_name_id_format'] == 'persistent') {
|
||||||
$scope.nameIdFormat = $scope.nameIdFormats[3];
|
$scope.nameIdFormat = $scope.nameIdFormats[3];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$scope.create) {
|
||||||
|
$scope.client = angular.copy(client);
|
||||||
|
updateProperties();
|
||||||
} else {
|
} else {
|
||||||
$scope.client = { enabled: true, attributes: {}};
|
$scope.client = { enabled: true, attributes: {}};
|
||||||
$scope.client.attributes['saml_signature_canonicalization_method'] = $scope.canonicalization[0].value;
|
$scope.client.attributes['saml_signature_canonicalization_method'] = $scope.canonicalization[0].value;
|
||||||
|
@ -813,6 +815,29 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, $route, se
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scope.importFile = function(fileContent){
|
||||||
|
console.debug(fileContent);
|
||||||
|
ClientDescriptionConverter.save({
|
||||||
|
realm: realm.realm
|
||||||
|
}, fileContent, function (data) {
|
||||||
|
$scope.client = data;
|
||||||
|
updateProperties();
|
||||||
|
$scope.importing = true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.viewImportDetails = function() {
|
||||||
|
$modal.open({
|
||||||
|
templateUrl: resourceUrl + '/partials/modal/view-object.html',
|
||||||
|
controller: 'JsonModalCtrl',
|
||||||
|
resolve: {
|
||||||
|
object: function () {
|
||||||
|
return $scope.client;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
$scope.switchChange = function() {
|
$scope.switchChange = function() {
|
||||||
$scope.changed = true;
|
$scope.changed = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -927,6 +927,13 @@ module.factory('Client', function($resource) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
module.factory('ClientDescriptionConverter', function($resource) {
|
||||||
|
return $resource(authUrl + '/admin/realms/:realm/client-description-converter', {
|
||||||
|
realm : '@realm'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
module.factory('ClientInstallation', function($resource) {
|
module.factory('ClientInstallation', function($resource) {
|
||||||
var url = authUrl + '/admin/realms/:realm/clients/:client/installation/json';
|
var url = authUrl + '/admin/realms/:realm/clients/:client/installation/json';
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -2,102 +2,102 @@
|
||||||
<kc-tabs-realm></kc-tabs-realm>
|
<kc-tabs-realm></kc-tabs-realm>
|
||||||
|
|
||||||
<ul class="nav nav-tabs nav-tabs-pf">
|
<ul class="nav nav-tabs nav-tabs-pf">
|
||||||
<li><a href="#/realms/{{realm.realm}}/defense/headers">Headers</a></li>
|
<li><a href="#/realms/{{realm.realm}}/defense/headers">{{:: 'headers' | translate}}</a></li>
|
||||||
<li class="active"><a href="#/realms/{{realm.realm}}/defense/brute-force">Brute Force Detection</a></li>
|
<li class="active"><a href="#/realms/{{realm.realm}}/defense/brute-force">{{:: 'brute-force-detection' | translate}}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||||
<fieldset class="border-top">
|
<fieldset class="border-top">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="bruteForceProtected">Enabled</label>
|
<label class="col-md-2 control-label" for="bruteForceProtected">{{:: 'enabled' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input ng-model="realm.bruteForceProtected" name="bruteForceProtected" id="bruteForceProtected" onoffswitch />
|
<input ng-model="realm.bruteForceProtected" name="bruteForceProtected" id="bruteForceProtected" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" data-ng-show="realm.bruteForceProtected">
|
<div class="form-group" data-ng-show="realm.bruteForceProtected">
|
||||||
<label class="col-md-2 control-label" for="failureFactor">Max Login Failures</label>
|
<label class="col-md-2 control-label" for="failureFactor">{{:: 'max-login-failures' | translate}}</label>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input class="form-control" type="number" min="1" max="31536000" id="failureFactor" name="failureFactor" data-ng-model="realm.failureFactor" autofocus
|
<input class="form-control" type="number" min="1" max="31536000" id="failureFactor" name="failureFactor" data-ng-model="realm.failureFactor" autofocus
|
||||||
required>
|
required>
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>How many failures before wait is triggered.</kc-tooltip>
|
<kc-tooltip>{{:: 'max-login-failures.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" data-ng-show="realm.bruteForceProtected">
|
<div class="form-group" data-ng-show="realm.bruteForceProtected">
|
||||||
<label class="col-md-2 control-label" for="waitIncrement">Wait Increment</label>
|
<label class="col-md-2 control-label" for="waitIncrement">{{:: 'wait-increment' | translate}}</label>
|
||||||
<div class="col-md-6 time-selector">
|
<div class="col-md-6 time-selector">
|
||||||
<input class="form-control" type="number" required min="1"
|
<input class="form-control" type="number" required min="1"
|
||||||
max="31536000" data-ng-model="realm.waitIncrement"
|
max="31536000" data-ng-model="realm.waitIncrement"
|
||||||
id="waitIncrement" name="waitIncrement"/>
|
id="waitIncrement" name="waitIncrement"/>
|
||||||
<select class="form-control" name="waitIncrementUnit" data-ng-model="realm.waitIncrementUnit" >
|
<select class="form-control" name="waitIncrementUnit" data-ng-model="realm.waitIncrementUnit" >
|
||||||
<option data-ng-selected="!realm.waitIncrementUnit">Seconds</option>
|
<option data-ng-selected="!realm.waitIncrementUnit" value="Seconds">{{:: 'seconds' | translate}}</option>
|
||||||
<option>Minutes</option>
|
<option value="Minutes">{{:: 'minutes' | translate}}</option>
|
||||||
<option>Hours</option>
|
<option value="Hours">{{:: 'hours' | translate}}</option>
|
||||||
<option>Days</option>
|
<option value="Days">{{:: 'days' | translate}}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>When failure threshold has been met, how much time should the user be locked out?</kc-tooltip>
|
<kc-tooltip>{{:: 'wait-increment.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" data-ng-show="realm.bruteForceProtected">
|
<div class="form-group" data-ng-show="realm.bruteForceProtected">
|
||||||
<label class="col-md-2 control-label" for="quickLoginCheckMilliSeconds">Quick Login Check Milli Seconds</label>
|
<label class="col-md-2 control-label" for="quickLoginCheckMilliSeconds">{{:: 'quick-login-check-millis' | translate}}</label>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input class="form-control" type="number" min="1" max="31536000" id="quickLoginCheckMilliSeconds" name="quickLoginCheckMilliSeconds" data-ng-model="realm.quickLoginCheckMilliSeconds" autofocus
|
<input class="form-control" type="number" min="1" max="31536000" id="quickLoginCheckMilliSeconds" name="quickLoginCheckMilliSeconds" data-ng-model="realm.quickLoginCheckMilliSeconds" autofocus
|
||||||
required>
|
required>
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>If a failure happens concurrently too quickly, lock out the user.</kc-tooltip>
|
<kc-tooltip>{{:: 'quick-login-check-millis.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" data-ng-show="realm.bruteForceProtected">
|
<div class="form-group" data-ng-show="realm.bruteForceProtected">
|
||||||
<label class="col-md-2 control-label" for="minimumQuickLoginWait">Minimum Quick Login Wait</label>
|
<label class="col-md-2 control-label" for="minimumQuickLoginWait">{{:: 'min-quick-login-wait' | translate}}</label>
|
||||||
<div class="col-md-6 time-selector">
|
<div class="col-md-6 time-selector">
|
||||||
<input class="form-control" type="number" required min="1"
|
<input class="form-control" type="number" required min="1"
|
||||||
max="31536000" data-ng-model="realm.minimumQuickLoginWait"
|
max="31536000" data-ng-model="realm.minimumQuickLoginWait"
|
||||||
id="minimumQuickLoginWait" name="minimumQuickLoginWait"/>
|
id="minimumQuickLoginWait" name="minimumQuickLoginWait"/>
|
||||||
<select class="form-control" name="minimumQuickLoginWaitUnit" data-ng-model="realm.minimumQuickLoginWaitUnit" >
|
<select class="form-control" name="minimumQuickLoginWaitUnit" data-ng-model="realm.minimumQuickLoginWaitUnit" >
|
||||||
<option data-ng-selected="!realm.minimumQuickLoginWaitUnit">Seconds</option>
|
<option data-ng-selected="!realm.minimumQuickLoginWaitUnit" value="Seconds">{{:: 'seconds' | translate}}</option>
|
||||||
<option>Minutes</option>
|
<option value="Minutes">{{:: 'minutes' | translate}}</option>
|
||||||
<option>Hours</option>
|
<option value="Hours">{{:: 'hours' | translate}}</option>
|
||||||
<option>Days</option>
|
<option value="Days">{{:: 'days' | translate}}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>How long to wait after a quick login failure.</kc-tooltip>
|
<kc-tooltip>{{:: 'min-quick-login-wait.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" data-ng-show="realm.bruteForceProtected">
|
<div class="form-group" data-ng-show="realm.bruteForceProtected">
|
||||||
<label class="col-md-2 control-label" for="maxFailureWait">Max Wait</label>
|
<label class="col-md-2 control-label" for="maxFailureWait">{{:: 'max-wait' | translate}}</label>
|
||||||
<div class="col-md-6 time-selector">
|
<div class="col-md-6 time-selector">
|
||||||
<input class="form-control" type="number" required min="1"
|
<input class="form-control" type="number" required min="1"
|
||||||
max="31536000" data-ng-model="realm.maxFailureWait"
|
max="31536000" data-ng-model="realm.maxFailureWait"
|
||||||
id="maxFailureWait" name="maxFailureWait"/>
|
id="maxFailureWait" name="maxFailureWait"/>
|
||||||
<select class="form-control" name="maxFailureWaitUnit" data-ng-model="realm.maxFailureWaitUnit" >
|
<select class="form-control" name="maxFailureWaitUnit" data-ng-model="realm.maxFailureWaitUnit" >
|
||||||
<option data-ng-selected="!realm.maxFailureWaitUnit">Seconds</option>
|
<option data-ng-selected="!realm.maxFailureWaitUnit" value="Seconds">{{:: 'seconds' | translate}}</option>
|
||||||
<option>Minutes</option>
|
<option value="Minutes">{{:: 'minutes' | translate}}</option>
|
||||||
<option>Hours</option>
|
<option value="Hours">{{:: 'hours' | translate}}</option>
|
||||||
<option>Days</option>
|
<option value="Days">{{:: 'days' | translate}}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>Max time a user will be locked out.</kc-tooltip>
|
<kc-tooltip>{{:: 'max-wait.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" data-ng-show="realm.bruteForceProtected">
|
<div class="form-group" data-ng-show="realm.bruteForceProtected">
|
||||||
<label class="col-md-2 control-label" for="maxDeltaTime">Failure Reset Time</label>
|
<label class="col-md-2 control-label" for="maxDeltaTime">{{:: 'failure-reset-time' | translate}}</label>
|
||||||
<div class="col-md-6 time-selector">
|
<div class="col-md-6 time-selector">
|
||||||
<input class="form-control" type="number" required min="1"
|
<input class="form-control" type="number" required min="1"
|
||||||
max="31536000" data-ng-model="realm.maxDeltaTime"
|
max="31536000" data-ng-model="realm.maxDeltaTime"
|
||||||
id="maxDeltaTime" name="maxDeltaTime"/>
|
id="maxDeltaTime" name="maxDeltaTime"/>
|
||||||
<select class="form-control" name="maxDeltaTimeUnit" data-ng-model="realm.maxDeltaTimeUnit" >
|
<select class="form-control" name="maxDeltaTimeUnit" data-ng-model="realm.maxDeltaTimeUnit" >
|
||||||
<option data-ng-selected="!realm.maxDeltaTimeUnit">Seconds</option>
|
<option data-ng-selected="!realm.maxDeltaTimeUnit" value="Seconds">{{:: 'seconds' | translate}}</option>
|
||||||
<option>Minutes</option>
|
<option value="Minutes">{{:: 'minutes' | translate}}</option>
|
||||||
<option>Hours</option>
|
<option value="Hours">{{:: 'hours' | translate}}</option>
|
||||||
<option>Days</option>
|
<option value="Days">{{:: 'days' | translate}}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>When will failure count be reset?</kc-tooltip>
|
<kc-tooltip>{{:: 'failure-reset-time.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<div class="form-group" data-ng-show="access.manageRealm">
|
<div class="form-group" data-ng-show="access.manageRealm">
|
||||||
<div class="col-md-10 col-md-offset-2">
|
<div class="col-md-10 col-md-offset-2">
|
||||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
<button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
|
||||||
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
<button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -10,6 +10,20 @@
|
||||||
|
|
||||||
<form class="form-horizontal" name="clientForm" novalidate kc-read-only="!access.manageClients">
|
<form class="form-horizontal" name="clientForm" novalidate kc-read-only="!access.manageClients">
|
||||||
<fieldset class="border-top">
|
<fieldset class="border-top">
|
||||||
|
<div class="form-group" data-ng-show="create">
|
||||||
|
<label for="name" class="col-sm-2 control-label">Import</label>
|
||||||
|
|
||||||
|
<div class="col-md-6" data-ng-hide="importing">
|
||||||
|
<label for="import-file" class="btn btn-default">Select file <i class="pficon pficon-import"></i></label>
|
||||||
|
<input id="import-file" type="file" class="hidden" kc-on-read-file="importFile($fileContent)">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6" data-ng-show="importing">
|
||||||
|
<button class="btn btn-default" data-ng-click="viewImportDetails()">View details</button>
|
||||||
|
<button class="btn btn-default" data-ng-click="reset()">Clear import</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="clientId">Client ID <span class="required" data-ng-show="create">*</span></label>
|
<label class="col-md-2 control-label" for="clientId">Client ID <span class="required" data-ng-show="create">*</span></label>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
|
@ -174,6 +188,14 @@
|
||||||
<kc-tooltip>The name ID format to use for the subject.</kc-tooltip>
|
<kc-tooltip>The name ID format to use for the subject.</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" data-ng-show="!client.bearerOnly">
|
||||||
|
<label class="col-md-2 control-label" for="rootUrl">Root URL</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input class="form-control" type="text" name="rootUrl" id="rootUrl" data-ng-model="client.rootUrl">
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>Root URL appended to relative URLs</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group clearfix block" data-ng-hide="client.bearerOnly || client.directGrantsOnly">
|
<div class="form-group clearfix block" data-ng-hide="client.bearerOnly || client.directGrantsOnly">
|
||||||
<label class="col-md-2 control-label" for="newRedirectUri"><span class="required" data-ng-show="protocol != 'saml'">*</span> Valid Redirect URIs</label>
|
<label class="col-md-2 control-label" for="newRedirectUri"><span class="required" data-ng-show="protocol != 'saml'">*</span> Valid Redirect URIs</label>
|
||||||
|
|
||||||
|
@ -252,7 +274,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<kc-tooltip>Allowed CORS origins.</kc-tooltip>
|
<kc-tooltip>Allowed CORS origins. To permit all origins of Valid Redirect URIs add '+'. To permit all origins add '*'.</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset data-ng-show="protocol == 'saml'">
|
<fieldset data-ng-show="protocol == 'saml'">
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
<td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}">{{client.clientId}}</a></td>
|
<td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}">{{client.clientId}}</a></td>
|
||||||
<td>{{client.enabled}}</td>
|
<td>{{client.enabled}}</td>
|
||||||
<td ng-class="{'text-muted': !client.baseUrl}">
|
<td ng-class="{'text-muted': !client.baseUrl}">
|
||||||
<a href="{{client.baseUrl}}" target="_blank" data-ng-show="client.baseUrl">{{client.baseUrl}}</a>
|
<a href="{{client.rootUrl}}{{client.baseUrl}}" target="_blank" data-ng-show="client.baseUrl">{{client.rootUrl}}{{client.baseUrl}}</a>
|
||||||
<span data-ng-hide="client.baseUrl">Not defined</span>
|
<span data-ng-hide="client.baseUrl">Not defined</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="kc-action-cell">
|
<td class="kc-action-cell">
|
||||||
|
|
|
@ -2,31 +2,31 @@
|
||||||
<kc-tabs-realm></kc-tabs-realm>
|
<kc-tabs-realm></kc-tabs-realm>
|
||||||
|
|
||||||
<ul class="nav nav-tabs nav-tabs-pf">
|
<ul class="nav nav-tabs nav-tabs-pf">
|
||||||
<li class="active"><a href="#/realms/{{realm.realm}}/defense/headers">Headers</a></li>
|
<li class="active"><a href="#/realms/{{realm.realm}}/defense/headers">{{:: 'headers' | translate}}</a></li>
|
||||||
<li><a href="#/realms/{{realm.realm}}/defense/brute-force">Brute Force Detection</a></li>
|
<li><a href="#/realms/{{realm.realm}}/defense/brute-force">{{:: 'brute-force-detection' | translate}}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||||
<fieldset class="border-top">
|
<fieldset class="border-top">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="xFrameOptions"><a href="http://tools.ietf.org/html/rfc7034">X-Frame-Options</a></label>
|
<label class="col-md-2 control-label" for="xFrameOptions"><a href="http://tools.ietf.org/html/rfc7034">{{:: 'x-frame-options' | translate}}</a></label>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<input class="form-control" id="xFrameOptions" type="text" ng-model="realm.browserSecurityHeaders.xFrameOptions">
|
<input class="form-control" id="xFrameOptions" type="text" ng-model="realm.browserSecurityHeaders.xFrameOptions">
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>Click on label link for more information. The default value prevents pages from being included via non-origin iframes.</kc-tooltip>
|
<kc-tooltip>{{:: 'click-label-for-info' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="contentSecurityPolicy"><a href="http://www.w3.org/TR/CSP/">Content-Security-Policy</a></label>
|
<label class="col-md-2 control-label" for="contentSecurityPolicy"><a href="http://www.w3.org/TR/CSP/">{{:: 'content-sec-policy' | translate}}</a></label>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<input class="form-control" id="contentSecurityPolicy" type="text" ng-model="realm.browserSecurityHeaders.contentSecurityPolicy">
|
<input class="form-control" id="contentSecurityPolicy" type="text" ng-model="realm.browserSecurityHeaders.contentSecurityPolicy">
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>Click on label link for more information. The default value prevents pages from being included via non-origin iframes.</kc-tooltip>
|
<kc-tooltip>{{:: 'click-label-for-info' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<div class="form-group" data-ng-show="access.manageRealm">
|
<div class="form-group" data-ng-show="access.manageRealm">
|
||||||
<div class="col-md-10 col-md-offset-2">
|
<div class="col-md-10 col-md-offset-2">
|
||||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
<button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
|
||||||
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
<button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -3,24 +3,24 @@
|
||||||
|
|
||||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="realmCacheEnabled">Realm Cache Enabled</label>
|
<label class="col-md-2 control-label" for="realmCacheEnabled">{{:: 'realm-cache-enabled' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input ng-model="realm.realmCacheEnabled" name="realmCacheEnabled" id="realmCacheEnabled" onoffswitch />
|
<input ng-model="realm.realmCacheEnabled" name="realmCacheEnabled" id="realmCacheEnabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>Enable/disable cache for realm, client and role data.</kc-tooltip>
|
<kc-tooltip>{{:: 'realm-cache-enabled.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="userCacheEnabled">User Cache Enabled</label>
|
<label class="col-md-2 control-label" for="userCacheEnabled">{{:: 'user-cache-enabled' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input ng-model="realm.userCacheEnabled" name="userCacheEnabled" id="userCacheEnabled" onoffswitch />
|
<input ng-model="realm.userCacheEnabled" name="userCacheEnabled" id="userCacheEnabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>Enable/disable user and user role mapping cache.</kc-tooltip>
|
<kc-tooltip>{{:: 'user-cache-enabled.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group" data-ng-show="access.manageRealm">
|
<div class="form-group" data-ng-show="access.manageRealm">
|
||||||
<div class="col-md-10 col-md-offset-2">
|
<div class="col-md-10 col-md-offset-2">
|
||||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
<button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
|
||||||
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
<button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||||
|
|
||||||
|
<h1>Add Realm</h1>
|
||||||
|
|
||||||
<form class="form-horizontal" name="realmForm" novalidate>
|
<form class="form-horizontal" name="realmForm" novalidate>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend><span class="text">Create Realm</span></legend>
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="name" class="col-sm-2 control-label">Import</label>
|
<label for="name" class="col-sm-2 control-label">Import</label>
|
||||||
|
|
||||||
|
|
|
@ -3,29 +3,29 @@
|
||||||
|
|
||||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="name"><span class="required">*</span> Name</label>
|
<label class="col-md-2 control-label" for="name"><span class="required">*</span> {{:: 'name' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input class="form-control" type="text" id="name" name="name" data-ng-model="realm.realm" autofocus required>
|
<input class="form-control" type="text" id="name" name="name" data-ng-model="realm.realm" autofocus required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="enabled">Enabled</label>
|
<label class="col-md-2 control-label" for="enabled">{{:: 'enabled' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input ng-model="realm.enabled" name="enabled" id="enabled" onoffswitch />
|
<input ng-model="realm.enabled" name="enabled" id="enabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>Users and clients can only access a realm if it's enabled</kc-tooltip>
|
<kc-tooltip>{{:: 'realm-detail.enabled.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-md-10 col-md-offset-2" data-ng-show="createRealm && access.manageRealm">
|
<div class="col-md-10 col-md-offset-2" data-ng-show="createRealm && access.manageRealm">
|
||||||
<button kc-save data-ng-show="changed">Save</button>
|
<button kc-save data-ng-show="changed">{{:: 'save' | translate}}</button>
|
||||||
<button kc-cancel data-ng-click="cancel()">Cancel</button>
|
<button kc-cancel data-ng-click="cancel()">{{:: 'cancel' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-10 col-md-offset-2" data-ng-show="!createRealm && access.manageRealm">
|
<div class="col-md-10 col-md-offset-2" data-ng-show="!createRealm && access.manageRealm">
|
||||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
<button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
|
||||||
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
<button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||||
<fieldset class="border-top">
|
<fieldset class="border-top">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="publicKey">Public key</label>
|
<label class="col-md-2 control-label" for="publicKey">{{:: 'publicKey' | translate}}</label>
|
||||||
|
|
||||||
<div class="col-md-10">
|
<div class="col-md-10">
|
||||||
<textarea type="text" id="publicKey" name="publicKey" class="form-control" rows="4"
|
<textarea type="text" id="publicKey" name="publicKey" class="form-control" rows="4"
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="certificate">Certificate</label>
|
<label class="col-md-2 control-label" for="certificate">{{:: 'certificate' | translate}}</label>
|
||||||
|
|
||||||
<div class="col-md-10">
|
<div class="col-md-10">
|
||||||
<textarea type="text" id="certificate" name="certificate" class="form-control" rows="8" kc-select-action="click" readonly>{{realm.certificate}}</textarea>
|
<textarea type="text" id="certificate" name="certificate" class="form-control" rows="8" kc-select-action="click" readonly>{{realm.certificate}}</textarea>
|
||||||
|
@ -22,7 +22,7 @@
|
||||||
|
|
||||||
<div class="form-group" data-ng-show="access.manageRealm">
|
<div class="form-group" data-ng-show="access.manageRealm">
|
||||||
<div class="col-md-10 col-md-offset-2">
|
<div class="col-md-10 col-md-offset-2">
|
||||||
<button class="btn btn-danger" type="submit" data-ng-click="generate()">Generate new keys</button>
|
<button class="btn btn-danger" type="submit" data-ng-click="generate()">{{:: 'gen-new-keys' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -4,66 +4,66 @@
|
||||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||||
<fieldset class="border-top">
|
<fieldset class="border-top">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="registrationAllowed" class="col-md-2 control-label">User registration</label>
|
<label for="registrationAllowed" class="col-md-2 control-label">{{:: 'registrationAllowed' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input ng-model="realm.registrationAllowed" name="registrationAllowed" id="registrationAllowed" onoffswitch />
|
<input ng-model="realm.registrationAllowed" name="registrationAllowed" id="registrationAllowed" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>Enable/disable the registration page. A link for registration will show on login page too.</kc-tooltip>
|
<kc-tooltip>{{:: 'registrationAllowed.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-show="realm.registrationAllowed">
|
<div class="form-group" ng-show="realm.registrationAllowed">
|
||||||
<label for="registrationEmailAsUsername" class="col-md-2 control-label">Email as username</label>
|
<label for="registrationEmailAsUsername" class="col-md-2 control-label">{{:: 'registrationEmailAsUsername' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input ng-model="realm.registrationEmailAsUsername" name="registrationEmailAsUsername" id="registrationEmailAsUsername" onoffswitch />
|
<input ng-model="realm.registrationEmailAsUsername" name="registrationEmailAsUsername" id="registrationEmailAsUsername" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>If enabled then username field is hidden from registration form and email is used as username for new user.</kc-tooltip>
|
<kc-tooltip>{{:: 'registrationEmailAsUsername.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="editUsernameAllowed" class="col-md-2 control-label">Edit username</label>
|
<label for="editUsernameAllowed" class="col-md-2 control-label">{{:: 'editUsernameAllowed' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input ng-model="realm.editUsernameAllowed" name="editUsernameAllowed" id="editUsernameAllowed" onoffswitch />
|
<input ng-model="realm.editUsernameAllowed" name="editUsernameAllowed" id="editUsernameAllowed" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>If enabled, the username field is editable, readonly otherwise.</kc-tooltip>
|
<kc-tooltip>{{:: 'editUsernameAllowed.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="resetPasswordAllowed" class="col-md-2 control-label">Forget password</label>
|
<label for="resetPasswordAllowed" class="col-md-2 control-label">{{:: 'resetPasswordAllowed' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input ng-model="realm.resetPasswordAllowed" name="resetPasswordAllowed" id="resetPasswordAllowed" onoffswitch />
|
<input ng-model="realm.resetPasswordAllowed" name="resetPasswordAllowed" id="resetPasswordAllowed" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>Show a link on login page for user to click on when they have forgotten their credentials.</kc-tooltip>
|
<kc-tooltip>{{:: 'resetPasswordAllowed.tooltip' |translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="rememberMe">Remember Me</label>
|
<label class="col-md-2 control-label" for="rememberMe">{{:: 'rememberMe' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input ng-model="realm.rememberMe" name="rememberMe" id="rememberMe" onoffswitch />
|
<input ng-model="realm.rememberMe" name="rememberMe" id="rememberMe" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>Show checkbox on login page to allow user to remain logged in between browser restarts until session expires.</kc-tooltip>
|
<kc-tooltip>{{:: 'rememberMe.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="verifyEmail" class="col-md-2 control-label">Verify email</label>
|
<label for="verifyEmail" class="col-md-2 control-label">{{:: 'verifyEmail' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input ng-model="realm.verifyEmail" name="verifyEmail" id="verifyEmail" onoffswitch />
|
<input ng-model="realm.verifyEmail" name="verifyEmail" id="verifyEmail" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>Require the user to verify their email address the first time they login.</kc-tooltip>
|
<kc-tooltip>{{:: 'verifyEmail.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="sslRequired" class="col-md-2 control-label">Require SSL</label>
|
<label for="sslRequired" class="col-md-2 control-label">{{:: 'sslRequired' | translate}}</label>
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<div>
|
<div>
|
||||||
<select id="sslRequired" ng-model="realm.sslRequired" class="form-control">
|
<select id="sslRequired" ng-model="realm.sslRequired" class="form-control">
|
||||||
<option value="all">all requests</option>
|
<option value="all">{{:: 'sslRequired.option.all' | translate}}</option>
|
||||||
<option value="external">external requests</option>
|
<option value="external">{{:: 'sslRequired.option.external' | translate}}</option>
|
||||||
<option value="none">none</option>
|
<option value="none">{{:: 'sslRequired.option.none' | translate}}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>Is HTTPS required? 'None' means HTTPS is not required for any client IP address. 'External requests' means localhost and private IP addresses can access without HTTPS. 'All requests' means HTTPS is required for all IP addresses.</kc-tooltip>
|
<kc-tooltip>{{:: 'sslRequired.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<div class="form-group" data-ng-show="access.manageRealm">
|
<div class="form-group" data-ng-show="access.manageRealm">
|
||||||
<div class="col-md-10 col-md-offset-2">
|
<div class="col-md-10 col-md-offset-2">
|
||||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
<button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
|
||||||
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
<button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -3,59 +3,59 @@
|
||||||
|
|
||||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||||
<div class="form-group clearfix">
|
<div class="form-group clearfix">
|
||||||
<label class="col-md-2 control-label" for="smtpHost"><span class="required">*</span> Host</label>
|
<label class="col-md-2 control-label" for="smtpHost"><span class="required">*</span> {{:: 'host' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input class="form-control" id="smtpHost" type="text" ng-model="realm.smtpServer.host" placeholder="SMTP Host" required>
|
<input class="form-control" id="smtpHost" type="text" ng-model="realm.smtpServer.host" placeholder="{{:: 'smtp-host' | translate}}" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group clearfix">
|
<div class="form-group clearfix">
|
||||||
<label class="col-md-2 control-label" for="smtpPort">Port</label>
|
<label class="col-md-2 control-label" for="smtpPort">{{:: 'port' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input class="form-control" id="smtpPort" type="number" ng-model="realm.smtpServer.port" placeholder="SMTP Port (defaults to 25)">
|
<input class="form-control" id="smtpPort" type="number" ng-model="realm.smtpServer.port" placeholder="{{:: 'smtp-port' | translate}}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group clearfix">
|
<div class="form-group clearfix">
|
||||||
<label class="col-md-2 control-label" for="smtpFrom"><span class="required">*</span> From</label>
|
<label class="col-md-2 control-label" for="smtpFrom"><span class="required">*</span> {{:: 'from' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input class="form-control" id="smtpFrom" type="email" ng-model="realm.smtpServer.from" placeholder="Sender Email Address" required>
|
<input class="form-control" id="smtpFrom" type="email" ng-model="realm.smtpServer.from" placeholder="{{:: 'sender-email-addr' | translate}}" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group clearfix">
|
<div class="form-group clearfix">
|
||||||
<label class="col-md-2 control-label" for="smtpSSL">Enable SSL</label>
|
<label class="col-md-2 control-label" for="smtpSSL">{{:: 'enable-ssl' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input ng-model="realm.smtpServer.ssl" name="smtpSSL" id="smtpSSL" onoffswitch />
|
<input ng-model="realm.smtpServer.ssl" name="smtpSSL" id="smtpSSL" onoffswitch onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group clearfix">
|
<div class="form-group clearfix">
|
||||||
<label class="col-md-2 control-label" for="smtpStartTLS">Enable StartTLS</label>
|
<label class="col-md-2 control-label" for="smtpStartTLS">{{:: 'enable-start-tls' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input ng-model="realm.smtpServer.starttls" name="smtpStartTLS" id="smtpStartTLS" onoffswitch />
|
<input ng-model="realm.smtpServer.starttls" name="smtpStartTLS" id="smtpStartTLS" onoffswitch onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group clearfix">
|
<div class="form-group clearfix">
|
||||||
<label class="col-md-2 control-label" for="smtpAuth">Enable Authentication</label>
|
<label class="col-md-2 control-label" for="smtpAuth">{{:: 'enable-auth' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input ng-model="realm.smtpServer.auth" name="smtpAuth" id="smtpAuth" onoffswitch />
|
<input ng-model="realm.smtpServer.auth" name="smtpAuth" id="smtpAuth" onoffswitch onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group clearfix" data-ng-show="realm.smtpServer.auth">
|
<div class="form-group clearfix" data-ng-show="realm.smtpServer.auth">
|
||||||
<label class="col-md-2 control-label" for="smtpUsername"><span class="required">*</span> Username</span></label>
|
<label class="col-md-2 control-label" for="smtpUsername"><span class="required">*</span> {{:: 'username' | translate}}</span></label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input class="form-control" id="smtpUsername" type="text" ng-model="realm.smtpServer.user" placeholder="Login Username" ng-disabled="!realm.smtpServer.auth" ng-required="realm.smtpServer.auth">
|
<input class="form-control" id="smtpUsername" type="text" ng-model="realm.smtpServer.user" placeholder="{{:: 'login-username' | translate}}" ng-disabled="!realm.smtpServer.auth" ng-required="realm.smtpServer.auth">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group clearfix" data-ng-show="realm.smtpServer.auth">
|
<div class="form-group clearfix" data-ng-show="realm.smtpServer.auth">
|
||||||
<label class="col-md-2 control-label" for="smtpPassword"><span class="required">*</span> Password</label>
|
<label class="col-md-2 control-label" for="smtpPassword"><span class="required">*</span> {{:: 'password' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input class="form-control" id="smtpPassword" type="password" ng-model="realm.smtpServer.password" placeholder="Login Password" ng-disabled="!realm.smtpServer.auth" ng-required="realm.smtpServer.auth">
|
<input class="form-control" id="smtpPassword" type="password" ng-model="realm.smtpServer.password" placeholder="{{:: 'login-password' | translate}}" ng-disabled="!realm.smtpServer.auth" ng-required="realm.smtpServer.auth">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group" data-ng-show="access.manageRealm">
|
<div class="form-group" data-ng-show="access.manageRealm">
|
||||||
<div class="col-md-10 col-md-offset-2">
|
<div class="col-md-10 col-md-offset-2">
|
||||||
<button data-kc-save data-ng-disabled="!changed">Save</button>
|
<button data-kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
|
||||||
<button data-kc-reset data-ng-disabled="!changed">Cancel</button>
|
<button data-kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -4,72 +4,72 @@
|
||||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||||
<fieldset class="border-top">
|
<fieldset class="border-top">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="loginTheme">Login Theme</label>
|
<label class="col-md-2 control-label" for="loginTheme">{{:: 'login-theme' | translate}}</label>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div>
|
<div>
|
||||||
<select class="form-control" id="loginTheme"
|
<select class="form-control" id="loginTheme"
|
||||||
ng-model="realm.loginTheme"
|
ng-model="realm.loginTheme"
|
||||||
ng-options="o as o for o in serverInfo.themes.login">
|
ng-options="o as o for o in serverInfo.themes.login">
|
||||||
<option value="" disabled selected>Select one...</option>
|
<option value="" disabled selected>{{:: 'select-one' | translate}}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>Select theme for login, TOTP, grant, registration, and forgot password pages.</kc-tooltip>
|
<kc-tooltip>{{:: 'login-theme.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="accountTheme">Account Theme</label>
|
<label class="col-md-2 control-label" for="accountTheme">{{:: 'account-theme' | translate}}</label>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div>
|
<div>
|
||||||
<select class="form-control" id="accountTheme"
|
<select class="form-control" id="accountTheme"
|
||||||
ng-model="realm.accountTheme"
|
ng-model="realm.accountTheme"
|
||||||
ng-options="o as o for o in serverInfo.themes.account">
|
ng-options="o as o for o in serverInfo.themes.account">
|
||||||
<option value="" disabled selected>Select one...</option>
|
<option value="" disabled selected>{{:: 'select-one' | translate}}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>Select theme for user account management pages.</kc-tooltip>
|
<kc-tooltip>{{ 'account-theme.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="adminTheme">Admin Console Theme</label>
|
<label class="col-md-2 control-label" for="adminTheme">{{:: 'admin-console-theme' | translate}}</label>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div>
|
<div>
|
||||||
<select class="form-control" id="adminTheme"
|
<select class="form-control" id="adminTheme"
|
||||||
ng-model="realm.adminTheme"
|
ng-model="realm.adminTheme"
|
||||||
ng-options="o as o for o in serverInfo.themes.admin">
|
ng-options="o as o for o in serverInfo.themes.admin">
|
||||||
<option value="" disabled selected>Select one...</option>
|
<option value="" disabled selected>{{:: 'select-one' | translate}}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>Select theme for admin console.</kc-tooltip>
|
<kc-tooltip>{{:: 'select-theme-admin-console' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="emailTheme">Email Theme</label>
|
<label class="col-md-2 control-label" for="emailTheme">{{:: 'email-theme' | translate}}</label>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div>
|
<div>
|
||||||
<select class="form-control" id="emailTheme"
|
<select class="form-control" id="emailTheme"
|
||||||
ng-model="realm.emailTheme"
|
ng-model="realm.emailTheme"
|
||||||
ng-options="o as o for o in serverInfo.themes.email">
|
ng-options="o as o for o in serverInfo.themes.email">
|
||||||
<option value="" disabled selected>Select one...</option>
|
<option value="" disabled selected>{{:: 'select-one' | translate}}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>Select theme for emails that are sent by the server.</kc-tooltip>
|
<kc-tooltip>{{:: 'select-theme-email' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="internationalizationEnabled">Internationalization Enabled</label>
|
<label class="col-md-2 control-label" for="internationalizationEnabled">{{:: 'i18n-enabled' | translate}}</label>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<input ng-model="realm.internationalizationEnabled" name="internationalizationEnabled" id="internationalizationEnabled" onoffswitch />
|
<input ng-model="realm.internationalizationEnabled" name="internationalizationEnabled" id="internationalizationEnabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" data-ng-show="realm.internationalizationEnabled">
|
<div class="form-group" data-ng-show="realm.internationalizationEnabled">
|
||||||
<label class="col-md-2 control-label" for="supportedLocales" class="control-label two-lines">Supported Locales</label>
|
<label class="col-md-2 control-label" for="supportedLocales" class="control-label two-lines">{{:: 'supported-locales' | translate}}</label>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input id="supportedLocales" type="text" ui-select2="supportedLocalesOptions" ng-model="realm.supportedLocales" placeholder="Type a locale and enter" ng-required="realm.internationalizationEnabled" ng-disabled="!realm.internationalizationEnabled">
|
<input id="supportedLocales" type="text" ui-select2="supportedLocalesOptions" ng-model="realm.supportedLocales" placeholder="{{:: 'supported-locales.placeholder' | translate}}" ng-required="realm.internationalizationEnabled" ng-disabled="!realm.internationalizationEnabled">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" data-ng-show="realm.internationalizationEnabled">
|
<div class="form-group" data-ng-show="realm.internationalizationEnabled">
|
||||||
<label class="col-md-2 control-label" for="defaultLocale">Default Locale</label>
|
<label class="col-md-2 control-label" for="defaultLocale">{{:: 'default-locale' | translate}}</label>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div>
|
<div>
|
||||||
<select class="form-control" id="defaultLocale"
|
<select class="form-control" id="defaultLocale"
|
||||||
|
@ -77,7 +77,7 @@
|
||||||
ng-options="o as o for o in realm.supportedLocales"
|
ng-options="o as o for o in realm.supportedLocales"
|
||||||
ng-required="realm.internationalizationEnabled"
|
ng-required="realm.internationalizationEnabled"
|
||||||
ng-disabled="!realm.internationalizationEnabled">
|
ng-disabled="!realm.internationalizationEnabled">
|
||||||
<option value="" disabled selected>Select one...</option>
|
<option value="" disabled selected>{{:: 'select-one' | translate}}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -86,8 +86,8 @@
|
||||||
|
|
||||||
<div class="form-group" data-ng-show="access.manageRealm">
|
<div class="form-group" data-ng-show="access.manageRealm">
|
||||||
<div class="col-md-10 col-md-offset-2">
|
<div class="col-md-10 col-md-offset-2">
|
||||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
<button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
|
||||||
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
<button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -4,110 +4,110 @@
|
||||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="ssoSessionIdleTimeout">SSO Session Idle</label>
|
<label class="col-md-2 control-label" for="ssoSessionIdleTimeout">{{:: 'sso-session-idle' | translate}}</label>
|
||||||
|
|
||||||
<div class="col-md-6 time-selector">
|
<div class="col-md-6 time-selector">
|
||||||
<input class="form-control" type="number" required min="1" max="31536000" data-ng-model="realm.ssoSessionIdleTimeout" id="ssoSessionIdleTimeout"
|
<input class="form-control" type="number" required min="1" max="31536000" data-ng-model="realm.ssoSessionIdleTimeout" id="ssoSessionIdleTimeout"
|
||||||
name="ssoSessionIdleTimeout"/>
|
name="ssoSessionIdleTimeout"/>
|
||||||
<select class="form-control" name="ssoSessionIdleTimeoutUnit" data-ng-model="realm.ssoSessionIdleTimeoutUnit">
|
<select class="form-control" name="ssoSessionIdleTimeoutUnit" data-ng-model="realm.ssoSessionIdleTimeoutUnit">
|
||||||
<option data-ng-selected="!realm.ssoSessionIdleTimeoutUnit">Seconds</option>
|
<option data-ng-selected="!realm.ssoSessionIdleTimeoutUnit" value="Seconds">{{:: 'seconds' | translate}}</option>
|
||||||
<option>Minutes</option>
|
<option value="Minutes">{{:: 'minutes' | translate}}</option>
|
||||||
<option>Hours</option>
|
<option value="Hours">{{:: 'hours' | translate}}</option>
|
||||||
<option>Days</option>
|
<option value="Days">{{:: 'days' | translate}}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>Time a session is allowed to be idle before it expires. Tokens and browser sessions are invalidated when a session is expired.
|
<kc-tooltip>{{:: 'sso-session-idle.tooltip' | translate}}
|
||||||
</kc-tooltip>
|
</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="ssoSessionMaxLifespan">SSO Session Max</label>
|
<label class="col-md-2 control-label" for="ssoSessionMaxLifespan">{{:: 'sso-session-max' | translate}}</label>
|
||||||
|
|
||||||
<div class="col-md-6 time-selector">
|
<div class="col-md-6 time-selector">
|
||||||
<input class="form-control" type="number" required min="1"
|
<input class="form-control" type="number" required min="1"
|
||||||
max="31536000" data-ng-model="realm.ssoSessionMaxLifespan"
|
max="31536000" data-ng-model="realm.ssoSessionMaxLifespan"
|
||||||
id="ssoSessionMaxLifespan" name="ssoSessionMaxLifespan"/>
|
id="ssoSessionMaxLifespan" name="ssoSessionMaxLifespan"/>
|
||||||
<select class="form-control" name="ssoSessionMaxLifespanUnit" data-ng-model="realm.ssoSessionMaxLifespanUnit">
|
<select class="form-control" name="ssoSessionMaxLifespanUnit" data-ng-model="realm.ssoSessionMaxLifespanUnit">
|
||||||
<option data-ng-selected="!realm.ssoSessionMaxLifespanUnit">Seconds</option>
|
<option data-ng-selected="!realm.ssoSessionMaxLifespanUnit" value="Seconds">{{:: 'seconds' | translate}}</option>
|
||||||
<option>Minutes</option>
|
<option value="Minutes">{{:: 'minutes' | translate}}</option>
|
||||||
<option>Hours</option>
|
<option value="Hours">{{:: 'hours' | translate}}</option>
|
||||||
<option>Days</option>
|
<option value="Days">{{:: 'days' | translate}}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>Max time before a session is expired. Tokens and browser sessions are invalidated when a session is expired.</kc-tooltip>
|
<kc-tooltip>{{:: 'sso-session-max.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="accessTokenLifespan">Access Token Lifespan</label>
|
<label class="col-md-2 control-label" for="accessTokenLifespan">{{:: 'access-token-lifespan' | translate}}</label>
|
||||||
|
|
||||||
<div class="col-md-6 time-selector">
|
<div class="col-md-6 time-selector">
|
||||||
<input class="form-control" type="number" required min="1"
|
<input class="form-control" type="number" required min="1"
|
||||||
max="31536000" data-ng-model="realm.accessTokenLifespan"
|
max="31536000" data-ng-model="realm.accessTokenLifespan"
|
||||||
id="accessTokenLifespan" name="accessTokenLifespan"/>
|
id="accessTokenLifespan" name="accessTokenLifespan"/>
|
||||||
<select class="form-control" name="accessTokenLifespanUnit" data-ng-model="realm.accessTokenLifespanUnit">
|
<select class="form-control" name="accessTokenLifespanUnit" data-ng-model="realm.accessTokenLifespanUnit">
|
||||||
<option data-ng-selected="!realm.accessTokenLifespanUnit">Seconds</option>
|
<option data-ng-selected="!realm.accessTokenLifespanUnit" value="Seconds">{{:: 'seconds' | translate}}</option>
|
||||||
<option>Minutes</option>
|
<option value="Minutes">{{:: 'minutes' | translate}}</option>
|
||||||
<option>Hours</option>
|
<option value="Hours">{{:: 'hours' | translate}}</option>
|
||||||
<option>Days</option>
|
<option value="Days">{{:: 'days' | translate}}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>Max time before an access token is expired. This value is recommended to be short relative to the SSO timeout.</kc-tooltip>
|
<kc-tooltip>{{:: 'access-token-lifespan.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="accessCodeLifespan">Client login timeout</label>
|
<label class="col-md-2 control-label" for="accessCodeLifespan">{{:: 'client-login-timeout' | translate}}</label>
|
||||||
|
|
||||||
<div class="col-md-6 time-selector">
|
<div class="col-md-6 time-selector">
|
||||||
<input class="form-control" type="number" required min="1" max="31536000" data-ng-model="realm.accessCodeLifespan" id="accessCodeLifespan"
|
<input class="form-control" type="number" required min="1" max="31536000" data-ng-model="realm.accessCodeLifespan" id="accessCodeLifespan"
|
||||||
name="accessCodeLifespan">
|
name="accessCodeLifespan">
|
||||||
<select class="form-control" name="accessCodeLifespanUnit" data-ng-model="realm.accessCodeLifespanUnit">
|
<select class="form-control" name="accessCodeLifespanUnit" data-ng-model="realm.accessCodeLifespanUnit">
|
||||||
<option data-ng-selected="!realm.accessCodeLifespanUnit">Seconds</option>
|
<option data-ng-selected="!realm.accessCodeLifespanUnit" value="Seconds">{{:: 'seconds' | translate}}</option>
|
||||||
<option>Minutes</option>
|
<option value="Minutes">{{:: 'minutes' | translate}}</option>
|
||||||
<option>Hours</option>
|
<option value="Hours">{{:: 'hours' | translate}}</option>
|
||||||
<option>Days</option>
|
<option value="Days">{{:: 'days' | translate}}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>Max time an client has to finish the access token protocol. This should normally be 1 minute.</kc-tooltip>
|
<kc-tooltip>{{:: 'client-login-timeout.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="accessCodeLifespanLogin" class="two-lines">Login timeout</label>
|
<label class="col-md-2 control-label" for="accessCodeLifespanLogin" class="two-lines">{{:: 'login-timeout' | translate}}</label>
|
||||||
|
|
||||||
<div class="col-md-6 time-selector">
|
<div class="col-md-6 time-selector">
|
||||||
<input class="form-control" type="number" required min="1" max="31536000" data-ng-model="realm.accessCodeLifespanLogin"
|
<input class="form-control" type="number" required min="1" max="31536000" data-ng-model="realm.accessCodeLifespanLogin"
|
||||||
id="accessCodeLifespanLogin" name="accessCodeLifespanLogin">
|
id="accessCodeLifespanLogin" name="accessCodeLifespanLogin">
|
||||||
<select class="form-control" name="accessCodeLifespanLoginUnit" data-ng-model="realm.accessCodeLifespanLoginUnit">
|
<select class="form-control" name="accessCodeLifespanLoginUnit" data-ng-model="realm.accessCodeLifespanLoginUnit">
|
||||||
<option data-ng-selected="!realm.accessCodeLifespanLoginUnit">Seconds</option>
|
<option data-ng-selected="!realm.accessCodeLifespanLoginUnit" value="Seconds">{{:: 'seconds' | translate}}</option>
|
||||||
<option>Minutes</option>
|
<option value="Minutes">{{:: 'minutes' | translate}}</option>
|
||||||
<option>Hours</option>
|
<option value="Hours">{{:: 'hours' | translate}}</option>
|
||||||
<option>Days</option>
|
<option value="Days">{{:: 'days' | translate}}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>Max time a user has to complete a login. This is recommended to be relatively long. 30 minutes or more.</kc-tooltip>
|
<kc-tooltip>{{:: 'login-timeout' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="accessCodeLifespanUserAction" class="two-lines">Login action timeout</label>
|
<label class="col-md-2 control-label" for="accessCodeLifespanUserAction" class="two-lines">{{:: 'login-action-timeout' | translate}}</label>
|
||||||
|
|
||||||
<div class="col-md-6 time-selector">
|
<div class="col-md-6 time-selector">
|
||||||
<input class="form-control" type="number" required min="1" max="31536000" data-ng-model="realm.accessCodeLifespanUserAction"
|
<input class="form-control" type="number" required min="1" max="31536000" data-ng-model="realm.accessCodeLifespanUserAction"
|
||||||
id="accessCodeLifespanUserAction" name="accessCodeLifespanUserAction">
|
id="accessCodeLifespanUserAction" name="accessCodeLifespanUserAction">
|
||||||
<select class="form-control" name="accessCodeLifespanUserActionUnit" data-ng-model="realm.accessCodeLifespanUserActionUnit">
|
<select class="form-control" name="accessCodeLifespanUserActionUnit" data-ng-model="realm.accessCodeLifespanUserActionUnit">
|
||||||
<option data-ng-selected="!realm.accessCodeLifespanUserActionUnit">Seconds</option>
|
<option data-ng-selected="!realm.accessCodeLifespanUserActionUnit" value="Seconds">{{:: 'seconds' | translate}}</option>
|
||||||
<option>Minutes</option>
|
<option value="Minutes">{{:: 'minutes' | translate}}</option>
|
||||||
<option>Hours</option>
|
<option value="Hours">{{:: 'hours' | translate}}</option>
|
||||||
<option>Days</option>
|
<option value="Days">{{:: 'days' | translate}}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>
|
<kc-tooltip>
|
||||||
Max time a user has to complete login related actions like update password or configure totp. This is recommended to be relatively long. 5 minutes or more.
|
{{:: 'login-action-timeout.tooltip' | translate}}
|
||||||
</kc-tooltip>
|
</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-md-10 col-md-offset-2" data-ng-show="access.manageRealm">
|
<div class="col-md-10 col-md-offset-2" data-ng-show="access.manageRealm">
|
||||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
<button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
|
||||||
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
<button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||||
<h1>Sessions</h1>
|
<h1>{{:: 'sessions' | translate}}</h1>
|
||||||
|
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li class="active"><a href="#/realms/{{realm.realm}}/sessions/realm">Realm Sessions</a></li>
|
<li class="active"><a href="#/realms/{{realm.realm}}/sessions/realm">{{:: 'realm-sessions' | translate}}</a></li>
|
||||||
<li><a href="#/realms/{{realm.realm}}/sessions/revocation">Revocation</a></li>
|
<li><a href="#/realms/{{realm.realm}}/sessions/revocation">{{:: 'revocation' | translate}}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<table class="table table-striped table-bordered">
|
<table class="table table-striped table-bordered">
|
||||||
|
@ -11,13 +11,13 @@
|
||||||
<tr>
|
<tr>
|
||||||
<th class="kc-table-actions" colspan="3">
|
<th class="kc-table-actions" colspan="3">
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<a id="logoutAllSessions" class="btn btn-default" ng-click="logoutAll()">Logout All</a>
|
<a id="logoutAllSessions" class="btn btn-default" ng-click="logoutAll()">{{:: 'logout-all' | translate}}</a>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Client</th>
|
<th>{{:: 'client' | translate}}</th>
|
||||||
<th>Active Sessions</th>
|
<th>{{:: 'active-sessions' | translate}}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||||
<h1>Sessions</h1>
|
<h1>{{:: 'sessions' | translate}}</h1>
|
||||||
|
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li><a href="#/realms/{{realm.realm}}/sessions/realm">Realm Sessions</a></li>
|
<li><a href="#/realms/{{realm.realm}}/sessions/realm">{{:: 'realm-sessions' | translate}}</a></li>
|
||||||
<li class="active"><a href="#/realms/{{realm.realm}}/sessions/revocation">Revocation</a></li>
|
<li class="active"><a href="#/realms/{{realm.realm}}/sessions/revocation">{{:: 'revocation' | translate}}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<form class="form-horizontal" name="credentialForm" novalidate kc-read-only="!access.manageRealm">
|
<form class="form-horizontal" name="credentialForm" novalidate kc-read-only="!access.manageRealm">
|
||||||
<fieldset class="border-top">
|
<fieldset class="border-top">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="notBefore">Not Before</label>
|
<label class="col-md-2 control-label" for="notBefore">{{:: 'not-before' | translate}}</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input ng-disabled="true" class="form-control" type="text" id="notBefore" name="notBefore" data-ng-model="notBefore" autofocus>
|
<input ng-disabled="true" class="form-control" type="text" id="notBefore" name="notBefore" data-ng-model="notBefore" autofocus>
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>Revoke any tokens issued before this date.</kc-tooltip>
|
<kc-tooltip>{{:: 'not-before.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-md-10 col-md-offset-2" data-ng-show="access.manageClients">
|
<div class="col-md-10 col-md-offset-2" data-ng-show="access.manageClients">
|
||||||
<button type="submit" data-ng-click="setNotBeforeNow()" class="btn btn-default">Set To Now</button>
|
<button type="submit" data-ng-click="setNotBeforeNow()" class="btn btn-default">{{:: 'set-to-now' | translate}}</button>
|
||||||
<button type="submit" data-ng-click="clear()" class="btn btn-default">Clear</button>
|
<button type="submit" data-ng-click="clear()" class="btn btn-default">{{:: 'clear' | translate}}</button>
|
||||||
<button type="submit" data-ng-click="pushRevocation()" class="btn btn-primary" tooltip-trigger="mouseover mouseout" tooltip="For every client that has an admin URL, notify them of the new revocation policy." tooltip-placement="bottom">Push</button>
|
<button type="submit" data-ng-click="pushRevocation()" class="btn btn-primary" tooltip-trigger="mouseover mouseout" tooltip="{{:: 'push.tooltip' | translate}}" tooltip-placement="bottom">{{:: 'push' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -3,16 +3,16 @@
|
||||||
{{realm.realm|capitalize}}
|
{{realm.realm|capitalize}}
|
||||||
<i id="removeRealm" class="pficon pficon-delete clickable" data-ng-show="access.manageRealm" data-ng-click="removeRealm()"></i>
|
<i id="removeRealm" class="pficon pficon-delete clickable" data-ng-show="access.manageRealm" data-ng-click="removeRealm()"></i>
|
||||||
</h1>
|
</h1>
|
||||||
<h1 data-ng-show="createRealm">Add Realm</h1>
|
<h1 data-ng-show="createRealm">{{:: 'add-realm' | translate}}</h1>
|
||||||
|
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li ng-class="{active: !path[2]}"><a href="#/realms/{{realm.realm}}">General</a></li>
|
<li ng-class="{active: !path[2]}"><a href="#/realms/{{realm.realm}}">{{:: 'realm-tab-general' | translate}}</a></li>
|
||||||
<li ng-class="{active: path[2] == 'login-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/login-settings">Login</a></li>
|
<li ng-class="{active: path[2] == 'login-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/login-settings">{{:: 'realm-tab-login' | translate}}</a></li>
|
||||||
<li ng-class="{active: path[2] == 'keys-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/keys-settings">Keys</a></li>
|
<li ng-class="{active: path[2] == 'keys-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/keys-settings">{{:: 'realm-tab-keys' | translate}}</a></li>
|
||||||
<li ng-class="{active: path[2] == 'smtp-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/smtp-settings">Email</a></li>
|
<li ng-class="{active: path[2] == 'smtp-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/smtp-settings">{{:: 'realm-tab-email' | translate}}</a></li>
|
||||||
<li ng-class="{active: path[2] == 'theme-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/theme-settings">Themes</a></li>
|
<li ng-class="{active: path[2] == 'theme-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/theme-settings">{{:: 'realm-tab-themes' | translate}}</a></li>
|
||||||
<li ng-class="{active: path[2] == 'cache-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/cache-settings">Cache</a></li>
|
<li ng-class="{active: path[2] == 'cache-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/cache-settings">{{:: 'realm-tab-cache' | translate}}</a></li>
|
||||||
<li ng-class="{active: path[2] == 'token-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/token-settings">Tokens</a></li>
|
<li ng-class="{active: path[2] == 'token-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/token-settings">{{:: 'realm-tab-tokens' | translate}}</a></li>
|
||||||
<li ng-class="{active: path[2] == 'defense'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/defense/headers">Security Defenses</a></li>
|
<li ng-class="{active: path[2] == 'defense'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/defense/headers">{{:: 'realm-tab-security-defenses' | translate}}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*!
|
||||||
|
* angular-translate - v2.7.2 - 2015-06-01
|
||||||
|
* http://github.com/angular-translate/angular-translate
|
||||||
|
* Copyright (c) 2015 ; Licensed MIT
|
||||||
|
*/
|
||||||
|
(function (root, factory) {
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
// AMD. Register as an anonymous module unless amdModuleId is set
|
||||||
|
define([], function () {
|
||||||
|
return (factory());
|
||||||
|
});
|
||||||
|
} else if (typeof exports === 'object') {
|
||||||
|
// Node. Does not work with strict CommonJS, but
|
||||||
|
// only CommonJS-like environments that support module.exports,
|
||||||
|
// like Node.
|
||||||
|
module.exports = factory();
|
||||||
|
} else {
|
||||||
|
factory();
|
||||||
|
}
|
||||||
|
}(this, function () {
|
||||||
|
|
||||||
|
angular.module('pascalprecht.translate')
|
||||||
|
/**
|
||||||
|
* @ngdoc object
|
||||||
|
* @name pascalprecht.translate.$translateUrlLoader
|
||||||
|
* @requires $q
|
||||||
|
* @requires $http
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* Creates a loading function for a typical dynamic url pattern:
|
||||||
|
* "locale.php?lang=en_US", "locale.php?lang=de_DE", "locale.php?language=nl_NL" etc.
|
||||||
|
* Prefixing the specified url, the current requested, language id will be applied
|
||||||
|
* with "?{queryParameter}={key}".
|
||||||
|
* Using this service, the response of these urls must be an object of
|
||||||
|
* key-value pairs.
|
||||||
|
*
|
||||||
|
* @param {object} options Options object, which gets the url, key and
|
||||||
|
* optional queryParameter ('lang' is used by default).
|
||||||
|
*/
|
||||||
|
.factory('$translateUrlLoader', $translateUrlLoader);
|
||||||
|
|
||||||
|
function $translateUrlLoader($q, $http) {
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
return function (options) {
|
||||||
|
|
||||||
|
if (!options || !options.url) {
|
||||||
|
throw new Error('Couldn\'t use urlLoader since no url is given!');
|
||||||
|
}
|
||||||
|
|
||||||
|
var deferred = $q.defer(),
|
||||||
|
requestParams = {};
|
||||||
|
|
||||||
|
requestParams[options.queryParameter || 'lang'] = options.key;
|
||||||
|
|
||||||
|
$http(angular.extend({
|
||||||
|
url: options.url,
|
||||||
|
params: requestParams,
|
||||||
|
method: 'GET'
|
||||||
|
}, options.$http)).success(function (data) {
|
||||||
|
deferred.resolve(data);
|
||||||
|
}).error(function () {
|
||||||
|
deferred.reject(options.key);
|
||||||
|
});
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
$translateUrlLoader.$inject = ['$q', '$http'];
|
||||||
|
|
||||||
|
$translateUrlLoader.displayName = '$translateUrlLoader';
|
||||||
|
return 'pascalprecht.translate';
|
||||||
|
|
||||||
|
}));
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*!
|
||||||
|
* angular-translate - v2.6.1 - 2015-03-01
|
||||||
|
* http://github.com/angular-translate/angular-translate
|
||||||
|
* Copyright (c) 2015 ; Licensed MIT
|
||||||
|
*/
|
||||||
|
angular.module('pascalprecht.translate')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ngdoc object
|
||||||
|
* @name pascalprecht.translate.$translateCookieStorage
|
||||||
|
* @requires $cookieStore
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* Abstraction layer for cookieStore. This service is used when telling angular-translate
|
||||||
|
* to use cookieStore as storage.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
.factory('$translateCookieStorage', ['$cookieStore', function ($cookieStore) {
|
||||||
|
|
||||||
|
var $translateCookieStorage = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ngdoc function
|
||||||
|
* @name pascalprecht.translate.$translateCookieStorage#get
|
||||||
|
* @methodOf pascalprecht.translate.$translateCookieStorage
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* Returns an item from cookieStorage by given name.
|
||||||
|
*
|
||||||
|
* @param {string} name Item name
|
||||||
|
* @return {string} Value of item name
|
||||||
|
*/
|
||||||
|
get: function (name) {
|
||||||
|
return $cookieStore.get(name);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ngdoc function
|
||||||
|
* @name pascalprecht.translate.$translateCookieStorage#set
|
||||||
|
* @methodOf pascalprecht.translate.$translateCookieStorage
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* Sets an item in cookieStorage by given name.
|
||||||
|
*
|
||||||
|
* @deprecated use #put
|
||||||
|
*
|
||||||
|
* @param {string} name Item name
|
||||||
|
* @param {string} value Item value
|
||||||
|
*/
|
||||||
|
set: function (name, value) {
|
||||||
|
$cookieStore.put(name, value);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ngdoc function
|
||||||
|
* @name pascalprecht.translate.$translateCookieStorage#put
|
||||||
|
* @methodOf pascalprecht.translate.$translateCookieStorage
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* Sets an item in cookieStorage by given name.
|
||||||
|
*
|
||||||
|
* @param {string} name Item name
|
||||||
|
* @param {string} value Item value
|
||||||
|
*/
|
||||||
|
put: function (name, value) {
|
||||||
|
$cookieStore.put(name, value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return $translateCookieStorage;
|
||||||
|
}]);
|
2904
forms/common-themes/src/main/resources/theme/keycloak/common/resources/lib/angular/angular-translate.js
vendored
Normal file
2904
forms/common-themes/src/main/resources/theme/keycloak/common/resources/lib/angular/angular-translate.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
package org.keycloak.login.freemarker;
|
package org.keycloak.login.freemarker;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
@ -241,7 +257,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client != null) {
|
if (client != null) {
|
||||||
attributes.put("client", new ClientBean(client));
|
attributes.put("client", new ClientBean(client, baseUri));
|
||||||
}
|
}
|
||||||
|
|
||||||
attributes.put("login", new LoginBean(formData));
|
attributes.put("login", new LoginBean(formData));
|
||||||
|
@ -276,7 +292,10 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
for (Map.Entry<String, String> entry : httpResponseHeaders.entrySet()) {
|
for (Map.Entry<String, String> entry : httpResponseHeaders.entrySet()) {
|
||||||
builder.header(entry.getKey(), entry.getValue());
|
builder.header(entry.getKey(), entry.getValue());
|
||||||
}
|
}
|
||||||
LocaleHelper.updateLocaleCookie(builder, locale, realm, uriInfo, Urls.localeCookiePath(baseUri, realm.getName()));
|
|
||||||
|
String cookiePath = Urls.localeCookiePath(baseUri, realm.getName());
|
||||||
|
LocaleHelper.updateLocaleCookie(builder, locale, realm, uriInfo, cookiePath);
|
||||||
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
} catch (FreeMarkerException e) {
|
} catch (FreeMarkerException e) {
|
||||||
logger.error("Failed to process template", e);
|
logger.error("Failed to process template", e);
|
||||||
|
@ -322,7 +341,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
logger.warn("Failed to load properties", e);
|
logger.warn("Failed to load properties", e);
|
||||||
}
|
}
|
||||||
if (client != null) {
|
if (client != null) {
|
||||||
attributes.put("client", new ClientBean(client));
|
attributes.put("client", new ClientBean(client, baseUri));
|
||||||
}
|
}
|
||||||
|
|
||||||
Properties messagesBundle;
|
Properties messagesBundle;
|
||||||
|
@ -374,7 +393,9 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
for (Map.Entry<String, String> entry : httpResponseHeaders.entrySet()) {
|
for (Map.Entry<String, String> entry : httpResponseHeaders.entrySet()) {
|
||||||
builder.header(entry.getKey(), entry.getValue());
|
builder.header(entry.getKey(), entry.getValue());
|
||||||
}
|
}
|
||||||
LocaleHelper.updateLocaleCookie(builder, locale, realm, uriInfo, Urls.localeCookiePath(baseUri, realm.getName()));
|
|
||||||
|
String cookiePath = Urls.localeCookiePath(baseUri, realm.getName());
|
||||||
|
LocaleHelper.updateLocaleCookie(builder, locale, realm, uriInfo, cookiePath);
|
||||||
return builder.build();
|
return builder.build();
|
||||||
} catch (FreeMarkerException e) {
|
} catch (FreeMarkerException e) {
|
||||||
logger.error("Failed to process template", e);
|
logger.error("Failed to process template", e);
|
||||||
|
@ -383,26 +404,32 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
public Response createLogin() {
|
public Response createLogin() {
|
||||||
return createResponse(LoginFormsPages.LOGIN);
|
return createResponse(LoginFormsPages.LOGIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Response createPasswordReset() {
|
public Response createPasswordReset() {
|
||||||
return createResponse(LoginFormsPages.LOGIN_RESET_PASSWORD);
|
return createResponse(LoginFormsPages.LOGIN_RESET_PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Response createLoginTotp() {
|
public Response createLoginTotp() {
|
||||||
return createResponse(LoginFormsPages.LOGIN_TOTP);
|
return createResponse(LoginFormsPages.LOGIN_TOTP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Response createRegistration() {
|
public Response createRegistration() {
|
||||||
return createResponse(LoginFormsPages.REGISTER);
|
return createResponse(LoginFormsPages.REGISTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Response createInfoPage() {
|
public Response createInfoPage() {
|
||||||
return createResponse(LoginFormsPages.INFO);
|
return createResponse(LoginFormsPages.INFO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Response createErrorPage() {
|
public Response createErrorPage() {
|
||||||
if (status == null) {
|
if (status == null) {
|
||||||
status = Response.Status.INTERNAL_SERVER_ERROR;
|
status = Response.Status.INTERNAL_SERVER_ERROR;
|
||||||
|
@ -410,7 +437,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
return createResponse(LoginFormsPages.ERROR);
|
return createResponse(LoginFormsPages.ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Response createOAuthGrant(ClientSessionModel clientSession) {
|
public Response createOAuthGrant(ClientSessionModel clientSession) {
|
||||||
this.clientSession = clientSession;
|
this.clientSession = clientSession;
|
||||||
return createResponse(LoginFormsPages.OAUTH_GRANT);
|
return createResponse(LoginFormsPages.OAUTH_GRANT);
|
||||||
|
@ -494,11 +521,13 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public FreeMarkerLoginFormsProvider setUser(UserModel user) {
|
public FreeMarkerLoginFormsProvider setUser(UserModel user) {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public FreeMarkerLoginFormsProvider setFormData(MultivaluedMap<String, String> formData) {
|
public FreeMarkerLoginFormsProvider setFormData(MultivaluedMap<String, String> formData) {
|
||||||
this.formData = formData;
|
this.formData = formData;
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package org.keycloak.login.freemarker.model;
|
package org.keycloak.login.freemarker.model;
|
||||||
|
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.services.util.ResolveRelative;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -10,8 +13,11 @@ public class ClientBean {
|
||||||
|
|
||||||
protected ClientModel client;
|
protected ClientModel client;
|
||||||
|
|
||||||
public ClientBean(ClientModel client) {
|
private URI requestUri;
|
||||||
|
|
||||||
|
public ClientBean(ClientModel client, URI requestUri) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
this.requestUri = requestUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getClientId() {
|
public String getClientId() {
|
||||||
|
@ -23,7 +29,7 @@ public class ClientBean {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBaseUrl() {
|
public String getBaseUrl() {
|
||||||
return client.getBaseUrl();
|
return ResolveRelative.resolveRelativeUri(requestUri, client.getRootUrl(), client.getBaseUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
package org.keycloak.admin.client.resource;
|
package org.keycloak.admin.client.resource;
|
||||||
|
|
||||||
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.*;
|
||||||
import javax.ws.rs.DELETE;
|
|
||||||
import javax.ws.rs.GET;
|
|
||||||
import javax.ws.rs.PUT;
|
|
||||||
import javax.ws.rs.Path;
|
|
||||||
import javax.ws.rs.Produces;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -28,6 +24,12 @@ public interface RealmResource {
|
||||||
@Path("clients")
|
@Path("clients")
|
||||||
ClientsResource clients();
|
ClientsResource clients();
|
||||||
|
|
||||||
|
@Path("client-description-converter")
|
||||||
|
@POST
|
||||||
|
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN })
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
ClientRepresentation convertClientDescription(String description);
|
||||||
|
|
||||||
@Path("users")
|
@Path("users")
|
||||||
UsersResource users();
|
UsersResource users();
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,10 @@ public interface ClientModel extends RoleContainerModel {
|
||||||
|
|
||||||
void setManagementUrl(String url);
|
void setManagementUrl(String url);
|
||||||
|
|
||||||
|
String getRootUrl();
|
||||||
|
|
||||||
|
void setRootUrl(String url);
|
||||||
|
|
||||||
String getBaseUrl();
|
String getBaseUrl();
|
||||||
|
|
||||||
void setBaseUrl(String url);
|
void setBaseUrl(String url);
|
||||||
|
|
|
@ -24,6 +24,7 @@ public class ClientEntity extends AbstractIdentifiableEntity {
|
||||||
|
|
||||||
private boolean surrogateAuthRequired;
|
private boolean surrogateAuthRequired;
|
||||||
private String managementUrl;
|
private String managementUrl;
|
||||||
|
private String rootUrl;
|
||||||
private String baseUrl;
|
private String baseUrl;
|
||||||
private boolean bearerOnly;
|
private boolean bearerOnly;
|
||||||
private boolean consentRequired;
|
private boolean consentRequired;
|
||||||
|
@ -196,6 +197,14 @@ public class ClientEntity extends AbstractIdentifiableEntity {
|
||||||
this.managementUrl = managementUrl;
|
this.managementUrl = managementUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRootUrl() {
|
||||||
|
return rootUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRootUrl(String rootUrl) {
|
||||||
|
this.rootUrl = rootUrl;
|
||||||
|
}
|
||||||
|
|
||||||
public String getBaseUrl() {
|
public String getBaseUrl() {
|
||||||
return baseUrl;
|
return baseUrl;
|
||||||
}
|
}
|
||||||
|
|
|
@ -306,6 +306,7 @@ public class ModelToRepresentation {
|
||||||
rep.setServiceAccountsEnabled(clientModel.isServiceAccountsEnabled());
|
rep.setServiceAccountsEnabled(clientModel.isServiceAccountsEnabled());
|
||||||
rep.setDirectGrantsOnly(clientModel.isDirectGrantsOnly());
|
rep.setDirectGrantsOnly(clientModel.isDirectGrantsOnly());
|
||||||
rep.setSurrogateAuthRequired(clientModel.isSurrogateAuthRequired());
|
rep.setSurrogateAuthRequired(clientModel.isSurrogateAuthRequired());
|
||||||
|
rep.setRootUrl(clientModel.getRootUrl());
|
||||||
rep.setBaseUrl(clientModel.getBaseUrl());
|
rep.setBaseUrl(clientModel.getBaseUrl());
|
||||||
rep.setNotBefore(clientModel.getNotBefore());
|
rep.setNotBefore(clientModel.getNotBefore());
|
||||||
rep.setNodeReRegistrationTimeout(clientModel.getNodeReRegistrationTimeout());
|
rep.setNodeReRegistrationTimeout(clientModel.getNodeReRegistrationTimeout());
|
||||||
|
|
|
@ -692,6 +692,7 @@ public class RepresentationToModel {
|
||||||
client.setManagementUrl(resourceRep.getAdminUrl());
|
client.setManagementUrl(resourceRep.getAdminUrl());
|
||||||
if (resourceRep.isSurrogateAuthRequired() != null)
|
if (resourceRep.isSurrogateAuthRequired() != null)
|
||||||
client.setSurrogateAuthRequired(resourceRep.isSurrogateAuthRequired());
|
client.setSurrogateAuthRequired(resourceRep.isSurrogateAuthRequired());
|
||||||
|
if (resourceRep.getRootUrl() != null) client.setRootUrl(resourceRep.getRootUrl());
|
||||||
if (resourceRep.getBaseUrl() != null) client.setBaseUrl(resourceRep.getBaseUrl());
|
if (resourceRep.getBaseUrl() != null) client.setBaseUrl(resourceRep.getBaseUrl());
|
||||||
if (resourceRep.isBearerOnly() != null) client.setBearerOnly(resourceRep.isBearerOnly());
|
if (resourceRep.isBearerOnly() != null) client.setBearerOnly(resourceRep.isBearerOnly());
|
||||||
if (resourceRep.isConsentRequired() != null) client.setConsentRequired(resourceRep.isConsentRequired());
|
if (resourceRep.isConsentRequired() != null) client.setConsentRequired(resourceRep.isConsentRequired());
|
||||||
|
@ -796,6 +797,7 @@ public class RepresentationToModel {
|
||||||
if (rep.isPublicClient() != null) resource.setPublicClient(rep.isPublicClient());
|
if (rep.isPublicClient() != null) resource.setPublicClient(rep.isPublicClient());
|
||||||
if (rep.isFullScopeAllowed() != null) resource.setFullScopeAllowed(rep.isFullScopeAllowed());
|
if (rep.isFullScopeAllowed() != null) resource.setFullScopeAllowed(rep.isFullScopeAllowed());
|
||||||
if (rep.isFrontchannelLogout() != null) resource.setFrontchannelLogout(rep.isFrontchannelLogout());
|
if (rep.isFrontchannelLogout() != null) resource.setFrontchannelLogout(rep.isFrontchannelLogout());
|
||||||
|
if (rep.getRootUrl() != null) resource.setRootUrl(rep.getRootUrl());
|
||||||
if (rep.getAdminUrl() != null) resource.setManagementUrl(rep.getAdminUrl());
|
if (rep.getAdminUrl() != null) resource.setManagementUrl(rep.getAdminUrl());
|
||||||
if (rep.getBaseUrl() != null) resource.setBaseUrl(rep.getBaseUrl());
|
if (rep.getBaseUrl() != null) resource.setBaseUrl(rep.getBaseUrl());
|
||||||
if (rep.isSurrogateAuthRequired() != null) resource.setSurrogateAuthRequired(rep.isSurrogateAuthRequired());
|
if (rep.isSurrogateAuthRequired() != null) resource.setSurrogateAuthRequired(rep.isSurrogateAuthRequired());
|
||||||
|
|
|
@ -421,6 +421,16 @@ public class ClientAdapter implements ClientModel {
|
||||||
entity.setManagementUrl(url);
|
entity.setManagementUrl(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRootUrl(String url) {
|
||||||
|
entity.setRootUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRootUrl() {
|
||||||
|
return entity.getRootUrl();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setBaseUrl(String url) {
|
public void setBaseUrl(String url) {
|
||||||
entity.setBaseUrl(url);
|
entity.setBaseUrl(url);
|
||||||
|
|
|
@ -345,6 +345,18 @@ public class ClientAdapter implements ClientModel {
|
||||||
updated.setManagementUrl(url);
|
updated.setManagementUrl(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRootUrl() {
|
||||||
|
if (updated != null) return updated.getRootUrl();
|
||||||
|
return cached.getRootUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRootUrl(String url) {
|
||||||
|
getDelegateForUpdate();
|
||||||
|
updated.setRootUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getBaseUrl() {
|
public String getBaseUrl() {
|
||||||
if (updated != null) return updated.getBaseUrl();
|
if (updated != null) return updated.getBaseUrl();
|
||||||
|
|
|
@ -42,6 +42,7 @@ public class CachedClient implements Serializable {
|
||||||
private Set<ProtocolMapperModel> protocolMappers = new HashSet<ProtocolMapperModel>();
|
private Set<ProtocolMapperModel> protocolMappers = new HashSet<ProtocolMapperModel>();
|
||||||
private boolean surrogateAuthRequired;
|
private boolean surrogateAuthRequired;
|
||||||
private String managementUrl;
|
private String managementUrl;
|
||||||
|
private String rootUrl;
|
||||||
private String baseUrl;
|
private String baseUrl;
|
||||||
private List<String> defaultRoles = new LinkedList<String>();
|
private List<String> defaultRoles = new LinkedList<String>();
|
||||||
private boolean bearerOnly;
|
private boolean bearerOnly;
|
||||||
|
@ -76,6 +77,7 @@ public class CachedClient implements Serializable {
|
||||||
}
|
}
|
||||||
surrogateAuthRequired = model.isSurrogateAuthRequired();
|
surrogateAuthRequired = model.isSurrogateAuthRequired();
|
||||||
managementUrl = model.getManagementUrl();
|
managementUrl = model.getManagementUrl();
|
||||||
|
rootUrl = model.getRootUrl();
|
||||||
baseUrl = model.getBaseUrl();
|
baseUrl = model.getBaseUrl();
|
||||||
defaultRoles.addAll(model.getDefaultRoles());
|
defaultRoles.addAll(model.getDefaultRoles());
|
||||||
bearerOnly = model.isBearerOnly();
|
bearerOnly = model.isBearerOnly();
|
||||||
|
@ -169,6 +171,10 @@ public class CachedClient implements Serializable {
|
||||||
return managementUrl;
|
return managementUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRootUrl() {
|
||||||
|
return rootUrl;
|
||||||
|
}
|
||||||
|
|
||||||
public String getBaseUrl() {
|
public String getBaseUrl() {
|
||||||
return baseUrl;
|
return baseUrl;
|
||||||
}
|
}
|
||||||
|
|
|
@ -441,6 +441,16 @@ public class ClientAdapter implements ClientModel {
|
||||||
entity.setManagementUrl(url);
|
entity.setManagementUrl(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRootUrl() {
|
||||||
|
return entity.getRootUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRootUrl(String url) {
|
||||||
|
entity.setRootUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getBaseUrl() {
|
public String getBaseUrl() {
|
||||||
return entity.getBaseUrl();
|
return entity.getBaseUrl();
|
||||||
|
|
|
@ -82,6 +82,9 @@ public class ClientEntity {
|
||||||
@Column(name="SURROGATE_AUTH_REQUIRED")
|
@Column(name="SURROGATE_AUTH_REQUIRED")
|
||||||
private boolean surrogateAuthRequired;
|
private boolean surrogateAuthRequired;
|
||||||
|
|
||||||
|
@Column(name="ROOT_URL")
|
||||||
|
private String rootUrl;
|
||||||
|
|
||||||
@Column(name="BASE_URL")
|
@Column(name="BASE_URL")
|
||||||
private String baseUrl;
|
private String baseUrl;
|
||||||
|
|
||||||
|
@ -260,6 +263,14 @@ public class ClientEntity {
|
||||||
this.surrogateAuthRequired = surrogateAuthRequired;
|
this.surrogateAuthRequired = surrogateAuthRequired;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRootUrl() {
|
||||||
|
return rootUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRootUrl(String rootUrl) {
|
||||||
|
this.rootUrl = rootUrl;
|
||||||
|
}
|
||||||
|
|
||||||
public String getBaseUrl() {
|
public String getBaseUrl() {
|
||||||
return baseUrl;
|
return baseUrl;
|
||||||
}
|
}
|
||||||
|
|
|
@ -439,6 +439,17 @@ public class ClientAdapter extends AbstractMongoAdapter<MongoClientEntity> imple
|
||||||
updateMongoEntity();
|
updateMongoEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRootUrl(String url) {
|
||||||
|
getMongoEntity().setRootUrl(url);
|
||||||
|
updateMongoEntity();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRootUrl() {
|
||||||
|
return getMongoEntity().getRootUrl();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setBaseUrl(String url) {
|
public void setBaseUrl(String url) {
|
||||||
getMongoEntity().setBaseUrl(url);
|
getMongoEntity().setBaseUrl(url);
|
||||||
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
package org.keycloak.protocol.saml;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.dom.saml.v2.metadata.*;
|
||||||
|
import org.keycloak.exportimport.ClientDescriptionConverter;
|
||||||
|
import org.keycloak.exportimport.ClientDescriptionConverterFactory;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
import org.keycloak.saml.SignatureAlgorithm;
|
||||||
|
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||||
|
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
||||||
|
import org.keycloak.saml.common.exceptions.ParsingException;
|
||||||
|
import org.keycloak.saml.common.exceptions.ProcessingException;
|
||||||
|
import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
|
||||||
|
import org.keycloak.saml.processing.core.saml.v2.util.SAMLMetadataUtil;
|
||||||
|
import org.keycloak.saml.processing.core.util.CoreConfigUtil;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class EntityDescriptorDescriptionConverter implements ClientDescriptionConverter, ClientDescriptionConverterFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSupported(String description) {
|
||||||
|
description = description.trim();
|
||||||
|
return (description.startsWith("<") && description.endsWith(">") && description.contains("EntityDescriptor"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientRepresentation convertToInternal(String description) {
|
||||||
|
return loadEntityDescriptors(new ByteArrayInputStream(description.getBytes()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClientRepresentation loadEntityDescriptors(InputStream is) {
|
||||||
|
Object metadata;
|
||||||
|
try {
|
||||||
|
metadata = new SAMLParser().parse(is);
|
||||||
|
} catch (ParsingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
EntitiesDescriptorType entities;
|
||||||
|
|
||||||
|
if (EntitiesDescriptorType.class.isInstance(metadata)) {
|
||||||
|
entities = (EntitiesDescriptorType) metadata;
|
||||||
|
} else {
|
||||||
|
entities = new EntitiesDescriptorType();
|
||||||
|
entities.addEntityDescriptor(metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entities.getEntityDescriptor().size() != 1) {
|
||||||
|
throw new RuntimeException("Expected one entity descriptor");
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityDescriptorType entity = (EntityDescriptorType) entities.getEntityDescriptor().get(0);
|
||||||
|
String entityId = entity.getEntityID();
|
||||||
|
|
||||||
|
ClientRepresentation app = new ClientRepresentation();
|
||||||
|
app.setClientId(entityId);
|
||||||
|
|
||||||
|
Map<String, String> attributes = new HashMap<>();
|
||||||
|
app.setAttributes(attributes);
|
||||||
|
|
||||||
|
List<String> redirectUris = new LinkedList<>();
|
||||||
|
app.setRedirectUris(redirectUris);
|
||||||
|
|
||||||
|
app.setFullScopeAllowed(true);
|
||||||
|
app.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
|
||||||
|
attributes.put(SamlProtocol.SAML_SERVER_SIGNATURE, SamlProtocol.ATTRIBUTE_TRUE_VALUE); // default to true
|
||||||
|
attributes.put(SamlProtocol.SAML_SIGNATURE_ALGORITHM, SignatureAlgorithm.RSA_SHA256.toString());
|
||||||
|
attributes.put(SamlProtocol.SAML_AUTHNSTATEMENT, SamlProtocol.ATTRIBUTE_TRUE_VALUE);
|
||||||
|
SPSSODescriptorType spDescriptorType = CoreConfigUtil.getSPDescriptor(entity);
|
||||||
|
if (spDescriptorType.isWantAssertionsSigned()) {
|
||||||
|
attributes.put(SamlProtocol.SAML_ASSERTION_SIGNATURE, SamlProtocol.ATTRIBUTE_TRUE_VALUE);
|
||||||
|
}
|
||||||
|
String logoutPost = getLogoutLocation(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get());
|
||||||
|
if (logoutPost != null) attributes.put(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE, logoutPost);
|
||||||
|
String logoutRedirect = getLogoutLocation(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get());
|
||||||
|
if (logoutPost != null) attributes.put(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_REDIRECT_ATTRIBUTE, logoutRedirect);
|
||||||
|
|
||||||
|
String assertionConsumerServicePostBinding = CoreConfigUtil.getServiceURL(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get());
|
||||||
|
if (assertionConsumerServicePostBinding != null) {
|
||||||
|
attributes.put(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE, assertionConsumerServicePostBinding);
|
||||||
|
redirectUris.add(assertionConsumerServicePostBinding);
|
||||||
|
}
|
||||||
|
String assertionConsumerServiceRedirectBinding = CoreConfigUtil.getServiceURL(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get());
|
||||||
|
if (assertionConsumerServiceRedirectBinding != null) {
|
||||||
|
attributes.put(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE, assertionConsumerServiceRedirectBinding);
|
||||||
|
redirectUris.add(assertionConsumerServiceRedirectBinding);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (KeyDescriptorType keyDescriptor : spDescriptorType.getKeyDescriptor()) {
|
||||||
|
X509Certificate cert = null;
|
||||||
|
try {
|
||||||
|
cert = SAMLMetadataUtil.getCertificate(keyDescriptor);
|
||||||
|
} catch (ConfigurationException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} catch (ProcessingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
String certPem = KeycloakModelUtils.getPemFromCertificate(cert);
|
||||||
|
if (keyDescriptor.getUse() == KeyTypes.SIGNING) {
|
||||||
|
attributes.put(SamlProtocol.SAML_CLIENT_SIGNATURE_ATTRIBUTE, SamlProtocol.ATTRIBUTE_TRUE_VALUE);
|
||||||
|
attributes.put(SamlProtocol.SAML_SIGNING_CERTIFICATE_ATTRIBUTE, certPem);
|
||||||
|
} else if (keyDescriptor.getUse() == KeyTypes.ENCRYPTION) {
|
||||||
|
attributes.put(SamlProtocol.SAML_ENCRYPT, SamlProtocol.ATTRIBUTE_TRUE_VALUE);
|
||||||
|
attributes.put(SamlProtocol.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE, certPem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getLogoutLocation(SPSSODescriptorType idp, String bindingURI) {
|
||||||
|
String logoutResponseLocation = null;
|
||||||
|
|
||||||
|
List<EndpointType> endpoints = idp.getSingleLogoutService();
|
||||||
|
for (EndpointType endpoint : endpoints) {
|
||||||
|
if (endpoint.getBinding().toString().equals(bindingURI)) {
|
||||||
|
if (endpoint.getLocation() != null) {
|
||||||
|
logoutResponseLocation = endpoint.getLocation().toString();
|
||||||
|
} else {
|
||||||
|
logoutResponseLocation = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return logoutResponseLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientDescriptionConverter create(KeycloakSession session) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "saml2-entity-descriptor";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,21 +0,0 @@
|
||||||
package org.keycloak.protocol.saml;
|
|
||||||
|
|
||||||
import org.keycloak.exportimport.ClientImporter;
|
|
||||||
import org.keycloak.models.RealmModel;
|
|
||||||
import org.keycloak.services.resources.admin.RealmAuth;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class EntityDescriptorImporter implements ClientImporter {
|
|
||||||
@Override
|
|
||||||
public Object createJaxrsService(RealmModel realm, RealmAuth auth) {
|
|
||||||
return new EntityDescriptorImporterService(realm, auth);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
package org.keycloak.protocol.saml;
|
|
||||||
|
|
||||||
import org.keycloak.Config;
|
|
||||||
import org.keycloak.exportimport.ClientImporter;
|
|
||||||
import org.keycloak.exportimport.ClientImporterFactory;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class EntityDescriptorImporterFactory implements ClientImporterFactory {
|
|
||||||
@Override
|
|
||||||
public String getDisplayName() {
|
|
||||||
return "SAML 2.0 Entity Descriptor";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ClientImporter create(KeycloakSession session) {
|
|
||||||
return new EntityDescriptorImporter();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(Config.Scope config) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void postInit(KeycloakSessionFactory factory) {
|
|
||||||
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getId() {
|
|
||||||
return "saml2-entity-descriptor";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,150 +0,0 @@
|
||||||
package org.keycloak.protocol.saml;
|
|
||||||
|
|
||||||
import org.jboss.resteasy.plugins.providers.multipart.InputPart;
|
|
||||||
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
|
|
||||||
import org.keycloak.models.ClientModel;
|
|
||||||
import org.keycloak.models.RealmModel;
|
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
|
||||||
import org.keycloak.saml.SignatureAlgorithm;
|
|
||||||
import org.keycloak.services.resources.admin.RealmAuth;
|
|
||||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
|
||||||
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
|
||||||
import org.keycloak.saml.common.exceptions.ParsingException;
|
|
||||||
import org.keycloak.saml.common.exceptions.ProcessingException;
|
|
||||||
import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
|
|
||||||
import org.keycloak.saml.processing.core.saml.v2.util.SAMLMetadataUtil;
|
|
||||||
import org.keycloak.saml.processing.core.util.CoreConfigUtil;
|
|
||||||
import org.keycloak.dom.saml.v2.metadata.EndpointType;
|
|
||||||
import org.keycloak.dom.saml.v2.metadata.EntitiesDescriptorType;
|
|
||||||
import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType;
|
|
||||||
import org.keycloak.dom.saml.v2.metadata.KeyDescriptorType;
|
|
||||||
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
|
|
||||||
import org.keycloak.dom.saml.v2.metadata.SPSSODescriptorType;
|
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
|
||||||
import javax.ws.rs.POST;
|
|
||||||
import javax.ws.rs.Path;
|
|
||||||
import javax.ws.rs.core.Context;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import javax.ws.rs.core.UriInfo;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class EntityDescriptorImporterService {
|
|
||||||
protected RealmModel realm;
|
|
||||||
protected RealmAuth auth;
|
|
||||||
|
|
||||||
public EntityDescriptorImporterService(RealmModel realm, RealmAuth auth) {
|
|
||||||
this.realm = realm;
|
|
||||||
this.auth = auth;
|
|
||||||
}
|
|
||||||
|
|
||||||
@POST
|
|
||||||
@Path("upload")
|
|
||||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
|
||||||
public void updateEntityDescriptor(@Context final UriInfo uriInfo, MultipartFormDataInput input) throws IOException {
|
|
||||||
auth.requireManage();
|
|
||||||
|
|
||||||
Map<String, List<InputPart>> uploadForm = input.getFormDataMap();
|
|
||||||
List<InputPart> inputParts = uploadForm.get("file");
|
|
||||||
|
|
||||||
InputStream is = inputParts.get(0).getBody(InputStream.class, null);
|
|
||||||
|
|
||||||
loadEntityDescriptors(is, realm);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void loadEntityDescriptors(InputStream is, RealmModel realm) {
|
|
||||||
Object metadata = null;
|
|
||||||
try {
|
|
||||||
metadata = new SAMLParser().parse(is);
|
|
||||||
} catch (ParsingException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
EntitiesDescriptorType entities;
|
|
||||||
|
|
||||||
if (EntitiesDescriptorType.class.isInstance(metadata)) {
|
|
||||||
entities = (EntitiesDescriptorType) metadata;
|
|
||||||
} else {
|
|
||||||
entities = new EntitiesDescriptorType();
|
|
||||||
entities.addEntityDescriptor(metadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Object o : entities.getEntityDescriptor()) {
|
|
||||||
EntityDescriptorType entity = (EntityDescriptorType)o;
|
|
||||||
String entityId = entity.getEntityID();
|
|
||||||
ClientModel app = realm.addClient(entityId);
|
|
||||||
app.setFullScopeAllowed(true);
|
|
||||||
app.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
|
|
||||||
app.setAttribute(SamlProtocol.SAML_SERVER_SIGNATURE, SamlProtocol.ATTRIBUTE_TRUE_VALUE); // default to true
|
|
||||||
app.setAttribute(SamlProtocol.SAML_SIGNATURE_ALGORITHM, SignatureAlgorithm.RSA_SHA256.toString());
|
|
||||||
app.setAttribute(SamlProtocol.SAML_AUTHNSTATEMENT, SamlProtocol.ATTRIBUTE_TRUE_VALUE);
|
|
||||||
SPSSODescriptorType spDescriptorType = CoreConfigUtil.getSPDescriptor(entity);
|
|
||||||
if (spDescriptorType.isWantAssertionsSigned()) {
|
|
||||||
app.setAttribute(SamlProtocol.SAML_ASSERTION_SIGNATURE, SamlProtocol.ATTRIBUTE_TRUE_VALUE);
|
|
||||||
}
|
|
||||||
String logoutPost = getLogoutLocation(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get());
|
|
||||||
if (logoutPost != null) app.setAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE, logoutPost);
|
|
||||||
String logoutRedirect = getLogoutLocation(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get());
|
|
||||||
if (logoutPost != null) app.setAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_REDIRECT_ATTRIBUTE, logoutRedirect);
|
|
||||||
|
|
||||||
String assertionConsumerServicePostBinding = CoreConfigUtil.getServiceURL(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get());
|
|
||||||
if (assertionConsumerServicePostBinding != null) {
|
|
||||||
app.setAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE, assertionConsumerServicePostBinding);
|
|
||||||
app.addRedirectUri(assertionConsumerServicePostBinding);
|
|
||||||
}
|
|
||||||
String assertionConsumerServiceRedirectBinding = CoreConfigUtil.getServiceURL(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get());
|
|
||||||
if (assertionConsumerServiceRedirectBinding != null) {
|
|
||||||
app.setAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE, assertionConsumerServiceRedirectBinding);
|
|
||||||
app.addRedirectUri(assertionConsumerServiceRedirectBinding);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (KeyDescriptorType keyDescriptor : spDescriptorType.getKeyDescriptor()) {
|
|
||||||
X509Certificate cert = null;
|
|
||||||
try {
|
|
||||||
cert = SAMLMetadataUtil.getCertificate(keyDescriptor);
|
|
||||||
} catch (ConfigurationException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (ProcessingException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
String certPem = KeycloakModelUtils.getPemFromCertificate(cert);
|
|
||||||
if (keyDescriptor.getUse() == KeyTypes.SIGNING) {
|
|
||||||
app.setAttribute(SamlProtocol.SAML_CLIENT_SIGNATURE_ATTRIBUTE, SamlProtocol.ATTRIBUTE_TRUE_VALUE);
|
|
||||||
app.setAttribute(SamlProtocol.SAML_SIGNING_CERTIFICATE_ATTRIBUTE, certPem);
|
|
||||||
} else if (keyDescriptor.getUse() == KeyTypes.ENCRYPTION) {
|
|
||||||
app.setAttribute(SamlProtocol.SAML_ENCRYPT, SamlProtocol.ATTRIBUTE_TRUE_VALUE);
|
|
||||||
app.setAttribute(SamlProtocol.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE, certPem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getLogoutLocation(SPSSODescriptorType idp, String bindingURI) {
|
|
||||||
String logoutResponseLocation = null;
|
|
||||||
|
|
||||||
List<EndpointType> endpoints = idp.getSingleLogoutService();
|
|
||||||
for (EndpointType endpoint : endpoints) {
|
|
||||||
if (endpoint.getBinding().toString().equals(bindingURI)) {
|
|
||||||
if (endpoint.getLocation() != null) {
|
|
||||||
logoutResponseLocation = endpoint.getLocation().toString();
|
|
||||||
} else {
|
|
||||||
logoutResponseLocation = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return logoutResponseLocation;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -485,7 +485,7 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
}
|
}
|
||||||
if (logoutServiceUrl == null && client instanceof ClientModel) logoutServiceUrl = ((ClientModel)client).getManagementUrl();
|
if (logoutServiceUrl == null && client instanceof ClientModel) logoutServiceUrl = ((ClientModel)client).getManagementUrl();
|
||||||
if (logoutServiceUrl == null || logoutServiceUrl.trim().equals("")) return null;
|
if (logoutServiceUrl == null || logoutServiceUrl.trim().equals("")) return null;
|
||||||
return ResourceAdminManager.resolveUri(uriInfo.getRequestUri(), logoutServiceUrl);
|
return ResourceAdminManager.resolveUri(uriInfo.getRequestUri(), client.getRootUrl(), logoutServiceUrl);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
org.keycloak.protocol.saml.EntityDescriptorDescriptionConverter
|
|
@ -1 +0,0 @@
|
||||||
org.keycloak.protocol.saml.EntityDescriptorImporterFactory
|
|
|
@ -1,8 +1,7 @@
|
||||||
package org.keycloak.exportimport;
|
package org.keycloak.exportimport;
|
||||||
|
|
||||||
import org.keycloak.models.RealmModel;
|
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
import org.keycloak.services.resources.admin.RealmAuth;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provider plugin interface for importing clients from an arbitrary configuration format
|
* Provider plugin interface for importing clients from an arbitrary configuration format
|
||||||
|
@ -10,6 +9,8 @@ import org.keycloak.services.resources.admin.RealmAuth;
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public interface ClientImporter extends Provider {
|
public interface ClientDescriptionConverter extends Provider {
|
||||||
public Object createJaxrsService(RealmModel realm, RealmAuth auth);
|
|
||||||
|
ClientRepresentation convertToInternal(String description);
|
||||||
|
|
||||||
}
|
}
|
|
@ -8,6 +8,8 @@ import org.keycloak.provider.ProviderFactory;
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public interface ClientImporterFactory extends ProviderFactory<ClientImporter> {
|
public interface ClientDescriptionConverterFactory extends ProviderFactory<ClientDescriptionConverter> {
|
||||||
public String getDisplayName();
|
|
||||||
|
boolean isSupported(String description);
|
||||||
|
|
||||||
}
|
}
|
|
@ -7,7 +7,7 @@ import org.keycloak.provider.Spi;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public class ClientImportSpi implements Spi {
|
public class ClientDescriptionConverterSpi implements Spi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isInternal() {
|
public boolean isInternal() {
|
||||||
|
@ -16,16 +16,17 @@ public class ClientImportSpi implements Spi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "client-import";
|
return "client-description-converter";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Class<? extends Provider> getProviderClass() {
|
public Class<? extends Provider> getProviderClass() {
|
||||||
return ClientImporter.class;
|
return ClientDescriptionConverter.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||||
return ClientImporterFactory.class;
|
return ClientDescriptionConverterFactory.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package org.keycloak.exportimport;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class KeycloakClientDescriptionConverter implements ClientDescriptionConverterFactory, ClientDescriptionConverter {
|
||||||
|
|
||||||
|
public static final String ID = "keycloak";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSupported(String description) {
|
||||||
|
description = description.trim();
|
||||||
|
return (description.startsWith("{") && description.endsWith("}") && description.contains("\"clientId\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientRepresentation convertToInternal(String description) {
|
||||||
|
try {
|
||||||
|
return JsonSerialization.readValue(description, ClientRepresentation.class);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientDescriptionConverter create(KeycloakSession session) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package org.keycloak.protocol.oidc;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.exportimport.ClientDescriptionConverter;
|
||||||
|
import org.keycloak.exportimport.ClientDescriptionConverterFactory;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
|
||||||
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class OIDCClientDescriptionConverter implements ClientDescriptionConverter, ClientDescriptionConverterFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSupported(String description) {
|
||||||
|
description = description.trim();
|
||||||
|
return (description.startsWith("{") && description.endsWith("}") && description.contains("\"redirect_uris\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientRepresentation convertToInternal(String description) {
|
||||||
|
try {
|
||||||
|
OIDCClientRepresentation oidcRep = JsonSerialization.readValue(description, OIDCClientRepresentation.class);
|
||||||
|
|
||||||
|
ClientRepresentation client = new ClientRepresentation();
|
||||||
|
client.setClientId(KeycloakModelUtils.generateId());
|
||||||
|
client.setName(oidcRep.getClientName());
|
||||||
|
client.setRedirectUris(oidcRep.getRedirectUris());
|
||||||
|
client.setBaseUrl(oidcRep.getClientUri());
|
||||||
|
|
||||||
|
return client;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientDescriptionConverter create(KeycloakSession session) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "openid-connect";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
package org.keycloak.protocol.oidc;
|
package org.keycloak.protocol.oidc;
|
||||||
|
|
||||||
import org.keycloak.constants.KerberosConstants;
|
import org.keycloak.constants.KerberosConstants;
|
||||||
|
@ -19,6 +35,7 @@ import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import org.keycloak.protocol.oidc.mappers.UserAttributeMapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -32,12 +49,14 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
|
||||||
public static final String GIVEN_NAME = "given name";
|
public static final String GIVEN_NAME = "given name";
|
||||||
public static final String FAMILY_NAME = "family name";
|
public static final String FAMILY_NAME = "family name";
|
||||||
public static final String FULL_NAME = "full name";
|
public static final String FULL_NAME = "full name";
|
||||||
|
public static final String LOCALE = "locale";
|
||||||
public static final String USERNAME_CONSENT_TEXT = "${username}";
|
public static final String USERNAME_CONSENT_TEXT = "${username}";
|
||||||
public static final String EMAIL_CONSENT_TEXT = "${email}";
|
public static final String EMAIL_CONSENT_TEXT = "${email}";
|
||||||
public static final String EMAIL_VERIFIED_CONSENT_TEXT = "${emailVerified}";
|
public static final String EMAIL_VERIFIED_CONSENT_TEXT = "${emailVerified}";
|
||||||
public static final String GIVEN_NAME_CONSENT_TEXT = "${givenName}";
|
public static final String GIVEN_NAME_CONSENT_TEXT = "${givenName}";
|
||||||
public static final String FAMILY_NAME_CONSENT_TEXT = "${familyName}";
|
public static final String FAMILY_NAME_CONSENT_TEXT = "${familyName}";
|
||||||
public static final String FULL_NAME_CONSENT_TEXT = "${fullName}";
|
public static final String FULL_NAME_CONSENT_TEXT = "${fullName}";
|
||||||
|
public static final String LOCALE_CONSENT_TEXT = "${locale}";
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -95,6 +114,12 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
|
||||||
false, EMAIL_VERIFIED_CONSENT_TEXT,
|
false, EMAIL_VERIFIED_CONSENT_TEXT,
|
||||||
true, true);
|
true, true);
|
||||||
builtins.add(model);
|
builtins.add(model);
|
||||||
|
model = UserAttributeMapper.createClaimMapper(LOCALE,
|
||||||
|
"locale",
|
||||||
|
"locale", "String",
|
||||||
|
false, LOCALE_CONSENT_TEXT,
|
||||||
|
true, true, false);
|
||||||
|
builtins.add(model);
|
||||||
|
|
||||||
ProtocolMapperModel fullName = new ProtocolMapperModel();
|
ProtocolMapperModel fullName = new ProtocolMapperModel();
|
||||||
fullName.setName(FULL_NAME);
|
fullName.setName(FULL_NAME);
|
||||||
|
|
|
@ -24,6 +24,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.protocol.ProtocolMapper;
|
import org.keycloak.protocol.ProtocolMapper;
|
||||||
import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper;
|
import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper;
|
||||||
import org.keycloak.protocol.oidc.mappers.OIDCIDTokenMapper;
|
import org.keycloak.protocol.oidc.mappers.OIDCIDTokenMapper;
|
||||||
|
import org.keycloak.protocol.oidc.utils.WebOriginsUtils;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
import org.keycloak.representations.AccessTokenResponse;
|
||||||
import org.keycloak.representations.IDToken;
|
import org.keycloak.representations.IDToken;
|
||||||
|
@ -217,7 +218,7 @@ public class TokenManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccessToken createClientAccessToken(KeycloakSession session, Set<RoleModel> requestedRoles, RealmModel realm, ClientModel client, UserModel user, UserSessionModel userSession, ClientSessionModel clientSession) {
|
public AccessToken createClientAccessToken(KeycloakSession session, Set<RoleModel> requestedRoles, RealmModel realm, ClientModel client, UserModel user, UserSessionModel userSession, ClientSessionModel clientSession) {
|
||||||
AccessToken token = initToken(realm, client, user, userSession, clientSession);
|
AccessToken token = initToken(realm, client, user, userSession, clientSession, session.getContext().getUri());
|
||||||
for (RoleModel role : requestedRoles) {
|
for (RoleModel role : requestedRoles) {
|
||||||
addComposites(token, role);
|
addComposites(token, role);
|
||||||
}
|
}
|
||||||
|
@ -380,7 +381,7 @@ public class TokenManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected AccessToken initToken(RealmModel realm, ClientModel client, UserModel user, UserSessionModel session, ClientSessionModel clientSession) {
|
protected AccessToken initToken(RealmModel realm, ClientModel client, UserModel user, UserSessionModel session, ClientSessionModel clientSession, UriInfo uriInfo) {
|
||||||
AccessToken token = new AccessToken();
|
AccessToken token = new AccessToken();
|
||||||
if (clientSession != null) token.clientSession(clientSession.getId());
|
if (clientSession != null) token.clientSession(clientSession.getId());
|
||||||
token.id(KeycloakModelUtils.generateId());
|
token.id(KeycloakModelUtils.generateId());
|
||||||
|
@ -398,7 +399,7 @@ public class TokenManager {
|
||||||
}
|
}
|
||||||
Set<String> allowedOrigins = client.getWebOrigins();
|
Set<String> allowedOrigins = client.getWebOrigins();
|
||||||
if (allowedOrigins != null) {
|
if (allowedOrigins != null) {
|
||||||
token.setAllowedOrigins(allowedOrigins);
|
token.setAllowedOrigins(WebOriginsUtils.resolveValidWebOrigins(uriInfo, client));
|
||||||
}
|
}
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ public class LoginStatusIframeEndpoint {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (String r : RedirectUtils.resolveValidRedirects(uriInfo, client.getRedirectUris())) {
|
for (String r : RedirectUtils.resolveValidRedirects(uriInfo, client.getRootUrl(), client.getRedirectUris())) {
|
||||||
int i = r.indexOf('/', 8);
|
int i = r.indexOf('/', 8);
|
||||||
if (i != -1) {
|
if (i != -1) {
|
||||||
r = r.substring(0, i);
|
r = r.substring(0, i);
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
package org.keycloak.protocol.oidc.representations;
|
||||||
|
|
||||||
|
import org.codehaus.jackson.annotate.JsonProperty;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class OIDCClientRepresentation {
|
||||||
|
|
||||||
|
@JsonProperty("redirect_uris")
|
||||||
|
private List<String> redirectUris;
|
||||||
|
|
||||||
|
@JsonProperty("client_name")
|
||||||
|
private String clientName;
|
||||||
|
|
||||||
|
@JsonProperty("client_uri")
|
||||||
|
private String clientUri;
|
||||||
|
|
||||||
|
public List<String> getRedirectUris() {
|
||||||
|
return redirectUris;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRedirectUris(List<String> redirectUris) {
|
||||||
|
this.redirectUris = redirectUris;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientName() {
|
||||||
|
return clientName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientName(String clientName) {
|
||||||
|
this.clientName = clientName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientUri() {
|
||||||
|
return clientUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientUri(String clientUri) {
|
||||||
|
this.clientUri = clientUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -19,22 +19,22 @@ public class RedirectUtils {
|
||||||
private static final Logger logger = Logger.getLogger(RedirectUtils.class);
|
private static final Logger logger = Logger.getLogger(RedirectUtils.class);
|
||||||
|
|
||||||
public static String verifyRealmRedirectUri(UriInfo uriInfo, String redirectUri, RealmModel realm) {
|
public static String verifyRealmRedirectUri(UriInfo uriInfo, String redirectUri, RealmModel realm) {
|
||||||
Set<String> validRedirects = getValidateRedirectUris(realm);
|
Set<String> validRedirects = getValidateRedirectUris(uriInfo, realm);
|
||||||
return verifyRedirectUri(uriInfo, redirectUri, realm, validRedirects);
|
return verifyRedirectUri(uriInfo, null, redirectUri, realm, validRedirects);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String verifyRedirectUri(UriInfo uriInfo, String redirectUri, RealmModel realm, ClientModel client) {
|
public static String verifyRedirectUri(UriInfo uriInfo, String redirectUri, RealmModel realm, ClientModel client) {
|
||||||
Set<String> validRedirects = client.getRedirectUris();
|
Set<String> validRedirects = client.getRedirectUris();
|
||||||
return verifyRedirectUri(uriInfo, redirectUri, realm, validRedirects);
|
return verifyRedirectUri(uriInfo, client.getRootUrl(), redirectUri, realm, validRedirects);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Set<String> resolveValidRedirects(UriInfo uriInfo, Set<String> validRedirects) {
|
public static Set<String> resolveValidRedirects(UriInfo uriInfo, String rootUrl, Set<String> validRedirects) {
|
||||||
// If the valid redirect URI is relative (no scheme, host, port) then use the request's scheme, host, and port
|
// If the valid redirect URI is relative (no scheme, host, port) then use the request's scheme, host, and port
|
||||||
Set<String> resolveValidRedirects = new HashSet<String>();
|
Set<String> resolveValidRedirects = new HashSet<String>();
|
||||||
for (String validRedirect : validRedirects) {
|
for (String validRedirect : validRedirects) {
|
||||||
resolveValidRedirects.add(validRedirect); // add even relative urls.
|
resolveValidRedirects.add(validRedirect); // add even relative urls.
|
||||||
if (validRedirect.startsWith("/")) {
|
if (validRedirect.startsWith("/")) {
|
||||||
validRedirect = relativeToAbsoluteURI(uriInfo, validRedirect);
|
validRedirect = relativeToAbsoluteURI(uriInfo, rootUrl, validRedirect);
|
||||||
logger.debugv("replacing relative valid redirect with: {0}", validRedirect);
|
logger.debugv("replacing relative valid redirect with: {0}", validRedirect);
|
||||||
resolveValidRedirects.add(validRedirect);
|
resolveValidRedirects.add(validRedirect);
|
||||||
}
|
}
|
||||||
|
@ -42,17 +42,15 @@ public class RedirectUtils {
|
||||||
return resolveValidRedirects;
|
return resolveValidRedirects;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Set<String> getValidateRedirectUris(RealmModel realm) {
|
private static Set<String> getValidateRedirectUris(UriInfo uriInfo, RealmModel realm) {
|
||||||
Set<String> redirects = new HashSet<String>();
|
Set<String> redirects = new HashSet<>();
|
||||||
for (ClientModel client : realm.getClients()) {
|
for (ClientModel client : realm.getClients()) {
|
||||||
for (String redirect : client.getRedirectUris()) {
|
redirects.addAll(resolveValidRedirects(uriInfo, client.getRootUrl(), client.getRedirectUris()));
|
||||||
redirects.add(redirect);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return redirects;
|
return redirects;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String verifyRedirectUri(UriInfo uriInfo, String redirectUri, RealmModel realm, Set<String> validRedirects) {
|
private static String verifyRedirectUri(UriInfo uriInfo, String rootUrl, String redirectUri, RealmModel realm, Set<String> validRedirects) {
|
||||||
if (redirectUri == null) {
|
if (redirectUri == null) {
|
||||||
if (validRedirects.size() != 1) return null;
|
if (validRedirects.size() != 1) return null;
|
||||||
String validRedirect = validRedirects.iterator().next();
|
String validRedirect = validRedirects.iterator().next();
|
||||||
|
@ -66,7 +64,7 @@ public class RedirectUtils {
|
||||||
redirectUri = null;
|
redirectUri = null;
|
||||||
} else {
|
} else {
|
||||||
String r = redirectUri.indexOf('?') != -1 ? redirectUri.substring(0, redirectUri.indexOf('?')) : redirectUri;
|
String r = redirectUri.indexOf('?') != -1 ? redirectUri.substring(0, redirectUri.indexOf('?')) : redirectUri;
|
||||||
Set<String> resolveValidRedirects = resolveValidRedirects(uriInfo, validRedirects);
|
Set<String> resolveValidRedirects = resolveValidRedirects(uriInfo, rootUrl, validRedirects);
|
||||||
|
|
||||||
boolean valid = matchesRedirects(resolveValidRedirects, r);
|
boolean valid = matchesRedirects(resolveValidRedirects, r);
|
||||||
|
|
||||||
|
@ -86,7 +84,7 @@ public class RedirectUtils {
|
||||||
valid = matchesRedirects(resolveValidRedirects, r);
|
valid = matchesRedirects(resolveValidRedirects, r);
|
||||||
}
|
}
|
||||||
if (valid && redirectUri.startsWith("/")) {
|
if (valid && redirectUri.startsWith("/")) {
|
||||||
redirectUri = relativeToAbsoluteURI(uriInfo, redirectUri);
|
redirectUri = relativeToAbsoluteURI(uriInfo, rootUrl, redirectUri);
|
||||||
}
|
}
|
||||||
redirectUri = valid ? redirectUri : null;
|
redirectUri = valid ? redirectUri : null;
|
||||||
}
|
}
|
||||||
|
@ -98,13 +96,16 @@ public class RedirectUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String relativeToAbsoluteURI(UriInfo uriInfo, String relative) {
|
private static String relativeToAbsoluteURI(UriInfo uriInfo, String rootUrl, String relative) {
|
||||||
URI baseUri = uriInfo.getBaseUri();
|
if (rootUrl == null) {
|
||||||
String uri = baseUri.getScheme() + "://" + baseUri.getHost();
|
URI baseUri = uriInfo.getBaseUri();
|
||||||
if (baseUri.getPort() != -1) {
|
String uri = baseUri.getScheme() + "://" + baseUri.getHost();
|
||||||
uri += ":" + baseUri.getPort();
|
if (baseUri.getPort() != -1) {
|
||||||
|
uri += ":" + baseUri.getPort();
|
||||||
|
}
|
||||||
|
rootUrl = uri;
|
||||||
}
|
}
|
||||||
relative = uri + relative;
|
relative = rootUrl + relative;
|
||||||
return relative;
|
return relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
package org.keycloak.protocol.oidc.utils;
|
||||||
|
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.util.UriUtils;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by st on 22.09.15.
|
||||||
|
*/
|
||||||
|
public class WebOriginsUtils {
|
||||||
|
|
||||||
|
public static final String INCLUDE_REDIRECTS = "+";
|
||||||
|
|
||||||
|
public static Set<String> resolveValidWebOrigins(UriInfo uriInfo, ClientModel client) {
|
||||||
|
Set<String> webOrigins = client.getWebOrigins();
|
||||||
|
if (webOrigins != null && webOrigins.contains("+")) {
|
||||||
|
webOrigins.remove(INCLUDE_REDIRECTS);
|
||||||
|
client.getRedirectUris();
|
||||||
|
for (String redirectUri : RedirectUtils.resolveValidRedirects(uriInfo, client.getRootUrl(), client.getRedirectUris())) {
|
||||||
|
if (redirectUri.startsWith("http://") || redirectUri.startsWith("https://")) {
|
||||||
|
webOrigins.add(UriUtils.getOrigin(redirectUri));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return webOrigins;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
package org.keycloak.services.managers;
|
package org.keycloak.services.managers;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
@ -49,7 +65,9 @@ import javax.ws.rs.core.UriInfo;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import org.keycloak.freemarker.LocaleHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stateless object that manages authentication
|
* Stateless object that manages authentication
|
||||||
|
@ -393,6 +411,9 @@ public class AuthenticationManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleLoginLocale(realm, userSession, request, uriInfo);
|
||||||
|
|
||||||
// refresh the cookies!
|
// refresh the cookies!
|
||||||
createLoginCookie(realm, userSession.getUser(), userSession, uriInfo, clientConnection);
|
createLoginCookie(realm, userSession.getUser(), userSession, uriInfo, clientConnection);
|
||||||
if (userSession.getState() != UserSessionModel.State.LOGGED_IN) userSession.setState(UserSessionModel.State.LOGGED_IN);
|
if (userSession.getState() != UserSessionModel.State.LOGGED_IN) userSession.setState(UserSessionModel.State.LOGGED_IN);
|
||||||
|
@ -406,6 +427,17 @@ public class AuthenticationManager {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If a locale has been set on the login screen, associate that locale with the user
|
||||||
|
private static void handleLoginLocale(RealmModel realm, UserSessionModel userSession,
|
||||||
|
HttpRequest request, UriInfo uriInfo) {
|
||||||
|
Cookie localeCookie = request.getHttpHeaders().getCookies().get(LocaleHelper.LOCALE_COOKIE);
|
||||||
|
if (localeCookie == null) return;
|
||||||
|
|
||||||
|
UserModel user = userSession.getUser();
|
||||||
|
Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, request.getHttpHeaders());
|
||||||
|
user.setSingleAttribute(UserModel.LOCALE, locale.toLanguageTag());
|
||||||
|
}
|
||||||
|
|
||||||
public static Response nextActionAfterAuthentication(KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession,
|
public static Response nextActionAfterAuthentication(KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession,
|
||||||
ClientConnection clientConnection,
|
ClientConnection clientConnection,
|
||||||
HttpRequest request, UriInfo uriInfo, EventBuilder event) {
|
HttpRequest request, UriInfo uriInfo, EventBuilder event) {
|
||||||
|
|
|
@ -46,8 +46,8 @@ public class ResourceAdminManager {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String resolveUri(URI requestUri, String uri) {
|
public static String resolveUri(URI requestUri, String rootUrl, String uri) {
|
||||||
String absoluteURI = ResolveRelative.resolveRelativeUri(requestUri, uri);
|
String absoluteURI = ResolveRelative.resolveRelativeUri(requestUri, rootUrl, uri);
|
||||||
return StringPropertyReplacer.replaceProperties(absoluteURI);
|
return StringPropertyReplacer.replaceProperties(absoluteURI);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -58,8 +58,7 @@ public class ResourceAdminManager {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is to support relative admin urls when keycloak and clients are deployed on the same machine
|
String absoluteURI = ResolveRelative.resolveRelativeUri(requestUri, client.getRootUrl(), mgmtUrl);
|
||||||
String absoluteURI = ResolveRelative.resolveRelativeUri(requestUri, mgmtUrl);
|
|
||||||
|
|
||||||
// this is for resolving URI like "http://${jboss.host.name}:8080/..." in order to send request to same machine and avoid request to LB in cluster environment
|
// this is for resolving URI like "http://${jboss.host.name}:8080/..." in order to send request to same machine and avoid request to LB in cluster environment
|
||||||
return StringPropertyReplacer.replaceProperties(absoluteURI);
|
return StringPropertyReplacer.replaceProperties(absoluteURI);
|
||||||
|
|
|
@ -779,7 +779,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
if (referrerUri != null) {
|
if (referrerUri != null) {
|
||||||
referrerUri = RedirectUtils.verifyRedirectUri(uriInfo, referrerUri, realm, referrerClient);
|
referrerUri = RedirectUtils.verifyRedirectUri(uriInfo, referrerUri, realm, referrerClient);
|
||||||
} else {
|
} else {
|
||||||
referrerUri = ResolveRelative.resolveRelativeUri(uriInfo.getRequestUri(), referrerClient.getBaseUrl());
|
referrerUri = ResolveRelative.resolveRelativeUri(uriInfo.getRequestUri(), client.getRootUrl(), referrerClient.getBaseUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (referrerUri != null) {
|
if (referrerUri != null) {
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
package org.keycloak.services.resources;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.jboss.resteasy.spi.BadRequestException;
|
||||||
|
import org.jboss.resteasy.spi.NotFoundException;
|
||||||
|
import org.keycloak.events.EventBuilder;
|
||||||
|
import org.keycloak.exportimport.ClientDescriptionConverter;
|
||||||
|
import org.keycloak.exportimport.KeycloakClientDescriptionConverter;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
|
import org.keycloak.models.utils.RepresentationToModel;
|
||||||
|
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
|
||||||
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
import org.keycloak.services.ErrorResponse;
|
||||||
|
|
||||||
|
import javax.ws.rs.*;
|
||||||
|
import javax.ws.rs.core.Context;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class ClientRegistrationService {
|
||||||
|
|
||||||
|
protected static final Logger logger = Logger.getLogger(ClientRegistrationService.class);
|
||||||
|
|
||||||
|
private RealmModel realm;
|
||||||
|
|
||||||
|
private EventBuilder event;
|
||||||
|
|
||||||
|
@Context
|
||||||
|
private KeycloakSession session;
|
||||||
|
|
||||||
|
public ClientRegistrationService(RealmModel realm, EventBuilder event) {
|
||||||
|
this.realm = realm;
|
||||||
|
this.event = event;
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN })
|
||||||
|
public Response create(String description, @QueryParam("format") String format) {
|
||||||
|
if (format == null) {
|
||||||
|
format = KeycloakClientDescriptionConverter.ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientDescriptionConverter converter = session.getProvider(ClientDescriptionConverter.class, format);
|
||||||
|
if (converter == null) {
|
||||||
|
throw new BadRequestException("Invalid format");
|
||||||
|
}
|
||||||
|
ClientRepresentation rep = converter.convertToInternal(description);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ClientModel clientModel = RepresentationToModel.createClient(session, realm, rep, true);
|
||||||
|
rep = ModelToRepresentation.toRepresentation(clientModel);
|
||||||
|
URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build();
|
||||||
|
return Response.created(uri).entity(rep).build();
|
||||||
|
} catch (ModelDuplicateException e) {
|
||||||
|
return ErrorResponse.exists("Client " + rep.getClientId() + " already exists");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("{clientId}")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
public ClientRepresentation get(@PathParam("clientId") String clientId) {
|
||||||
|
AuthorizeClientUtil.ClientAuthResult clientAuth = AuthorizeClientUtil.authorizeClient(session, event, realm);
|
||||||
|
ClientModel client = clientAuth.getClient();
|
||||||
|
if (client == null) {
|
||||||
|
throw new NotFoundException("Client not found");
|
||||||
|
}
|
||||||
|
return ModelToRepresentation.toRepresentation(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PUT
|
||||||
|
@Path("{clientId}")
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
public void update(@PathParam("clientId") String clientId, ClientRepresentation rep) {
|
||||||
|
ClientModel client = realm.getClientByClientId(clientId);
|
||||||
|
if (client == null) {
|
||||||
|
throw new NotFoundException("Client not found");
|
||||||
|
}
|
||||||
|
RepresentationToModel.updateClient(rep, client);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DELETE
|
||||||
|
@Path("{clientId}")
|
||||||
|
public void delete(@PathParam("clientId") String clientId) {
|
||||||
|
ClientModel client = realm.getClientByClientId(clientId);
|
||||||
|
if (client == null) {
|
||||||
|
throw new NotFoundException("Client not found");
|
||||||
|
}
|
||||||
|
realm.removeClient(client.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -112,6 +112,15 @@ public class RealmsResource {
|
||||||
return service;
|
return service;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Path("{realm}/client-registration")
|
||||||
|
// public ClientRegistrationService getClientsService(final @PathParam("realm") String name) {
|
||||||
|
// RealmModel realm = init(name);
|
||||||
|
// EventBuilder event = new EventBuilder(realm, session, clientConnection);
|
||||||
|
// ClientRegistrationService service = new ClientRegistrationService(realm, event);
|
||||||
|
// ResteasyProviderFactory.getInstance().injectProperties(service);
|
||||||
|
// return service;
|
||||||
|
// }
|
||||||
|
|
||||||
@Path("{realm}/clients-managements")
|
@Path("{realm}/clients-managements")
|
||||||
public ClientsManagementService getClientsManagementService(final @PathParam("realm") String name) {
|
public ClientsManagementService getClientsManagementService(final @PathParam("realm") String name) {
|
||||||
RealmModel realm = init(name);
|
RealmModel realm = init(name);
|
||||||
|
|
|
@ -44,7 +44,9 @@ import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import javax.ws.rs.QueryParam;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -283,8 +285,7 @@ public class AdminConsole {
|
||||||
map.put("resourceUrl", Urls.themeRoot(baseUri) + "/admin/" + adminTheme);
|
map.put("resourceUrl", Urls.themeRoot(baseUri) + "/admin/" + adminTheme);
|
||||||
map.put("resourceVersion", Version.RESOURCES_VERSION);
|
map.put("resourceVersion", Version.RESOURCES_VERSION);
|
||||||
|
|
||||||
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
|
Theme theme = getTheme();
|
||||||
Theme theme = themeProvider.getTheme(realm.getAdminTheme(), Theme.Type.ADMIN);
|
|
||||||
|
|
||||||
map.put("properties", theme.getProperties());
|
map.put("properties", theme.getProperties());
|
||||||
|
|
||||||
|
@ -296,10 +297,38 @@ public class AdminConsole {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Theme getTheme() throws IOException {
|
||||||
|
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
|
||||||
|
return themeProvider.getTheme(realm.getAdminTheme(), Theme.Type.ADMIN);
|
||||||
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("{indexhtml: index.html}") // this expression is a hack to get around jaxdoclet generation bug. Doesn't like index.html
|
@Path("{indexhtml: index.html}") // this expression is a hack to get around jaxdoclet generation bug. Doesn't like index.html
|
||||||
public Response getIndexHtmlRedirect() {
|
public Response getIndexHtmlRedirect() {
|
||||||
return Response.status(302).location(uriInfo.getRequestUriBuilder().path("../").build()).build();
|
return Response.status(302).location(uriInfo.getRequestUriBuilder().path("../").build()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("messages.json")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
public Properties getMessages(@QueryParam("lang") String lang) {
|
||||||
|
if (lang == null) {
|
||||||
|
logger.warn("Locale not specified for messages.json");
|
||||||
|
lang = "en";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Properties msgs = AdminMessagesLoader.getMessages(getTheme(), lang);
|
||||||
|
if (msgs.isEmpty()) {
|
||||||
|
logger.warn("Message bundle not found for language code '" + lang + "'");
|
||||||
|
msgs = AdminMessagesLoader.getMessages(getTheme(), "en"); // fall back to en
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msgs.isEmpty()) logger.fatal("Message bundle not found for language code 'en'");
|
||||||
|
|
||||||
|
return msgs;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.services.resources.admin;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
import org.keycloak.freemarker.Theme;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple loader and cache for message bundles consumed by angular-translate.
|
||||||
|
*
|
||||||
|
* Note that these bundles are converted to JSON before being shipped to the UI.
|
||||||
|
* Also, the content should be formatted such that it can be interpolated by
|
||||||
|
* angular-translate. This is somewhat different from an ordinary Java bundle.
|
||||||
|
*
|
||||||
|
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||||
|
*/
|
||||||
|
public class AdminMessagesLoader {
|
||||||
|
private static final Map<String, Properties> allMessages = new HashMap<String, Properties>();
|
||||||
|
|
||||||
|
static Properties getMessages(Theme theme, String strLocale) throws IOException {
|
||||||
|
String allMessagesKey = theme.getName() + "_" + strLocale;
|
||||||
|
Properties messages = allMessages.get(allMessagesKey);
|
||||||
|
if (messages != null) return messages;
|
||||||
|
|
||||||
|
Locale locale = Locale.forLanguageTag(strLocale);
|
||||||
|
messages = theme.getMessages("admin-messages", locale);
|
||||||
|
if (messages == null) return new Properties();
|
||||||
|
|
||||||
|
allMessages.put(allMessagesKey, messages);
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,8 @@ import org.keycloak.events.EventType;
|
||||||
import org.keycloak.events.admin.AdminEvent;
|
import org.keycloak.events.admin.AdminEvent;
|
||||||
import org.keycloak.events.admin.AdminEventQuery;
|
import org.keycloak.events.admin.AdminEventQuery;
|
||||||
import org.keycloak.events.admin.OperationType;
|
import org.keycloak.events.admin.OperationType;
|
||||||
import org.keycloak.exportimport.ClientImporter;
|
import org.keycloak.exportimport.ClientDescriptionConverter;
|
||||||
|
import org.keycloak.exportimport.ClientDescriptionConverterFactory;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
|
@ -25,7 +26,9 @@ import org.keycloak.models.cache.CacheUserProvider;
|
||||||
import org.keycloak.models.utils.ModelToRepresentation;
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
import org.keycloak.models.utils.RepresentationToModel;
|
import org.keycloak.models.utils.RepresentationToModel;
|
||||||
import org.keycloak.protocol.oidc.TokenManager;
|
import org.keycloak.protocol.oidc.TokenManager;
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
import org.keycloak.representations.adapters.action.GlobalRequestResult;
|
import org.keycloak.representations.adapters.action.GlobalRequestResult;
|
||||||
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
|
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
|
@ -99,10 +102,18 @@ public class RealmAdminResource {
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Path("client-importers/{formatId}")
|
@Path("client-description-converter")
|
||||||
public Object getClientImporter(@PathParam("formatId") String formatId) {
|
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN })
|
||||||
ClientImporter importer = session.getProvider(ClientImporter.class, formatId);
|
@POST
|
||||||
return importer.createJaxrsService(realm, auth);
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
public ClientRepresentation convertClientDescription(String description) {
|
||||||
|
for (ProviderFactory<ClientDescriptionConverter> factory : session.getKeycloakSessionFactory().getProviderFactories(ClientDescriptionConverter.class)) {
|
||||||
|
if (((ClientDescriptionConverterFactory) factory).isSupported(description)) {
|
||||||
|
return factory.create(session).convertToInternal(description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BadRequestException("Unsupported format");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -4,8 +4,6 @@ import org.keycloak.broker.provider.IdentityProvider;
|
||||||
import org.keycloak.broker.provider.IdentityProviderFactory;
|
import org.keycloak.broker.provider.IdentityProviderFactory;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.events.admin.OperationType;
|
import org.keycloak.events.admin.OperationType;
|
||||||
import org.keycloak.exportimport.ClientImporter;
|
|
||||||
import org.keycloak.exportimport.ClientImporterFactory;
|
|
||||||
import org.keycloak.freemarker.Theme;
|
import org.keycloak.freemarker.Theme;
|
||||||
import org.keycloak.freemarker.ThemeProvider;
|
import org.keycloak.freemarker.ThemeProvider;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
@ -51,7 +49,6 @@ public class ServerInfoAdminResource {
|
||||||
setSocialProviders(info);
|
setSocialProviders(info);
|
||||||
setIdentityProviders(info);
|
setIdentityProviders(info);
|
||||||
setThemes(info);
|
setThemes(info);
|
||||||
setClientImporters(info);
|
|
||||||
setProviders(info);
|
setProviders(info);
|
||||||
setProtocolMapperTypes(info);
|
setProtocolMapperTypes(info);
|
||||||
setBuiltinProtocolMappers(info);
|
setBuiltinProtocolMappers(info);
|
||||||
|
@ -144,7 +141,7 @@ public class ServerInfoAdminResource {
|
||||||
ProtocolMapper mapper = (ProtocolMapper)p;
|
ProtocolMapper mapper = (ProtocolMapper)p;
|
||||||
List<ProtocolMapperTypeRepresentation> types = info.getProtocolMapperTypes().get(mapper.getProtocol());
|
List<ProtocolMapperTypeRepresentation> types = info.getProtocolMapperTypes().get(mapper.getProtocol());
|
||||||
if (types == null) {
|
if (types == null) {
|
||||||
types = new LinkedList<ProtocolMapperTypeRepresentation>();
|
types = new LinkedList<>();
|
||||||
info.getProtocolMapperTypes().put(mapper.getProtocol(), types);
|
info.getProtocolMapperTypes().put(mapper.getProtocol(), types);
|
||||||
}
|
}
|
||||||
ProtocolMapperTypeRepresentation rep = new ProtocolMapperTypeRepresentation();
|
ProtocolMapperTypeRepresentation rep = new ProtocolMapperTypeRepresentation();
|
||||||
|
@ -179,17 +176,6 @@ public class ServerInfoAdminResource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setClientImporters(ServerInfoRepresentation info) {
|
|
||||||
info.setClientImporters(new LinkedList<Map<String, String>>());
|
|
||||||
for (ProviderFactory p : session.getKeycloakSessionFactory().getProviderFactories(ClientImporter.class)) {
|
|
||||||
ClientImporterFactory factory = (ClientImporterFactory)p;
|
|
||||||
Map<String, String> data = new HashMap<String, String>();
|
|
||||||
data.put("id", factory.getId());
|
|
||||||
data.put("name", factory.getDisplayName());
|
|
||||||
info.getClientImporters().add(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Map<String, List<String>> createEnumsMap(Class... enums) {
|
private static Map<String, List<String>> createEnumsMap(Class... enums) {
|
||||||
Map<String, List<String>> m = new HashMap<>();
|
Map<String, List<String>> m = new HashMap<>();
|
||||||
for (Class e : enums) {
|
for (Class e : enums) {
|
||||||
|
|
|
@ -8,13 +8,17 @@ import java.net.URI;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class ResolveRelative {
|
public class ResolveRelative {
|
||||||
public static String resolveRelativeUri(URI requestUri, String url) {
|
public static String resolveRelativeUri(URI requestUri, String rootUrl, String url) {
|
||||||
if (url == null || !url.startsWith("/")) return url;
|
if (url == null || !url.startsWith("/")) return url;
|
||||||
UriBuilder builder = UriBuilder.fromPath(url).host(requestUri.getHost());
|
if (rootUrl != null) {
|
||||||
builder.scheme(requestUri.getScheme());
|
return rootUrl + url;
|
||||||
if (requestUri.getPort() != -1) {
|
} else {
|
||||||
builder.port(requestUri.getPort());
|
UriBuilder builder = UriBuilder.fromPath(url).host(requestUri.getHost());
|
||||||
|
builder.scheme(requestUri.getScheme());
|
||||||
|
if (requestUri.getPort() != -1) {
|
||||||
|
builder.port(requestUri.getPort());
|
||||||
|
}
|
||||||
|
return builder.build().toString();
|
||||||
}
|
}
|
||||||
return builder.build().toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
org.keycloak.exportimport.KeycloakClientDescriptionConverter
|
||||||
|
org.keycloak.protocol.oidc.OIDCClientDescriptionConverter
|
|
@ -1,6 +1,6 @@
|
||||||
org.keycloak.protocol.LoginProtocolSpi
|
org.keycloak.protocol.LoginProtocolSpi
|
||||||
org.keycloak.protocol.ProtocolMapperSpi
|
org.keycloak.protocol.ProtocolMapperSpi
|
||||||
org.keycloak.exportimport.ClientImportSpi
|
org.keycloak.exportimport.ClientDescriptionConverterSpi
|
||||||
org.keycloak.wellknown.WellKnownSpi
|
org.keycloak.wellknown.WellKnownSpi
|
||||||
org.keycloak.messages.MessagesSpi
|
org.keycloak.messages.MessagesSpi
|
||||||
org.keycloak.authentication.AuthenticatorSpi
|
org.keycloak.authentication.AuthenticatorSpi
|
||||||
|
|
|
@ -7,7 +7,7 @@ import org.openqa.selenium.support.ui.Select;
|
||||||
/**
|
/**
|
||||||
* Created by mhajas on 8/21/15.
|
* Created by mhajas on 8/21/15.
|
||||||
*/
|
*/
|
||||||
public class Bindings extends Authentication{
|
public class Bindings extends Authentication {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getUriFragment() {
|
public String getUriFragment() {
|
||||||
|
@ -17,48 +17,48 @@ public class Bindings extends Authentication{
|
||||||
@FindBy(id = "browser")
|
@FindBy(id = "browser")
|
||||||
private Select BrowserFlowSelect;
|
private Select BrowserFlowSelect;
|
||||||
|
|
||||||
|
@FindBy(id = "registration")
|
||||||
|
private Select RegistrationFlowSelect;
|
||||||
|
|
||||||
|
@FindBy(id = "grant")
|
||||||
|
private Select DirectGrantFlowSelect;
|
||||||
|
|
||||||
|
@FindBy(id = "resetCredentials")
|
||||||
|
private Select ResetCredentialsSelect;
|
||||||
|
|
||||||
|
@FindBy(id = "clientAuthentication")
|
||||||
|
private Select ClientAuthenticationSelect;
|
||||||
|
|
||||||
|
@FindBy(xpath = "//button[text()='Save']")
|
||||||
|
private WebElement saveButton;
|
||||||
|
|
||||||
|
@FindBy(xpath = "//button[text()='Cancel']")
|
||||||
|
private WebElement cancelButton;
|
||||||
|
|
||||||
public void changeBrowserFlowSelect(BrowserFlowSelectValues value) {
|
public void changeBrowserFlowSelect(BrowserFlowSelectValues value) {
|
||||||
BrowserFlowSelect.selectByVisibleText(value.getName());
|
BrowserFlowSelect.selectByVisibleText(value.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindBy(id = "registration")
|
|
||||||
private Select RegistrationFlowSelect;
|
|
||||||
|
|
||||||
public void changeRegistrationFlowSelect(RegistrationFlowSelectValues value) {
|
public void changeRegistrationFlowSelect(RegistrationFlowSelectValues value) {
|
||||||
RegistrationFlowSelect.selectByVisibleText(value.getName());
|
RegistrationFlowSelect.selectByVisibleText(value.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindBy(id = "grant")
|
|
||||||
private Select DirectGrantFlowSelect;
|
|
||||||
|
|
||||||
public void changeDirectGrantFlowSelect(DirectGrantFlowSelectValues value) {
|
public void changeDirectGrantFlowSelect(DirectGrantFlowSelectValues value) {
|
||||||
DirectGrantFlowSelect.selectByVisibleText(value.getName());
|
DirectGrantFlowSelect.selectByVisibleText(value.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindBy(id = "resetCredentials")
|
|
||||||
private Select ResetCredentialsSelect;
|
|
||||||
|
|
||||||
public void changeResetCredentialsSelect(ResetCredentialsSelectValues value) {
|
public void changeResetCredentialsSelect(ResetCredentialsSelectValues value) {
|
||||||
ResetCredentialsSelect.selectByVisibleText(value.getName());
|
ResetCredentialsSelect.selectByVisibleText(value.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindBy(id = "clientAuthentication")
|
|
||||||
private Select ClientAuthenticationSelect;
|
|
||||||
|
|
||||||
public void changeClientAuthenticationSelect(ClientAuthenticationSelectValues value) {
|
public void changeClientAuthenticationSelect(ClientAuthenticationSelectValues value) {
|
||||||
ClientAuthenticationSelect.selectByVisibleText(value.getName());
|
ClientAuthenticationSelect.selectByVisibleText(value.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindBy(xpath = "//button[text()='Save']")
|
|
||||||
private WebElement saveButton;
|
|
||||||
|
|
||||||
public void clickSave() {
|
public void clickSave() {
|
||||||
saveButton.click();
|
saveButton.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindBy(xpath = "//button[text()='Cancel']")
|
|
||||||
private WebElement cancelButton;
|
|
||||||
|
|
||||||
public void clickCancel() {
|
public void clickCancel() {
|
||||||
cancelButton.click();
|
cancelButton.click();
|
||||||
}
|
}
|
||||||
|
@ -143,5 +143,4 @@ public class Bindings extends Authentication{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,14 @@ public class Flows extends Authentication {
|
||||||
@FindBy(tagName = "select")
|
@FindBy(tagName = "select")
|
||||||
private Select flowSelect;
|
private Select flowSelect;
|
||||||
|
|
||||||
public void changeFlowSelect(FlowSelectValues value) {
|
@FindBy(linkText = "New")
|
||||||
flowSelect.selectByVisibleText(value.getName());
|
private WebElement newButton;
|
||||||
}
|
|
||||||
|
@FindBy(linkText = "Copy")
|
||||||
|
private WebElement copyButton;
|
||||||
|
|
||||||
|
@FindBy(tagName = "table")
|
||||||
|
private WebElement flowsTable;
|
||||||
|
|
||||||
public enum FlowSelectValues {
|
public enum FlowSelectValues {
|
||||||
|
|
||||||
|
@ -37,13 +42,12 @@ public class Flows extends Authentication {
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindBy(linkText = "New")
|
public void changeFlowSelect(FlowSelectValues value) {
|
||||||
private WebElement newButton;
|
flowSelect.selectByVisibleText(value.getName());
|
||||||
|
}
|
||||||
@FindBy(linkText = "Copy")
|
|
||||||
private WebElement copyButton;
|
|
||||||
|
|
||||||
public void clickNew() {
|
public void clickNew() {
|
||||||
newButton.click();
|
newButton.click();
|
||||||
|
@ -53,160 +57,156 @@ public class Flows extends Authentication {
|
||||||
copyButton.click();
|
copyButton.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void clickRadioButton(String row, int column) {
|
||||||
|
flowsTable.findElement(By.xpath("//td[text()[contains(.,'" + row + "')]]/../td[" + String.valueOf(column) + "]//input[@type='radio']")).click();
|
||||||
|
}
|
||||||
|
|
||||||
// Direct grant
|
// Direct grant
|
||||||
public void setPasswordRequired() {
|
public void setPasswordRequired() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Password')]]/../td[2]//input[@type='radio']")).click();
|
clickRadioButton("Password", 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPasswordDisabled() {
|
public void setPasswordDisabled() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Password')]]/../td[3]//input[@type='radio']")).click();
|
clickRadioButton("Password", 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOTPRequired() {
|
public void setOTPRequired() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'O T P')]]/../td[2]//input[@type='radio']")).click();
|
clickRadioButton("O T P", 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOTPOptional() {
|
public void setOTPOptional() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'O T P')]]/../td[3]//input[@type='radio']")).click();
|
clickRadioButton("O T P", 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOTPDisabled() {
|
public void setOTPDisabled() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'O T P')]]/../td[4]//input[@type='radio']")).click();
|
clickRadioButton("O T P", 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Registration
|
// Registration
|
||||||
public void setRegistrationFormRequired() {
|
public void setRegistrationFormRequired() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Registration form')]]/../td[3]//input[@type='radio']")).click();
|
clickRadioButton("Registration form", 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRegistrationFormDisabled() {
|
public void setRegistrationFormDisabled() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Registration form')]]/../td[4]//input[@type='radio']")).click();
|
clickRadioButton("Registration form", 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRegistrationUserCreationRequired() {
|
public void setRegistrationUserCreationRequired() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Registration User Creation')]]/../td[3]//input[@type='radio']")).click();
|
clickRadioButton("Registration User Creation", 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRegistrationUserCreationDisabled() {
|
public void setRegistrationUserCreationDisabled() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Registration User Creation')]]/../td[4]//input[@type='radio']")).click();
|
clickRadioButton("Registration User Creation", 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setProfileValidationRequired() {
|
public void setProfileValidationRequired() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Profile Validation')]]/../td[3]//input[@type='radio']")).click();
|
clickRadioButton("Profile Validation", 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setProfileValidationDisabled() {
|
public void setProfileValidationDisabled() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Profile Validation')]]/../td[4]//input[@type='radio']")).click();
|
clickRadioButton("Profile Validation", 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPasswordValidationRequired() {
|
public void setPasswordValidationRequired() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Password Validation')]]/../td[3]//input[@type='radio']")).click();
|
clickRadioButton("Password Validation", 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPasswordValidationDisabled() {
|
public void setPasswordValidationDisabled() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Password Validation')]]/../td[4]//input[@type='radio']")).click();
|
clickRadioButton("Password Validation", 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRecaptchaRequired() {
|
public void setRecaptchaRequired() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Recaptcha')]]/../td[3]//input[@type='radio']")).click();
|
clickRadioButton("Recaptcha", 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRecaptchaDisabled() {
|
public void setRecaptchaDisabled() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Recaptcha')]]/../td[4]//input[@type='radio']")).click();
|
clickRadioButton("Recaptcha", 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Browser
|
// Browser
|
||||||
public void setCookieAlternative() {
|
public void setCookieAlternative() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Cookie')]]/../td[3]//input[@type='radio']")).click();
|
clickRadioButton("Cookie", 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCookieDisabled() {
|
public void setCookieDisabled() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Cookie')]]/../td[4]//input[@type='radio']")).click();
|
clickRadioButton("Cookie", 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setKerberosAlternative() {
|
public void setKerberosAlternative() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Kerberos')]]/../td[3]//input[@type='radio']")).click();
|
clickRadioButton("Kerberos", 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setKerberosRequired() {
|
public void setKerberosRequired() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Kerberos')]]/../td[4]//input[@type='radio']")).click();
|
clickRadioButton("Kerberos", 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setKerberosDisabled() {
|
public void setKerberosDisabled() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Kerberos')]]/../td[5]//input[@type='radio']")).click();
|
clickRadioButton("Kerberos", 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFormsAlternative() {
|
public void setFormsAlternative() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Forms')]]/../td[3]//input[@type='radio']")).click();
|
clickRadioButton("Forms", 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFormsRequired() {
|
public void setFormsRequired() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Forms')]]/../td[4]//input[@type='radio']")).click();
|
clickRadioButton("Forms", 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFormsDisabled() {
|
public void setFormsDisabled() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Forms')]]/../td[5]//input[@type='radio']")).click();
|
clickRadioButton("Forms", 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOTPFormRequired() {
|
public void setOTPFormRequired() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,' O T P Form')]]/../td[3]//input[@type='radio']")).click();
|
clickRadioButton(" O T P Form", 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOTPFormOptional() {
|
public void setOTPFormOptional() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,' O T P Form')]]/../td[4]//input[@type='radio']")).click();
|
clickRadioButton(" O T P Form", 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOTPFormDisabled() {
|
public void setOTPFormDisabled() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,' O T P Form')]]/../td[5]//input[@type='radio']")).click();
|
clickRadioButton(" O T P Form", 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset credentials
|
// Reset credentials
|
||||||
public void setResetPasswordRequired() {
|
public void setResetPasswordRequired() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Reset Password')]]/../td[2]//input[@type='radio']")).click();
|
clickRadioButton("Reset Password", 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setResetPasswordOptional() {
|
public void setResetPasswordOptional() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Reset Password')]]/../td[3]//input[@type='radio']")).click();
|
clickRadioButton("Reset Password", 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setResetPasswordDisabled() {
|
public void setResetPasswordDisabled() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Reset Password')]]/../td[4]//input[@type='radio']")).click();
|
clickRadioButton("Reset Password", 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setResetOTPRequired() {
|
public void setResetOTPRequired() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Reset O T P')]]/../td[2]//input[@type='radio']")).click();
|
clickRadioButton("Reset O T P", 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setResetOTPOptional() {
|
public void setResetOTPOptional() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Reset O T P')]]/../td[3]//input[@type='radio']")).click();
|
clickRadioButton("Reset O T P", 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setResetOTPDisabled() {
|
public void setResetOTPDisabled() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Reset O T P')]]/../td[4]//input[@type='radio']")).click();
|
clickRadioButton("Reset O T P", 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clients
|
// Clients
|
||||||
public void setClientIdAndSecretRequired() {
|
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Client Id and Secret')]]/../td[2]//input[@type='radio']")).click();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setClientIdAndSecretAlternative() {
|
public void setClientIdAndSecretAlternative() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Client Id and Secret')]]/../td[3]//input[@type='radio']")).click();
|
clickRadioButton("Client Id and Secret", 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setClientIdAndSecretDisabled() {
|
public void setClientIdAndSecretDisabled() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,'Client Id and Secret')]]/../td[4]//input[@type='radio']")).click();
|
clickRadioButton("Client Id and Secret", 3);
|
||||||
}
|
|
||||||
|
|
||||||
public void setSignedJwtRequired() {
|
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,' Signed Jwt')]]/../td[2]//input[@type='radio']")).click();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSignedJwtAlternative() {
|
public void setSignedJwtAlternative() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,' Signed Jwt')]]/../td[3]//input[@type='radio']")).click();
|
clickRadioButton(" Signed Jwt", 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSignedJwtDisabled() {
|
public void setSignedJwtDisabled() {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()[contains(.,' Signed Jwt')]]/../td[4]//input[@type='radio']")).click();
|
clickRadioButton(" Signed Jwt", 3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,20 +19,20 @@ public class OTPPolicy extends Authentication {
|
||||||
@FindBy(linkText = "Cancel")
|
@FindBy(linkText = "Cancel")
|
||||||
private WebElement cancelButton;
|
private WebElement cancelButton;
|
||||||
|
|
||||||
|
@FindBy(id = "lookAhead")
|
||||||
|
private WebElement lookAheadInput;
|
||||||
|
|
||||||
|
@FindBy(id = "counter")
|
||||||
|
private WebElement initialCounterInput;
|
||||||
|
|
||||||
public void clickCancel() {
|
public void clickCancel() {
|
||||||
cancelButton.click();
|
cancelButton.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindBy(id = "lookAhead")
|
|
||||||
private WebElement lookAheadInput;
|
|
||||||
|
|
||||||
public void setLookAheadInputValue(String value) {
|
public void setLookAheadInputValue(String value) {
|
||||||
Form.setInputValue(lookAheadInput, value);
|
Form.setInputValue(lookAheadInput, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindBy(id = "counter")
|
|
||||||
private WebElement initialCounterInput;
|
|
||||||
|
|
||||||
public void setInitialcounterInputValue(String value) {
|
public void setInitialcounterInputValue(String value) {
|
||||||
Form.setInputValue(initialCounterInput, value);
|
Form.setInputValue(initialCounterInput, value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
package org.keycloak.testsuite.console.page.authentication;
|
package org.keycloak.testsuite.console.page.authentication;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.jboss.arquillian.graphene.findby.ByJQuery;
|
import org.jboss.arquillian.graphene.findby.ByJQuery;
|
||||||
import static org.keycloak.testsuite.util.WaitUtils.waitGuiForElement;
|
|
||||||
import org.openqa.selenium.By;
|
import org.openqa.selenium.By;
|
||||||
import org.openqa.selenium.WebElement;
|
import org.openqa.selenium.WebElement;
|
||||||
import org.openqa.selenium.support.FindBy;
|
import org.openqa.selenium.support.FindBy;
|
||||||
import org.openqa.selenium.support.ui.Select;
|
import org.openqa.selenium.support.ui.Select;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.keycloak.testsuite.util.WaitUtils.waitGuiForElement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Petr Mensik
|
* @author Petr Mensik
|
||||||
* @author tkyjovsk
|
* @author tkyjovsk
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package org.keycloak.testsuite.console.page.authentication;
|
package org.keycloak.testsuite.console.page.authentication;
|
||||||
|
|
||||||
import org.openqa.selenium.By;
|
import org.openqa.selenium.By;
|
||||||
|
import org.openqa.selenium.WebElement;
|
||||||
|
import org.openqa.selenium.support.FindBy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author tkyjovsk
|
* @author tkyjovsk
|
||||||
|
@ -8,48 +10,62 @@ import org.openqa.selenium.By;
|
||||||
*/
|
*/
|
||||||
public class RequiredActions extends Authentication {
|
public class RequiredActions extends Authentication {
|
||||||
|
|
||||||
|
public final static String ENABLED = "enabled";
|
||||||
|
public final static String DEFAULT_ACTION = "defaultAction";
|
||||||
|
|
||||||
|
@FindBy(tagName = "table")
|
||||||
|
private WebElement requiredActionTable;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getUriFragment() {
|
public String getUriFragment() {
|
||||||
return super.getUriFragment() + "/required-actions";
|
return super.getUriFragment() + "/required-actions";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clickTermsAndConditionEnabled() {
|
private void setRequiredActionValue(String row, String column, boolean value) {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()='Terms and Conditions']/..//input[@type='checkbox' and @ng-model='requiredAction.enabled']")).click();
|
WebElement checkbox = requiredActionTable.findElement(By.xpath("//td[text()='" + row + "']/..//input[@ng-model='requiredAction." + column + "']"));
|
||||||
|
|
||||||
|
if (checkbox.isSelected() != value) {
|
||||||
|
checkbox.click();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clickTermsAndConditionDefaultAction() {
|
public void setTermsAndConditionEnabled(boolean value) {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()='Terms and Conditions']/..//input[@type='checkbox' and @ng-model='requiredAction.defaultAction']")).click();
|
setRequiredActionValue("Terms and Conditions", ENABLED, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clickVerifyEmailEnabled() {
|
public void setTermsAndConditionDefaultAction(boolean value) {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()='Verify Email']/..//input[@type='checkbox' and @ng-model='requiredAction.enabled']")).click();
|
setRequiredActionValue("Terms and Conditions", DEFAULT_ACTION, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clickVerifyEmailDefaultAction() {
|
public void setVerifyEmailEnabled(boolean value) {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()='Verify Email']/..//input[@type='checkbox' and @ng-model='requiredAction.defaultAction']")).click();
|
setRequiredActionValue("Verify Email", ENABLED, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clickUpdatePasswordEnabled() {
|
public void setVerifyEmailDefaultAction(boolean value) {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()='Update Password']/..//input[@type='checkbox' and @ng-model='requiredAction.enabled']")).click();
|
setRequiredActionValue("Verify Email", DEFAULT_ACTION, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clickUpdatePasswordDefaultAction() {
|
public void setUpdatePasswordEnabled(boolean value) {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()='Update Password']/..//input[@type='checkbox' and @ng-model='requiredAction.defaultAction']")).click();
|
setRequiredActionValue("Update Password", ENABLED, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clickConfigureTotpEnabled() {
|
public void setUpdatePasswordDefaultAction(boolean value) {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()='Configure Totp']/..//input[@type='checkbox' and @ng-model='requiredAction.enabled']")).click();
|
setRequiredActionValue("Update Password", DEFAULT_ACTION, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clickConfigureTotpDefaultAction() {
|
public void setConfigureTotpEnabled(boolean value) {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()='Configure Totp']/..//input[@type='checkbox' and @ng-model='requiredAction.defaultAction']")).click();
|
setRequiredActionValue("Configure Totp", ENABLED, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clickUpdateProfileEnabled() {
|
public void setConfigureTotpDefaultAction(boolean value) {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()='Update Profile']/..//input[@type='checkbox' and @ng-model='requiredAction.enabled']")).click();
|
setRequiredActionValue("Configure Totp", DEFAULT_ACTION, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clickUpdateProfileDefaultAction() {
|
public void setUpdateProfileEnabled(boolean value) {
|
||||||
driver.findElement(By.xpath("//td[@class='ng-binding' and text()='Update Profile']/..//input[@type='checkbox' and @ng-model='requiredAction.defaultAction']")).click();
|
setRequiredActionValue("Update Profile", ENABLED, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdateProfileDefaultAction(boolean value) {
|
||||||
|
setRequiredActionValue("Update Profile", DEFAULT_ACTION, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import org.openqa.selenium.WebElement;
|
||||||
import org.openqa.selenium.support.FindBy;
|
import org.openqa.selenium.support.FindBy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author tkyjovsk
|
* @author tkyjovsk
|
||||||
* @author mhajas
|
* @author mhajas
|
||||||
*/
|
*/
|
||||||
|
@ -27,6 +26,12 @@ public class AdminEvents extends Events {
|
||||||
|
|
||||||
public class AdminEventsTable extends DataTable {
|
public class AdminEventsTable extends DataTable {
|
||||||
|
|
||||||
|
@FindBy(xpath = "//button[text()[contains(.,'Filter')]]")
|
||||||
|
private WebElement filterButton;
|
||||||
|
|
||||||
|
@FindBy(tagName = "form")
|
||||||
|
private AdminEventsTableFilterForm filterForm;
|
||||||
|
|
||||||
public void update() {
|
public void update() {
|
||||||
waitAjaxForBody();
|
waitAjaxForBody();
|
||||||
clickHeaderButton("Update");
|
clickHeaderButton("Update");
|
||||||
|
@ -37,63 +42,63 @@ public class AdminEvents extends Events {
|
||||||
clickHeaderButton("Reset");
|
clickHeaderButton("Reset");
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindBy(xpath = "//button[text()[contains(.,'Filter')]]")
|
|
||||||
private WebElement filterButton;
|
|
||||||
|
|
||||||
public void filter() {
|
public void filter() {
|
||||||
waitAjaxForBody();
|
waitAjaxForBody();
|
||||||
filterButton.click();
|
filterButton.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindBy(tagName = "form")
|
|
||||||
private AdminEventsTableFilterForm filterForm;
|
|
||||||
|
|
||||||
public AdminEventsTableFilterForm filterForm() {
|
public AdminEventsTableFilterForm filterForm() {
|
||||||
return filterForm;
|
return filterForm;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AdminEventsTableFilterForm extends Form {
|
public class AdminEventsTableFilterForm extends Form {
|
||||||
|
|
||||||
|
@FindBy(id = "resource")
|
||||||
|
private WebElement resourcePathInput;
|
||||||
|
|
||||||
|
@FindBy(id = "realm")
|
||||||
|
private WebElement realmInput;
|
||||||
|
|
||||||
|
@FindBy(id = "client")
|
||||||
|
private WebElement clientInput;
|
||||||
|
|
||||||
|
@FindBy(id = "user")
|
||||||
|
private WebElement userInput;
|
||||||
|
|
||||||
|
@FindBy(id = "ipAddress")
|
||||||
|
private WebElement ipAddressInput;
|
||||||
|
|
||||||
|
@FindBy(xpath = "//div[@id='s2id_adminEnabledEventOperations']/ul")
|
||||||
|
private WebElement operationTypesInput;
|
||||||
|
|
||||||
|
@FindBy(xpath = "//div[@id='select2-drop']")
|
||||||
|
private WebElement operationTypesValues;
|
||||||
|
|
||||||
public void addOperationType(String type) {
|
public void addOperationType(String type) {
|
||||||
driver.findElement(By.xpath("//div[@id='s2id_adminEnabledEventOperations']/ul")).click();
|
operationTypesInput.click();
|
||||||
driver.findElement(By.xpath("//div[@id='select2-drop']//div[text()[contains(.,'" + type + "')]]/..")).click();
|
operationTypesValues.findElement(By.xpath("//div[text() = '" + type + "']")).click();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeOperationType(String type) {
|
public void removeOperationType(String type) {
|
||||||
driver.findElement(By.xpath("//div[@id='s2id_adminEnabledEventOperations']//div[text()='" + type + "']/../a")).click();
|
operationTypesInput.findElement(By.xpath("//div[text()='" + type + "']/../a")).click();
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindBy(id = "resource")
|
|
||||||
private WebElement resourcePathInput;
|
|
||||||
|
|
||||||
public void setResourcePathInput(String value) {
|
public void setResourcePathInput(String value) {
|
||||||
setInputValue(resourcePathInput, value);
|
setInputValue(resourcePathInput, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindBy(id = "realm")
|
|
||||||
private WebElement realmInput;
|
|
||||||
|
|
||||||
public void setRealmInput(String value) {
|
public void setRealmInput(String value) {
|
||||||
setInputValue(realmInput, value);
|
setInputValue(realmInput, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindBy(id = "client")
|
|
||||||
private WebElement clientInput;
|
|
||||||
|
|
||||||
public void setClientInput(String value) {
|
public void setClientInput(String value) {
|
||||||
setInputValue(clientInput, value);
|
setInputValue(clientInput, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindBy(id = "user")
|
|
||||||
private WebElement userInput;
|
|
||||||
|
|
||||||
public void setUserInput(String value) {
|
public void setUserInput(String value) {
|
||||||
setInputValue(userInput, value);
|
setInputValue(userInput, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindBy(id = "ipAddress")
|
|
||||||
private WebElement ipAddressInput;
|
|
||||||
|
|
||||||
public void setIpAddressInput(String value) {
|
public void setIpAddressInput(String value) {
|
||||||
setInputValue(ipAddressInput, value);
|
setInputValue(ipAddressInput, value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,33 @@ public class Config extends Events {
|
||||||
@FindBy(xpath = "//div[@id='s2id_autogen1']/..//select")
|
@FindBy(xpath = "//div[@id='s2id_autogen1']/..//select")
|
||||||
private Select eventListenersSelect;
|
private Select eventListenersSelect;
|
||||||
|
|
||||||
|
@FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='enabled']]")
|
||||||
|
private OnOffSwitch SaveEvents;
|
||||||
|
|
||||||
|
@FindBy(xpath = "//div[@id='s2id_enabledEventTypes']//input")
|
||||||
|
private WebElement savedTypesInput;
|
||||||
|
|
||||||
|
@FindBy(xpath = "//div[@id='select2-drop']/ul")
|
||||||
|
private WebElement savedTypesOptions;
|
||||||
|
|
||||||
|
@FindBy(id = "expiration")
|
||||||
|
private WebElement expirationInput;
|
||||||
|
|
||||||
|
@FindBy(name = "expirationUnit")
|
||||||
|
private Select expirationUnitSelect;
|
||||||
|
|
||||||
|
@FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='adminEventsEnabled']]")
|
||||||
|
private OnOffSwitch saveAdminEvents;
|
||||||
|
|
||||||
|
@FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='adminEventsDetailsEnabled']]")
|
||||||
|
private OnOffSwitch includeRepresentation;
|
||||||
|
|
||||||
|
@FindBy(xpath = "//button[@data-ng-click='clearEvents()']")
|
||||||
|
private WebElement clearLoginEventsButton;
|
||||||
|
|
||||||
|
@FindBy(xpath = "//button[@data-ng-click='clearAdminEvents()']")
|
||||||
|
private WebElement clearAdminEventsButton;
|
||||||
|
|
||||||
public void addEventListener(String listener) {
|
public void addEventListener(String listener) {
|
||||||
eventListenersInput.click();
|
eventListenersInput.click();
|
||||||
eventListenersSelect.selectByVisibleText(listener);
|
eventListenersSelect.selectByVisibleText(listener);
|
||||||
|
@ -41,19 +68,10 @@ public class Config extends Events {
|
||||||
eventListenersInput.findElement(By.xpath("//div[text()='" + listener + "']/../a")).click();
|
eventListenersInput.findElement(By.xpath("//div[text()='" + listener + "']/../a")).click();
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='enabled']]")
|
|
||||||
private OnOffSwitch SaveEvents;
|
|
||||||
|
|
||||||
public void setSaveEvents(boolean value) {
|
public void setSaveEvents(boolean value) {
|
||||||
SaveEvents.setOn(value);
|
SaveEvents.setOn(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindBy(xpath = "//div[@id='s2id_enabledEventTypes']//input")
|
|
||||||
private WebElement savedTypesInput;
|
|
||||||
|
|
||||||
@FindBy(xpath = "//div[@id='select2-drop']/ul")
|
|
||||||
private WebElement savedTypesOptions;
|
|
||||||
|
|
||||||
public void addSaveType(String type) {
|
public void addSaveType(String type) {
|
||||||
savedTypesInput.click();
|
savedTypesInput.click();
|
||||||
savedTypesOptions.findElement(By.xpath("//div[text()='" + type + "']")).click();
|
savedTypesOptions.findElement(By.xpath("//div[text()='" + type + "']")).click();
|
||||||
|
@ -64,36 +82,24 @@ public class Config extends Events {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearLoginEvents() {
|
public void clearLoginEvents() {
|
||||||
driver.findElement(By.xpath("//button[@data-ng-click='clearEvents()']")).click();
|
clearLoginEventsButton.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindBy(id = "expiration")
|
|
||||||
private WebElement expirationInput;
|
|
||||||
|
|
||||||
@FindBy(name = "expirationUnit")
|
|
||||||
private Select expirationUnitSelect;
|
|
||||||
|
|
||||||
public void setExpiration(String value, String unit) {
|
public void setExpiration(String value, String unit) {
|
||||||
expirationUnitSelect.selectByVisibleText(unit);
|
expirationUnitSelect.selectByVisibleText(unit);
|
||||||
Form.setInputValue(expirationInput, value);
|
Form.setInputValue(expirationInput, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='adminEventsEnabled']]")
|
|
||||||
private OnOffSwitch saveAdminEvents;
|
|
||||||
|
|
||||||
public void setSaveAdminEvents(boolean value) {
|
public void setSaveAdminEvents(boolean value) {
|
||||||
saveAdminEvents.setOn(value);
|
saveAdminEvents.setOn(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='adminEventsDetailsEnabled']]")
|
|
||||||
private OnOffSwitch includeRepresentation;
|
|
||||||
|
|
||||||
public void setIncludeRepresentation(boolean value) {
|
public void setIncludeRepresentation(boolean value) {
|
||||||
includeRepresentation.setOn(value);
|
includeRepresentation.setOn(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearAdminEvents() {
|
public void clearAdminEvents() {
|
||||||
driver.findElement(By.xpath("//button[@data-ng-click='clearAdminEvents()']")).click();
|
clearAdminEventsButton.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,12 @@ public class LoginEvents extends Events {
|
||||||
|
|
||||||
public class LoginEventsTable extends DataTable {
|
public class LoginEventsTable extends DataTable {
|
||||||
|
|
||||||
|
@FindBy(xpath = "//button[text()[contains(.,'Filter')]]")
|
||||||
|
private WebElement filterButton;
|
||||||
|
|
||||||
|
@FindBy(tagName = "form")
|
||||||
|
private LoginEventsTableFilterForm filterForm;
|
||||||
|
|
||||||
public void update() {
|
public void update() {
|
||||||
waitAjaxForBody();
|
waitAjaxForBody();
|
||||||
clickHeaderButton("Update");
|
clickHeaderButton("Update");
|
||||||
|
@ -36,42 +42,42 @@ public class LoginEvents extends Events {
|
||||||
clickHeaderButton("Reset");
|
clickHeaderButton("Reset");
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindBy(xpath = "//button[text()[contains(.,'Filter')]]")
|
|
||||||
private WebElement filterButton;
|
|
||||||
|
|
||||||
public void filter() {
|
public void filter() {
|
||||||
waitAjaxForBody();
|
waitAjaxForBody();
|
||||||
filterButton.click();
|
filterButton.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindBy(tagName = "form")
|
|
||||||
private LoginEventsTableFilterForm filterForm;
|
|
||||||
|
|
||||||
public LoginEventsTableFilterForm filterForm() {
|
public LoginEventsTableFilterForm filterForm() {
|
||||||
return filterForm;
|
return filterForm;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class LoginEventsTableFilterForm extends Form {
|
public class LoginEventsTableFilterForm extends Form {
|
||||||
|
|
||||||
|
@FindBy(id = "client")
|
||||||
|
private WebElement clientInput;
|
||||||
|
|
||||||
|
@FindBy(id = "user")
|
||||||
|
private WebElement userInput;
|
||||||
|
|
||||||
|
@FindBy(xpath = "//div[@id='s2id_eventTypes']/ul")
|
||||||
|
private WebElement eventTypeInput;
|
||||||
|
|
||||||
|
@FindBy(xpath = "//div[@id='select2-drop']")
|
||||||
|
private WebElement eventTypeValues;
|
||||||
|
|
||||||
public void addEventType(String type) {
|
public void addEventType(String type) {
|
||||||
driver.findElement(By.xpath("//div[@id='s2id_eventTypes']/ul")).click();
|
eventTypeInput.click();
|
||||||
driver.findElement(By.xpath("//div[@id='select2-drop']//div[text()='" + type + "']/..")).click();
|
eventTypeValues.findElement(By.xpath("//div[text()='" + type + "']")).click();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeOperationType(String type) {
|
public void removeOperationType(String type) {
|
||||||
driver.findElement(By.xpath("//div[@id='s2id_eventTypes']//div[text()='" + type + "']/../a")).click();
|
eventTypeInput.findElement(By.xpath("//div[text()='" + type + "']/../a]")).click();
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindBy(id = "client")
|
|
||||||
private WebElement clientInput;
|
|
||||||
|
|
||||||
public void setClientInput(String value) {
|
public void setClientInput(String value) {
|
||||||
setInputValue(clientInput, value);
|
setInputValue(clientInput, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindBy(id = "user")
|
|
||||||
private WebElement userInput;
|
|
||||||
|
|
||||||
public void setUserInput(String value) {
|
public void setUserInput(String value) {
|
||||||
setInputValue(userInput, value);
|
setInputValue(userInput, value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
package org.keycloak.testsuite.console.page.realm;
|
||||||
|
|
||||||
|
import org.jboss.arquillian.graphene.findby.FindByJQuery;
|
||||||
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
|
import org.keycloak.testsuite.console.page.fragment.OnOffSwitch;
|
||||||
|
import org.keycloak.testsuite.page.Form;
|
||||||
|
import org.openqa.selenium.WebElement;
|
||||||
|
import org.openqa.selenium.support.FindBy;
|
||||||
|
import org.openqa.selenium.support.ui.Select;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author mhajas
|
||||||
|
*/
|
||||||
|
public class BruteForceDetection extends SecurityDefenses {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUriFragment() {
|
||||||
|
return super.getUriFragment() + "/brute-force";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Page
|
||||||
|
private BruteForceDetectionForm form;
|
||||||
|
|
||||||
|
public BruteForceDetectionForm form() {
|
||||||
|
return form;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BruteForceDetectionForm extends Form {
|
||||||
|
|
||||||
|
@FindByJQuery("div[class='onoffswitch']")
|
||||||
|
private OnOffSwitch protectionEnabled;
|
||||||
|
|
||||||
|
@FindBy(id = "failureFactor")
|
||||||
|
private WebElement maxLoginFailures;
|
||||||
|
|
||||||
|
@FindBy(id = "waitIncrement")
|
||||||
|
private WebElement waitIncrementInput;
|
||||||
|
|
||||||
|
@FindBy(name = "waitIncrementUnit")
|
||||||
|
private Select waitIncrementSelect;
|
||||||
|
|
||||||
|
@FindBy(id = "quickLoginCheckMilliSeconds")
|
||||||
|
private WebElement quickLoginCheckInput;
|
||||||
|
|
||||||
|
@FindBy(id = "minimumQuickLoginWait")
|
||||||
|
private WebElement minQuickLoginWaitInput;
|
||||||
|
|
||||||
|
@FindBy(name = "minimumQuickLoginWaitUnit")
|
||||||
|
private Select minQuickLoginWaitSelect;
|
||||||
|
|
||||||
|
@FindBy(id = "maxFailureWait")
|
||||||
|
private WebElement maxWaitInput;
|
||||||
|
|
||||||
|
@FindBy(name = "maxFailureWaitUnit")
|
||||||
|
private Select maxWaitSelect;
|
||||||
|
|
||||||
|
@FindBy(id = "maxDeltaTime")
|
||||||
|
private WebElement failureResetTimeInput;
|
||||||
|
|
||||||
|
@FindBy(name = "maxDeltaTimeUnit")
|
||||||
|
private Select failureResetTimeSelect;
|
||||||
|
|
||||||
|
public void setProtectionEnabled(boolean protectionEnabled) {
|
||||||
|
this.protectionEnabled.setOn(protectionEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxLoginFailures(String value) {
|
||||||
|
setInputValue(maxLoginFailures, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWaitIncrementInput(String value) {
|
||||||
|
setInputValue(waitIncrementInput, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWaitIncrementSelect(TimeSelectValues value) {
|
||||||
|
waitIncrementSelect.selectByVisibleText(value.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQuickLoginCheckInput(String value) {
|
||||||
|
setInputValue(quickLoginCheckInput, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMinQuickLoginWaitInput(String value) {
|
||||||
|
setInputValue(minQuickLoginWaitInput, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMinQuickLoginWaitSelect(TimeSelectValues value) {
|
||||||
|
minQuickLoginWaitSelect.selectByVisibleText(value.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxWaitInput(String value) {
|
||||||
|
setInputValue(maxWaitInput, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxWaitSelect(TimeSelectValues value) {
|
||||||
|
maxWaitSelect.selectByVisibleText(value.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFailureResetTimeInput(String value) {
|
||||||
|
setInputValue(failureResetTimeInput, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFailureResetTimeSelect(TimeSelectValues value) {
|
||||||
|
failureResetTimeSelect.selectByVisibleText(value.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TimeSelectValues {
|
||||||
|
|
||||||
|
SECONDS("Seconds"), MINUTES("Minutes"), HOURS("Hours"), DAYS("Days");
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private TimeSelectValues(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package org.keycloak.testsuite.console.page.realm;
|
||||||
|
|
||||||
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
|
import org.keycloak.testsuite.page.Form;
|
||||||
|
import org.openqa.selenium.WebElement;
|
||||||
|
import org.openqa.selenium.support.FindBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author mhajas
|
||||||
|
*/
|
||||||
|
public class Headers extends SecurityDefenses {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUriFragment() {
|
||||||
|
return super.getUriFragment() + "/headers";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Page
|
||||||
|
private HeadersForm form;
|
||||||
|
|
||||||
|
public HeadersForm form() {
|
||||||
|
return form;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HeadersForm extends Form {
|
||||||
|
|
||||||
|
@FindBy(id = "xFrameOptions")
|
||||||
|
private WebElement xFrameOptions;
|
||||||
|
|
||||||
|
@FindBy(id = "contentSecurityPolicy")
|
||||||
|
private WebElement contentSecurityPolicy;
|
||||||
|
|
||||||
|
public void setXFrameOptions(String value) {
|
||||||
|
setInputValue(xFrameOptions, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContentSecurityPolicy(String value) {
|
||||||
|
setInputValue(contentSecurityPolicy, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,13 +18,7 @@
|
||||||
package org.keycloak.testsuite.console.page.realm;
|
package org.keycloak.testsuite.console.page.realm;
|
||||||
|
|
||||||
import org.jboss.arquillian.graphene.findby.FindByJQuery;
|
import org.jboss.arquillian.graphene.findby.FindByJQuery;
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
|
||||||
import org.keycloak.testsuite.console.page.fragment.OnOffSwitch;
|
|
||||||
import org.keycloak.testsuite.page.Form;
|
|
||||||
import static org.keycloak.testsuite.page.Form.setInputValue;
|
|
||||||
import org.openqa.selenium.WebElement;
|
import org.openqa.selenium.WebElement;
|
||||||
import org.openqa.selenium.support.FindBy;
|
|
||||||
import org.openqa.selenium.support.ui.Select;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Filip Kiss
|
* @author Filip Kiss
|
||||||
|
@ -37,160 +31,16 @@ public class SecurityDefenses extends RealmSettings {
|
||||||
return super.getUriFragment() + "/defense"; // NOTE: page doesn't exist, only subpages
|
return super.getUriFragment() + "/defense"; // NOTE: page doesn't exist, only subpages
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Headers extends SecurityDefenses {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUriFragment() {
|
|
||||||
return super.getUriFragment() + "/headers";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Page
|
|
||||||
private HeadersForm form;
|
|
||||||
|
|
||||||
public HeadersForm form() {
|
|
||||||
return form;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class HeadersForm extends Form {
|
|
||||||
|
|
||||||
@FindBy(id = "xFrameOptions")
|
|
||||||
private WebElement xFrameOptions;
|
|
||||||
|
|
||||||
public void setXFrameOptions(String value) {
|
|
||||||
setInputValue(xFrameOptions, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@FindBy(id = "contentSecurityPolicy")
|
|
||||||
private WebElement contentSecurityPolicy;
|
|
||||||
|
|
||||||
public void setContentSecurityPolicy(String value) {
|
|
||||||
setInputValue(contentSecurityPolicy, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum TimeSelectValues {
|
|
||||||
|
|
||||||
SECONDS("Seconds"), MINUTES("Minutes"), HOURS("Hours"), DAYS("Days");
|
|
||||||
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
private TimeSelectValues(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BruteForceDetection extends SecurityDefenses {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUriFragment() {
|
|
||||||
return super.getUriFragment() + "/brute-force";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Page
|
|
||||||
private BruteForceDetectionForm form;
|
|
||||||
|
|
||||||
public BruteForceDetectionForm form() {
|
|
||||||
return form;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BruteForceDetectionForm extends Form {
|
|
||||||
|
|
||||||
@FindByJQuery("div[class='onoffswitch']")
|
|
||||||
private OnOffSwitch protectionEnabled;
|
|
||||||
|
|
||||||
public void setProtectionEnabled(boolean protectionEnabled) {
|
|
||||||
this.protectionEnabled.setOn(protectionEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
@FindBy(id = "failureFactor")
|
|
||||||
private WebElement maxLoginFailures;
|
|
||||||
|
|
||||||
public void setMaxLoginFailures(String value) {
|
|
||||||
setInputValue(maxLoginFailures, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@FindBy(id = "waitIncrement")
|
|
||||||
private WebElement waitIncrementInput;
|
|
||||||
|
|
||||||
@FindBy(name = "waitIncrementUnit")
|
|
||||||
private Select waitIncrementSelect;
|
|
||||||
|
|
||||||
public void setWaitIncrementInput(String value) {
|
|
||||||
setInputValue(waitIncrementInput, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setWaitIncrementSelect(TimeSelectValues value) {
|
|
||||||
waitIncrementSelect.selectByVisibleText(value.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@FindBy(id = "quickLoginCheckMilliSeconds")
|
|
||||||
private WebElement quickLoginCheckInput;
|
|
||||||
|
|
||||||
public void setQuickLoginCheckInput(String value) {
|
|
||||||
setInputValue(quickLoginCheckInput, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@FindBy(id = "minimumQuickLoginWait")
|
|
||||||
private WebElement minQuickLoginWaitInput;
|
|
||||||
|
|
||||||
@FindBy(name = "minimumQuickLoginWaitUnit")
|
|
||||||
private Select minQuickLoginWaitSelect;
|
|
||||||
|
|
||||||
public void setMinQuickLoginWaitInput(String value) {
|
|
||||||
setInputValue(minQuickLoginWaitInput, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMinQuickLoginWaitSelect(TimeSelectValues value) {
|
|
||||||
minQuickLoginWaitSelect.selectByVisibleText(value.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@FindBy(id = "maxFailureWait")
|
|
||||||
private WebElement maxWaitInput;
|
|
||||||
|
|
||||||
@FindBy(name = "maxFailureWaitUnit")
|
|
||||||
private Select maxWaitSelect;
|
|
||||||
|
|
||||||
public void setMaxWaitInput(String value) {
|
|
||||||
setInputValue(maxWaitInput, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaxWaitSelect(TimeSelectValues value) {
|
|
||||||
maxWaitSelect.selectByVisibleText(value.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@FindBy(id = "maxDeltaTime")
|
|
||||||
private WebElement failureResetTimeInput;
|
|
||||||
|
|
||||||
@FindBy(name = "maxDeltaTimeUnit")
|
|
||||||
private Select failureResetTimeSelect;
|
|
||||||
|
|
||||||
public void setFailureResetTimeInput(String value) {
|
|
||||||
setInputValue(failureResetTimeInput, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFailureResetTimeSelect(TimeSelectValues value) {
|
|
||||||
failureResetTimeSelect.selectByVisibleText(value.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@FindByJQuery("a:contains('Brute Force Detection')")
|
@FindByJQuery("a:contains('Brute Force Detection')")
|
||||||
private WebElement bruteForceDetectionTab;
|
private WebElement bruteForceDetectionTab;
|
||||||
|
|
||||||
|
@FindByJQuery("a:contains('Headers')")
|
||||||
|
private WebElement headersTab;
|
||||||
|
|
||||||
public void goToBruteForceDetection() {
|
public void goToBruteForceDetection() {
|
||||||
bruteForceDetectionTab.click();
|
bruteForceDetectionTab.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
@FindByJQuery("a:contains('Headers')")
|
|
||||||
private WebElement headersTab;
|
|
||||||
|
|
||||||
public void goToHeaders() {
|
public void goToHeaders() {
|
||||||
headersTab.click();
|
headersTab.click();
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,9 +22,9 @@ import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.testsuite.console.AbstractConsoleTest;
|
import org.keycloak.testsuite.console.AbstractConsoleTest;
|
||||||
import org.keycloak.testsuite.console.page.authentication.PasswordPolicy;
|
import org.keycloak.testsuite.console.page.authentication.PasswordPolicy;
|
||||||
|
import org.keycloak.testsuite.console.page.users.UserCredentials;
|
||||||
|
|
||||||
import static org.keycloak.testsuite.console.page.authentication.PasswordPolicy.Type.*;
|
import static org.keycloak.testsuite.console.page.authentication.PasswordPolicy.Type.*;
|
||||||
import org.keycloak.testsuite.console.page.users.UserCredentials;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Petr Mensik
|
* @author Petr Mensik
|
||||||
|
|
|
@ -19,7 +19,6 @@ package org.keycloak.testsuite.console.authentication;
|
||||||
|
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.testsuite.auth.page.login.Registration;
|
import org.keycloak.testsuite.auth.page.login.Registration;
|
||||||
|
@ -57,43 +56,11 @@ public class RequiredActionsTest extends AbstractConsoleTest {
|
||||||
requiredActionsPage.navigateTo();
|
requiredActionsPage.navigateTo();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void requiredActionsTest() {
|
|
||||||
requiredActionsPage.clickTermsAndConditionEnabled();
|
|
||||||
assertFlashMessageSuccess();
|
|
||||||
|
|
||||||
requiredActionsPage.clickTermsAndConditionDefaultAction();
|
|
||||||
assertFlashMessageSuccess();
|
|
||||||
|
|
||||||
requiredActionsPage.clickVerifyEmailEnabled();
|
|
||||||
assertFlashMessageSuccess();
|
|
||||||
|
|
||||||
requiredActionsPage.clickVerifyEmailDefaultAction();
|
|
||||||
assertFlashMessageSuccess();
|
|
||||||
|
|
||||||
requiredActionsPage.clickUpdatePasswordEnabled();
|
|
||||||
assertFlashMessageSuccess();
|
|
||||||
|
|
||||||
requiredActionsPage.clickUpdatePasswordDefaultAction();
|
|
||||||
assertFlashMessageSuccess();
|
|
||||||
|
|
||||||
requiredActionsPage.clickConfigureTotpEnabled();
|
|
||||||
assertFlashMessageSuccess();
|
|
||||||
|
|
||||||
requiredActionsPage.clickConfigureTotpDefaultAction();
|
|
||||||
assertFlashMessageSuccess();
|
|
||||||
|
|
||||||
requiredActionsPage.clickUpdateProfileEnabled();
|
|
||||||
assertFlashMessageSuccess();
|
|
||||||
|
|
||||||
requiredActionsPage.clickUpdateProfileDefaultAction();
|
|
||||||
assertFlashMessageSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void termsAndConditionsDefaultActionTest() {
|
public void termsAndConditionsDefaultActionTest() {
|
||||||
requiredActionsPage.clickTermsAndConditionEnabled();
|
requiredActionsPage.setTermsAndConditionEnabled(true);
|
||||||
requiredActionsPage.clickTermsAndConditionDefaultAction();
|
requiredActionsPage.setTermsAndConditionDefaultAction(true);
|
||||||
|
assertFlashMessageSuccess();
|
||||||
|
|
||||||
allowTestRealmUserRegistration();
|
allowTestRealmUserRegistration();
|
||||||
|
|
||||||
|
@ -106,7 +73,8 @@ public class RequiredActionsTest extends AbstractConsoleTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void configureTotpDefaultActionTest() {
|
public void configureTotpDefaultActionTest() {
|
||||||
requiredActionsPage.clickConfigureTotpDefaultAction();
|
requiredActionsPage.setConfigureTotpDefaultAction(true);
|
||||||
|
assertFlashMessageSuccess();
|
||||||
|
|
||||||
allowTestRealmUserRegistration();
|
allowTestRealmUserRegistration();
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.testsuite.auth.page.account.Account;
|
import org.keycloak.testsuite.auth.page.account.Account;
|
||||||
import org.keycloak.testsuite.console.page.realm.SecurityDefenses;
|
import org.keycloak.testsuite.console.page.realm.BruteForceDetection;
|
||||||
import org.keycloak.testsuite.console.page.users.UserAttributes;
|
import org.keycloak.testsuite.console.page.users.UserAttributes;
|
||||||
import org.keycloak.testsuite.console.page.users.Users;
|
import org.keycloak.testsuite.console.page.users.Users;
|
||||||
import org.openqa.selenium.By;
|
import org.openqa.selenium.By;
|
||||||
|
@ -41,7 +41,7 @@ import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
|
||||||
public class SecurityDefensesTest extends AbstractRealmTest {
|
public class SecurityDefensesTest extends AbstractRealmTest {
|
||||||
|
|
||||||
@Page
|
@Page
|
||||||
private SecurityDefenses.BruteForceDetection bruteForceDetectionPage;
|
private BruteForceDetection bruteForceDetectionPage;
|
||||||
|
|
||||||
@Page
|
@Page
|
||||||
private Account testRealmAccountPage;
|
private Account testRealmAccountPage;
|
||||||
|
@ -69,7 +69,7 @@ public class SecurityDefensesTest extends AbstractRealmTest {
|
||||||
|
|
||||||
bruteForceDetectionPage.form().setProtectionEnabled(true);
|
bruteForceDetectionPage.form().setProtectionEnabled(true);
|
||||||
bruteForceDetectionPage.form().setMaxLoginFailures("1");
|
bruteForceDetectionPage.form().setMaxLoginFailures("1");
|
||||||
bruteForceDetectionPage.form().setWaitIncrementSelect(SecurityDefenses.TimeSelectValues.SECONDS);
|
bruteForceDetectionPage.form().setWaitIncrementSelect(BruteForceDetection.TimeSelectValues.SECONDS);
|
||||||
bruteForceDetectionPage.form().setWaitIncrementInput(String.valueOf(secondsToWait));
|
bruteForceDetectionPage.form().setWaitIncrementInput(String.valueOf(secondsToWait));
|
||||||
bruteForceDetectionPage.form().save();
|
bruteForceDetectionPage.form().save();
|
||||||
assertFlashMessageSuccess();
|
assertFlashMessageSuccess();
|
||||||
|
@ -110,7 +110,7 @@ public class SecurityDefensesTest extends AbstractRealmTest {
|
||||||
bruteForceDetectionPage.form().setProtectionEnabled(true);
|
bruteForceDetectionPage.form().setProtectionEnabled(true);
|
||||||
bruteForceDetectionPage.form().setMaxLoginFailures("100");
|
bruteForceDetectionPage.form().setMaxLoginFailures("100");
|
||||||
bruteForceDetectionPage.form().setQuickLoginCheckInput("1500");
|
bruteForceDetectionPage.form().setQuickLoginCheckInput("1500");
|
||||||
bruteForceDetectionPage.form().setMinQuickLoginWaitSelect(SecurityDefenses.TimeSelectValues.SECONDS);
|
bruteForceDetectionPage.form().setMinQuickLoginWaitSelect(BruteForceDetection.TimeSelectValues.SECONDS);
|
||||||
bruteForceDetectionPage.form().setMinQuickLoginWaitInput(String.valueOf(secondsToWait));
|
bruteForceDetectionPage.form().setMinQuickLoginWaitInput(String.valueOf(secondsToWait));
|
||||||
bruteForceDetectionPage.form().save();
|
bruteForceDetectionPage.form().save();
|
||||||
assertFlashMessageSuccess();
|
assertFlashMessageSuccess();
|
||||||
|
@ -150,7 +150,7 @@ public class SecurityDefensesTest extends AbstractRealmTest {
|
||||||
|
|
||||||
bruteForceDetectionPage.form().setProtectionEnabled(true);
|
bruteForceDetectionPage.form().setProtectionEnabled(true);
|
||||||
bruteForceDetectionPage.form().setMaxLoginFailures("1");
|
bruteForceDetectionPage.form().setMaxLoginFailures("1");
|
||||||
bruteForceDetectionPage.form().setMaxWaitSelect(SecurityDefenses.TimeSelectValues.SECONDS);
|
bruteForceDetectionPage.form().setMaxWaitSelect(BruteForceDetection.TimeSelectValues.SECONDS);
|
||||||
bruteForceDetectionPage.form().setMaxWaitInput(String.valueOf(secondsToWait));
|
bruteForceDetectionPage.form().setMaxWaitInput(String.valueOf(secondsToWait));
|
||||||
bruteForceDetectionPage.form().save();
|
bruteForceDetectionPage.form().save();
|
||||||
|
|
||||||
|
@ -190,7 +190,7 @@ public class SecurityDefensesTest extends AbstractRealmTest {
|
||||||
|
|
||||||
bruteForceDetectionPage.form().setProtectionEnabled(true);
|
bruteForceDetectionPage.form().setProtectionEnabled(true);
|
||||||
bruteForceDetectionPage.form().setMaxLoginFailures("2");
|
bruteForceDetectionPage.form().setMaxLoginFailures("2");
|
||||||
bruteForceDetectionPage.form().setFailureResetTimeSelect(SecurityDefenses.TimeSelectValues.SECONDS);
|
bruteForceDetectionPage.form().setFailureResetTimeSelect(BruteForceDetection.TimeSelectValues.SECONDS);
|
||||||
bruteForceDetectionPage.form().setFailureResetTimeInput(String.valueOf(secondsToWait));
|
bruteForceDetectionPage.form().setFailureResetTimeInput(String.valueOf(secondsToWait));
|
||||||
bruteForceDetectionPage.form().save();
|
bruteForceDetectionPage.form().save();
|
||||||
assertFlashMessageSuccess();
|
assertFlashMessageSuccess();
|
||||||
|
@ -223,7 +223,7 @@ public class SecurityDefensesTest extends AbstractRealmTest {
|
||||||
public void userUnlockTest() {
|
public void userUnlockTest() {
|
||||||
bruteForceDetectionPage.form().setProtectionEnabled(true);
|
bruteForceDetectionPage.form().setProtectionEnabled(true);
|
||||||
bruteForceDetectionPage.form().setMaxLoginFailures("1");
|
bruteForceDetectionPage.form().setMaxLoginFailures("1");
|
||||||
bruteForceDetectionPage.form().setWaitIncrementSelect(SecurityDefenses.TimeSelectValues.MINUTES);
|
bruteForceDetectionPage.form().setWaitIncrementSelect(BruteForceDetection.TimeSelectValues.MINUTES);
|
||||||
bruteForceDetectionPage.form().setWaitIncrementInput("10");
|
bruteForceDetectionPage.form().setWaitIncrementInput("10");
|
||||||
bruteForceDetectionPage.form().save();
|
bruteForceDetectionPage.form().save();
|
||||||
assertFlashMessageSuccess();
|
assertFlashMessageSuccess();
|
||||||
|
|
|
@ -1,28 +1,24 @@
|
||||||
package org.keycloak.testsuite.console.users;
|
package org.keycloak.testsuite.console.users;
|
||||||
|
|
||||||
import static org.jboss.arquillian.graphene.Graphene.waitGui;
|
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
|
|
||||||
import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
|
|
||||||
import org.keycloak.testsuite.auth.page.account.Account;
|
import org.keycloak.testsuite.auth.page.account.Account;
|
||||||
import org.keycloak.testsuite.auth.page.login.UpdateAccount;
|
import org.keycloak.testsuite.auth.page.login.UpdateAccount;
|
||||||
import org.keycloak.testsuite.auth.page.login.UpdatePassword;
|
import org.keycloak.testsuite.auth.page.login.UpdatePassword;
|
||||||
import org.keycloak.testsuite.console.page.authentication.RequiredActions;
|
import org.keycloak.testsuite.console.page.authentication.RequiredActions;
|
||||||
import org.keycloak.testsuite.console.page.users.UserAttributes;
|
import org.keycloak.testsuite.console.page.users.UserAttributes;
|
||||||
|
|
||||||
import static org.keycloak.testsuite.model.RequiredUserAction.TERMS_AND_CONDITIONS;
|
|
||||||
import static org.keycloak.testsuite.model.RequiredUserAction.UPDATE_PASSWORD;
|
|
||||||
import static org.keycloak.testsuite.model.RequiredUserAction.UPDATE_PROFILE;
|
|
||||||
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
|
|
||||||
|
|
||||||
import org.openqa.selenium.By;
|
import org.openqa.selenium.By;
|
||||||
import org.openqa.selenium.WebElement;
|
import org.openqa.selenium.WebElement;
|
||||||
import org.openqa.selenium.support.FindBy;
|
import org.openqa.selenium.support.FindBy;
|
||||||
|
|
||||||
|
import static org.jboss.arquillian.graphene.Graphene.waitGui;
|
||||||
|
import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
|
||||||
|
import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
|
||||||
|
import static org.keycloak.testsuite.model.RequiredUserAction.*;
|
||||||
|
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author tkyjovsk
|
* @author tkyjovsk
|
||||||
* @author mhajas
|
* @author mhajas
|
||||||
*/
|
*/
|
||||||
|
@ -124,7 +120,7 @@ public class RequiredUserActionsTest extends AbstractUserTest {
|
||||||
@Test
|
@Test
|
||||||
public void termsAndConditions() {
|
public void termsAndConditions() {
|
||||||
requiredActionsPage.navigateTo();
|
requiredActionsPage.navigateTo();
|
||||||
requiredActionsPage.clickTermsAndConditionEnabled();
|
requiredActionsPage.setTermsAndConditionEnabled(true);
|
||||||
|
|
||||||
manage().users();
|
manage().users();
|
||||||
usersPage.table().viewAllUsers();
|
usersPage.table().viewAllUsers();
|
||||||
|
@ -142,6 +138,4 @@ public class RequiredUserActionsTest extends AbstractUserTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -177,6 +177,7 @@ public class AdminAPITest {
|
||||||
if (appRep.isBearerOnly() != null) Assert.assertEquals(appRep.isBearerOnly(), storedApp.isBearerOnly());
|
if (appRep.isBearerOnly() != null) Assert.assertEquals(appRep.isBearerOnly(), storedApp.isBearerOnly());
|
||||||
if (appRep.isPublicClient() != null) Assert.assertEquals(appRep.isPublicClient(), storedApp.isPublicClient());
|
if (appRep.isPublicClient() != null) Assert.assertEquals(appRep.isPublicClient(), storedApp.isPublicClient());
|
||||||
if (appRep.isFullScopeAllowed() != null) Assert.assertEquals(appRep.isFullScopeAllowed(), storedApp.isFullScopeAllowed());
|
if (appRep.isFullScopeAllowed() != null) Assert.assertEquals(appRep.isFullScopeAllowed(), storedApp.isFullScopeAllowed());
|
||||||
|
if (appRep.getRootUrl() != null) Assert.assertEquals(appRep.getRootUrl(), storedApp.getRootUrl());
|
||||||
if (appRep.getAdminUrl() != null) Assert.assertEquals(appRep.getAdminUrl(), storedApp.getAdminUrl());
|
if (appRep.getAdminUrl() != null) Assert.assertEquals(appRep.getAdminUrl(), storedApp.getAdminUrl());
|
||||||
if (appRep.getBaseUrl() != null) Assert.assertEquals(appRep.getBaseUrl(), storedApp.getBaseUrl());
|
if (appRep.getBaseUrl() != null) Assert.assertEquals(appRep.getBaseUrl(), storedApp.getBaseUrl());
|
||||||
if (appRep.isSurrogateAuthRequired() != null) Assert.assertEquals(appRep.isSurrogateAuthRequired(), storedApp.isSurrogateAuthRequired());
|
if (appRep.isSurrogateAuthRequired() != null) Assert.assertEquals(appRep.isSurrogateAuthRequired(), storedApp.isSurrogateAuthRequired());
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
package org.keycloak.testsuite.admin;
|
package org.keycloak.testsuite.admin;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.representations.idm.RoleRepresentation;
|
import org.keycloak.representations.idm.RoleRepresentation;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
import javax.ws.rs.NotFoundException;
|
import javax.ws.rs.NotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -132,4 +137,35 @@ public class RealmTest extends AbstractClientTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void convertKeycloakClientDescription() throws IOException {
|
||||||
|
ClientRepresentation description = new ClientRepresentation();
|
||||||
|
description.setClientId("client-id");
|
||||||
|
description.setRedirectUris(Collections.singletonList("http://localhost"));
|
||||||
|
|
||||||
|
ClientRepresentation converted = realm.convertClientDescription(JsonSerialization.writeValueAsString(description));
|
||||||
|
assertEquals("client-id", converted.getClientId());
|
||||||
|
assertEquals("http://localhost", converted.getRedirectUris().get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void convertOIDCClientDescription() throws IOException {
|
||||||
|
String description = IOUtils.toString(getClass().getResourceAsStream("/client-descriptions/client-oidc.json"));
|
||||||
|
|
||||||
|
ClientRepresentation converted = realm.convertClientDescription(description);
|
||||||
|
assertEquals(36, converted.getClientId().length());
|
||||||
|
assertEquals(1, converted.getRedirectUris().size());
|
||||||
|
assertEquals("http://localhost", converted.getRedirectUris().get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void convertSAMLClientDescription() throws IOException {
|
||||||
|
String description = IOUtils.toString(getClass().getResourceAsStream("/client-descriptions/saml-entity-descriptor.xml"));
|
||||||
|
|
||||||
|
ClientRepresentation converted = realm.convertClientDescription(description);
|
||||||
|
assertEquals("loadbalancer-9.siroe.com", converted.getClientId());
|
||||||
|
assertEquals(1, converted.getRedirectUris().size());
|
||||||
|
assertEquals("https://LoadBalancer-9.siroe.com:3443/federation/Consumer/metaAlias/sp", converted.getRedirectUris().get(0));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ public class SamlAdapterTest {
|
||||||
initializeSamlSecuredWar("/keycloak-saml/bad-client-signed-post", "/bad-client-sales-post-sig", "bad-client-post-sig.war", classLoader);
|
initializeSamlSecuredWar("/keycloak-saml/bad-client-signed-post", "/bad-client-sales-post-sig", "bad-client-post-sig.war", classLoader);
|
||||||
initializeSamlSecuredWar("/keycloak-saml/bad-realm-signed-post", "/bad-realm-sales-post-sig", "bad-realm-post-sig.war", classLoader);
|
initializeSamlSecuredWar("/keycloak-saml/bad-realm-signed-post", "/bad-realm-sales-post-sig", "bad-realm-post-sig.war", classLoader);
|
||||||
initializeSamlSecuredWar("/keycloak-saml/encrypted-post", "/sales-post-enc", "post-enc.war", classLoader);
|
initializeSamlSecuredWar("/keycloak-saml/encrypted-post", "/sales-post-enc", "post-enc.war", classLoader);
|
||||||
SamlAdapterTestStrategy.uploadSP("http://localhost:8081/auth", this);
|
SamlAdapterTestStrategy.uploadSP("http://localhost:8081/auth");
|
||||||
server.getServer().deploy(createDeploymentInfo("employee.war", "/employee", SamlSPFacade.class));
|
server.getServer().deploy(createDeploymentInfo("employee.war", "/employee", SamlSPFacade.class));
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.keycloak.testsuite.keycloaksaml;
|
package org.keycloak.testsuite.keycloaksaml;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
|
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.ClassRule;
|
import org.junit.ClassRule;
|
||||||
|
@ -8,6 +9,8 @@ import org.junit.Test;
|
||||||
import org.junit.rules.ExternalResource;
|
import org.junit.rules.ExternalResource;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.adapters.saml.SamlPrincipal;
|
import org.keycloak.adapters.saml.SamlPrincipal;
|
||||||
|
import org.keycloak.admin.client.Keycloak;
|
||||||
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
|
@ -24,6 +27,7 @@ import org.keycloak.protocol.saml.mappers.HardcodedRole;
|
||||||
import org.keycloak.protocol.saml.mappers.RoleListMapper;
|
import org.keycloak.protocol.saml.mappers.RoleListMapper;
|
||||||
import org.keycloak.protocol.saml.mappers.RoleNameMapper;
|
import org.keycloak.protocol.saml.mappers.RoleNameMapper;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants;
|
import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
|
@ -34,6 +38,7 @@ import org.keycloak.testsuite.rule.AbstractKeycloakRule;
|
||||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||||
import org.keycloak.testsuite.rule.WebResource;
|
import org.keycloak.testsuite.rule.WebResource;
|
||||||
import org.keycloak.testsuite.rule.WebRule;
|
import org.keycloak.testsuite.rule.WebRule;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
import org.openqa.selenium.WebDriver;
|
import org.openqa.selenium.WebDriver;
|
||||||
|
|
||||||
import javax.ws.rs.client.Client;
|
import javax.ws.rs.client.Client;
|
||||||
|
@ -51,6 +56,8 @@ import java.io.InputStream;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
|
@ -106,9 +113,9 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
||||||
|
|
||||||
public void testPostSimpleLoginLogout() {
|
public void testPostSimpleLoginLogout() {
|
||||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post/");
|
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post/");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||||
loginPage.login("bburke", "password");
|
loginPage.login("bburke", "password");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post/");
|
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post/");
|
||||||
System.out.println(driver.getPageSource());
|
System.out.println(driver.getPageSource());
|
||||||
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
||||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post?GLO=true");
|
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post?GLO=true");
|
||||||
|
@ -117,9 +124,9 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
||||||
|
|
||||||
public void testPostSimpleUnauthorized(CheckAuthError error) {
|
public void testPostSimpleUnauthorized(CheckAuthError error) {
|
||||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post/");
|
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post/");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||||
loginPage.login("unauthorized", "password");
|
loginPage.login("unauthorized", "password");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post/");
|
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post/");
|
||||||
System.out.println(driver.getPageSource());
|
System.out.println(driver.getPageSource());
|
||||||
error.check(driver);
|
error.check(driver);
|
||||||
}
|
}
|
||||||
|
@ -127,7 +134,7 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
||||||
public void testPostSimpleLoginLogoutIdpInitiated() {
|
public void testPostSimpleLoginLogoutIdpInitiated() {
|
||||||
driver.navigate().to(AUTH_SERVER_URL + "/realms/demo/protocol/saml/clients/sales-post");
|
driver.navigate().to(AUTH_SERVER_URL + "/realms/demo/protocol/saml/clients/sales-post");
|
||||||
loginPage.login("bburke", "password");
|
loginPage.login("bburke", "password");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post/");
|
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post/");
|
||||||
System.out.println(driver.getPageSource());
|
System.out.println(driver.getPageSource());
|
||||||
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
||||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post?GLO=true");
|
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post?GLO=true");
|
||||||
|
@ -136,9 +143,9 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
||||||
|
|
||||||
public void testPostSignedLoginLogout() {
|
public void testPostSignedLoginLogout() {
|
||||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig/");
|
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig/");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||||
loginPage.login("bburke", "password");
|
loginPage.login("bburke", "password");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-sig/");
|
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-sig/");
|
||||||
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
||||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig?GLO=true");
|
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig?GLO=true");
|
||||||
checkLoggedOut(APP_SERVER_BASE_URL + "/sales-post-sig/");
|
checkLoggedOut(APP_SERVER_BASE_URL + "/sales-post-sig/");
|
||||||
|
@ -146,9 +153,9 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
||||||
}
|
}
|
||||||
public void testPostSignedLoginLogoutTransientNameID() {
|
public void testPostSignedLoginLogoutTransientNameID() {
|
||||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig-transient/");
|
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig-transient/");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||||
loginPage.login("bburke", "password");
|
loginPage.login("bburke", "password");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-sig-transient/");
|
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-sig-transient/");
|
||||||
System.out.println(driver.getPageSource());
|
System.out.println(driver.getPageSource());
|
||||||
Assert.assertFalse(driver.getPageSource().contains("bburke"));
|
Assert.assertFalse(driver.getPageSource().contains("bburke"));
|
||||||
Assert.assertTrue(driver.getPageSource().contains("principal=G-"));
|
Assert.assertTrue(driver.getPageSource().contains("principal=G-"));
|
||||||
|
@ -158,9 +165,9 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
||||||
}
|
}
|
||||||
public void testPostSignedLoginLogoutPersistentNameID() {
|
public void testPostSignedLoginLogoutPersistentNameID() {
|
||||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig-persistent/");
|
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig-persistent/");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||||
loginPage.login("bburke", "password");
|
loginPage.login("bburke", "password");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-sig-persistent/");
|
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-sig-persistent/");
|
||||||
System.out.println(driver.getPageSource());
|
System.out.println(driver.getPageSource());
|
||||||
Assert.assertFalse(driver.getPageSource().contains("bburke"));
|
Assert.assertFalse(driver.getPageSource().contains("bburke"));
|
||||||
Assert.assertTrue(driver.getPageSource().contains("principal=G-"));
|
Assert.assertTrue(driver.getPageSource().contains("principal=G-"));
|
||||||
|
@ -170,9 +177,9 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
||||||
}
|
}
|
||||||
public void testPostSignedLoginLogoutEmailNameID() {
|
public void testPostSignedLoginLogoutEmailNameID() {
|
||||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig-email/");
|
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig-email/");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||||
loginPage.login("bburke", "password");
|
loginPage.login("bburke", "password");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-sig-email/");
|
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-sig-email/");
|
||||||
System.out.println(driver.getPageSource());
|
System.out.println(driver.getPageSource());
|
||||||
Assert.assertTrue(driver.getPageSource().contains("principal=bburke@redhat.com"));
|
Assert.assertTrue(driver.getPageSource().contains("principal=bburke@redhat.com"));
|
||||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig-email?GLO=true");
|
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig-email?GLO=true");
|
||||||
|
@ -188,8 +195,8 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
||||||
Assert.assertTrue(driver.getCurrentUrl().startsWith(AUTH_SERVER_URL + "/realms/demo/protocol/saml"));
|
Assert.assertTrue(driver.getCurrentUrl().startsWith(AUTH_SERVER_URL + "/realms/demo/protocol/saml"));
|
||||||
System.out.println(driver.getCurrentUrl());
|
System.out.println(driver.getCurrentUrl());
|
||||||
loginPage.login("bburke", "password");
|
loginPage.login("bburke", "password");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee/");
|
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee/");
|
||||||
Assert.assertEquals(SamlSPFacade.sentRelayState, SamlSPFacade.RELAY_STATE);
|
assertEquals(SamlSPFacade.sentRelayState, SamlSPFacade.RELAY_STATE);
|
||||||
Assert.assertNotNull(SamlSPFacade.samlResponse);
|
Assert.assertNotNull(SamlSPFacade.samlResponse);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -206,13 +213,13 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
||||||
requiredRoles.add("user");
|
requiredRoles.add("user");
|
||||||
SendUsernameServlet.checkRoles = requiredRoles;
|
SendUsernameServlet.checkRoles = requiredRoles;
|
||||||
loginPage.login("bburke", "password");
|
loginPage.login("bburke", "password");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee2/");
|
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee2/");
|
||||||
SendUsernameServlet.checkRoles = null;
|
SendUsernameServlet.checkRoles = null;
|
||||||
SamlPrincipal principal = (SamlPrincipal) SendUsernameServlet.sentPrincipal;
|
SamlPrincipal principal = (SamlPrincipal) SendUsernameServlet.sentPrincipal;
|
||||||
Assert.assertNotNull(principal);
|
Assert.assertNotNull(principal);
|
||||||
Assert.assertEquals("bburke@redhat.com", principal.getAttribute(X500SAMLProfileConstants.EMAIL.get()));
|
assertEquals("bburke@redhat.com", principal.getAttribute(X500SAMLProfileConstants.EMAIL.get()));
|
||||||
Assert.assertEquals("bburke@redhat.com", principal.getFriendlyAttribute("email"));
|
assertEquals("bburke@redhat.com", principal.getFriendlyAttribute("email"));
|
||||||
Assert.assertEquals("617", principal.getAttribute("phone"));
|
assertEquals("617", principal.getAttribute("phone"));
|
||||||
Assert.assertNull(principal.getFriendlyAttribute("phone"));
|
Assert.assertNull(principal.getFriendlyAttribute("phone"));
|
||||||
driver.navigate().to(APP_SERVER_BASE_URL + "/employee2/?GLO=true");
|
driver.navigate().to(APP_SERVER_BASE_URL + "/employee2/?GLO=true");
|
||||||
checkLoggedOut(APP_SERVER_BASE_URL + "/employee2/");
|
checkLoggedOut(APP_SERVER_BASE_URL + "/employee2/");
|
||||||
|
@ -252,11 +259,11 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
||||||
requiredRoles.add("pee-on");
|
requiredRoles.add("pee-on");
|
||||||
SendUsernameServlet.checkRoles = requiredRoles;
|
SendUsernameServlet.checkRoles = requiredRoles;
|
||||||
loginPage.login("bburke", "password");
|
loginPage.login("bburke", "password");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee2/");
|
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee2/");
|
||||||
SendUsernameServlet.checkRoles = null;
|
SendUsernameServlet.checkRoles = null;
|
||||||
SamlPrincipal principal = (SamlPrincipal) SendUsernameServlet.sentPrincipal;
|
SamlPrincipal principal = (SamlPrincipal) SendUsernameServlet.sentPrincipal;
|
||||||
Assert.assertNotNull(principal);
|
Assert.assertNotNull(principal);
|
||||||
Assert.assertEquals("hard", principal.getAttribute("hardcoded-attribute"));
|
assertEquals("hard", principal.getAttribute("hardcoded-attribute"));
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -266,7 +273,7 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
||||||
driver.navigate().to(APP_SERVER_BASE_URL + "/employee-sig/");
|
driver.navigate().to(APP_SERVER_BASE_URL + "/employee-sig/");
|
||||||
Assert.assertTrue(driver.getCurrentUrl().startsWith(AUTH_SERVER_URL + "/realms/demo/protocol/saml"));
|
Assert.assertTrue(driver.getCurrentUrl().startsWith(AUTH_SERVER_URL + "/realms/demo/protocol/saml"));
|
||||||
loginPage.login("bburke", "password");
|
loginPage.login("bburke", "password");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee-sig/");
|
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee-sig/");
|
||||||
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
||||||
driver.navigate().to(APP_SERVER_BASE_URL + "/employee-sig?GLO=true");
|
driver.navigate().to(APP_SERVER_BASE_URL + "/employee-sig?GLO=true");
|
||||||
checkLoggedOut(APP_SERVER_BASE_URL + "/employee-sig/");
|
checkLoggedOut(APP_SERVER_BASE_URL + "/employee-sig/");
|
||||||
|
@ -277,7 +284,7 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
||||||
driver.navigate().to(APP_SERVER_BASE_URL + "/employee-sig-front/");
|
driver.navigate().to(APP_SERVER_BASE_URL + "/employee-sig-front/");
|
||||||
Assert.assertTrue(driver.getCurrentUrl().startsWith(AUTH_SERVER_URL + "/realms/demo/protocol/saml"));
|
Assert.assertTrue(driver.getCurrentUrl().startsWith(AUTH_SERVER_URL + "/realms/demo/protocol/saml"));
|
||||||
loginPage.login("bburke", "password");
|
loginPage.login("bburke", "password");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee-sig-front/");
|
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee-sig-front/");
|
||||||
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
||||||
driver.navigate().to(APP_SERVER_BASE_URL + "/employee-sig-front?GLO=true");
|
driver.navigate().to(APP_SERVER_BASE_URL + "/employee-sig-front?GLO=true");
|
||||||
checkLoggedOut(APP_SERVER_BASE_URL + "/employee-sig-front/");
|
checkLoggedOut(APP_SERVER_BASE_URL + "/employee-sig-front/");
|
||||||
|
@ -291,19 +298,19 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
||||||
Assert.assertTrue(driver.getCurrentUrl().startsWith(AUTH_SERVER_URL + "/realms/demo/protocol/saml"));
|
Assert.assertTrue(driver.getCurrentUrl().startsWith(AUTH_SERVER_URL + "/realms/demo/protocol/saml"));
|
||||||
System.out.println("login to form");
|
System.out.println("login to form");
|
||||||
loginPage.login("bburke", "password");
|
loginPage.login("bburke", "password");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee-sig/");
|
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee-sig/");
|
||||||
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
||||||
|
|
||||||
// visit 2nd app
|
// visit 2nd app
|
||||||
System.out.println("visit 2nd app ");
|
System.out.println("visit 2nd app ");
|
||||||
driver.navigate().to(APP_SERVER_BASE_URL + "/employee-sig-front/");
|
driver.navigate().to(APP_SERVER_BASE_URL + "/employee-sig-front/");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee-sig-front/");
|
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee-sig-front/");
|
||||||
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
||||||
|
|
||||||
// visit 3rd app
|
// visit 3rd app
|
||||||
System.out.println("visit 3rd app ");
|
System.out.println("visit 3rd app ");
|
||||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig/");
|
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig/");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-sig/");
|
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-sig/");
|
||||||
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
||||||
|
|
||||||
// logout of first app
|
// logout of first app
|
||||||
|
@ -320,9 +327,9 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
||||||
|
|
||||||
public void testPostEncryptedLoginLogout() {
|
public void testPostEncryptedLoginLogout() {
|
||||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-enc/");
|
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-enc/");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||||
loginPage.login("bburke", "password");
|
loginPage.login("bburke", "password");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-enc/");
|
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-enc/");
|
||||||
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
||||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-enc?GLO=true");
|
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-enc?GLO=true");
|
||||||
checkLoggedOut(APP_SERVER_BASE_URL + "/sales-post-enc/");
|
checkLoggedOut(APP_SERVER_BASE_URL + "/sales-post-enc/");
|
||||||
|
@ -330,8 +337,8 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
||||||
}
|
}
|
||||||
public void testPostBadClientSignature() {
|
public void testPostBadClientSignature() {
|
||||||
driver.navigate().to(APP_SERVER_BASE_URL + "/bad-client-sales-post-sig/");
|
driver.navigate().to(APP_SERVER_BASE_URL + "/bad-client-sales-post-sig/");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||||
Assert.assertEquals(driver.getTitle(), "We're sorry...");
|
assertEquals(driver.getTitle(), "We're sorry...");
|
||||||
|
|
||||||
}
|
}
|
||||||
public static interface CheckAuthError {
|
public static interface CheckAuthError {
|
||||||
|
@ -340,39 +347,19 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
||||||
|
|
||||||
public void testPostBadRealmSignature(CheckAuthError error) {
|
public void testPostBadRealmSignature(CheckAuthError error) {
|
||||||
driver.navigate().to(APP_SERVER_BASE_URL + "/bad-realm-sales-post-sig/");
|
driver.navigate().to(APP_SERVER_BASE_URL + "/bad-realm-sales-post-sig/");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||||
loginPage.login("bburke", "password");
|
loginPage.login("bburke", "password");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/bad-realm-sales-post-sig/");
|
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/bad-realm-sales-post-sig/");
|
||||||
System.out.println(driver.getPageSource());
|
System.out.println(driver.getPageSource());
|
||||||
error.check(driver);
|
error.check(driver);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String createToken(String AUTH_SERVER_URL, AbstractKeycloakRule keycloakRule) {
|
|
||||||
KeycloakSession session = keycloakRule.startSession();
|
|
||||||
try {
|
|
||||||
RealmManager manager = new RealmManager(session);
|
|
||||||
|
|
||||||
RealmModel adminRealm = manager.getRealm(Config.getAdminRealm());
|
|
||||||
ClientModel adminConsole = adminRealm.getClientByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID);
|
|
||||||
TokenManager tm = new TokenManager();
|
|
||||||
UserModel admin = session.users().getUserByUsername("admin", adminRealm);
|
|
||||||
ClientSessionModel clientSession = session.sessions().createClientSession(adminRealm, adminConsole);
|
|
||||||
clientSession.setNote(OIDCLoginProtocol.ISSUER, AUTH_SERVER_URL + "/realms/master");
|
|
||||||
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false, null, null);
|
|
||||||
AccessToken token = tm.createClientAccessToken(session, tm.getAccess(null, true, adminConsole, admin), adminRealm, adminConsole, admin, userSession, clientSession);
|
|
||||||
return tm.encodeToken(adminRealm, token);
|
|
||||||
} finally {
|
|
||||||
keycloakRule.stopSession(session, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void testMetadataPostSignedLoginLogout() throws Exception {
|
public void testMetadataPostSignedLoginLogout() throws Exception {
|
||||||
|
|
||||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-metadata/");
|
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-metadata/");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||||
loginPage.login("bburke", "password");
|
loginPage.login("bburke", "password");
|
||||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-metadata/");
|
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-metadata/");
|
||||||
String pageSource = driver.getPageSource();
|
String pageSource = driver.getPageSource();
|
||||||
Assert.assertTrue(pageSource.contains("bburke"));
|
Assert.assertTrue(pageSource.contains("bburke"));
|
||||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-metadata?GLO=true");
|
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-metadata?GLO=true");
|
||||||
|
@ -380,30 +367,21 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void uploadSP(String AUTH_SERVER_URL, AbstractKeycloakRule keycloakRule) {
|
public static void uploadSP(String AUTH_SERVER_URL) {
|
||||||
String token = createToken(AUTH_SERVER_URL, keycloakRule);
|
try {
|
||||||
final String authHeader = "Bearer " + token;
|
Keycloak keycloak = Keycloak.getInstance(AUTH_SERVER_URL, "master", "admin", "admin", Constants.ADMIN_CONSOLE_CLIENT_ID, null);
|
||||||
ClientRequestFilter authFilter = new ClientRequestFilter() {
|
RealmResource admin = keycloak.realm("demo");
|
||||||
@Override
|
|
||||||
public void filter(ClientRequestContext requestContext) throws IOException {
|
|
||||||
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Client client = ClientBuilder.newBuilder().register(authFilter).build();
|
|
||||||
UriBuilder authBase = UriBuilder.fromUri(AUTH_SERVER_URL + "");
|
|
||||||
WebTarget adminRealms = client.target(AdminRoot.realmsUrl(authBase));
|
|
||||||
|
|
||||||
|
admin.toRepresentation();
|
||||||
|
|
||||||
MultipartFormDataOutput formData = new MultipartFormDataOutput();
|
ClientRepresentation clientRep = admin.convertClientDescription(IOUtils.toString(SamlAdapterTestStrategy.class.getResourceAsStream("/keycloak-saml/sp-metadata.xml")));
|
||||||
InputStream is = SamlAdapterTestStrategy.class.getResourceAsStream("/keycloak-saml/sp-metadata.xml");
|
Response response = admin.clients().create(clientRep);
|
||||||
Assert.assertNotNull(is);
|
|
||||||
formData.addFormData("file", is, MediaType.APPLICATION_XML_TYPE);
|
|
||||||
|
|
||||||
WebTarget upload = adminRealms.path("demo/client-importers/saml2-entity-descriptor/upload");
|
assertEquals(201, response.getStatus());
|
||||||
System.out.println(upload.getUri());
|
|
||||||
Response response = upload.request().post(Entity.entity(formData, MediaType.MULTIPART_FORM_DATA));
|
keycloak.close();
|
||||||
Assert.assertEquals(204, response.getStatus());
|
} catch (IOException e) {
|
||||||
response.close();
|
throw new RuntimeException(e);
|
||||||
client.close();
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
package org.keycloak.testsuite.saml;
|
package org.keycloak.testsuite.saml;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
|
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.ClassRule;
|
import org.junit.ClassRule;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.admin.client.Keycloak;
|
||||||
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
|
@ -22,9 +25,11 @@ import org.keycloak.protocol.saml.mappers.HardcodedRole;
|
||||||
import org.keycloak.protocol.saml.mappers.RoleListMapper;
|
import org.keycloak.protocol.saml.mappers.RoleListMapper;
|
||||||
import org.keycloak.protocol.saml.mappers.RoleNameMapper;
|
import org.keycloak.protocol.saml.mappers.RoleNameMapper;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.services.resources.admin.AdminRoot;
|
import org.keycloak.services.resources.admin.AdminRoot;
|
||||||
import org.keycloak.testsuite.pages.LoginPage;
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
|
import org.keycloak.testsuite.rule.AbstractKeycloakRule;
|
||||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||||
import org.keycloak.testsuite.rule.WebResource;
|
import org.keycloak.testsuite.rule.WebResource;
|
||||||
import org.keycloak.testsuite.rule.WebRule;
|
import org.keycloak.testsuite.rule.WebRule;
|
||||||
|
@ -56,6 +61,8 @@ import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
|
@ -480,30 +487,21 @@ public class SamlBindingTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void uploadSP() {
|
public static void uploadSP() {
|
||||||
String token = createToken();
|
try {
|
||||||
final String authHeader = "Bearer " + token;
|
Keycloak keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CONSOLE_CLIENT_ID, null);
|
||||||
ClientRequestFilter authFilter = new ClientRequestFilter() {
|
RealmResource admin = keycloak.realm("demo");
|
||||||
@Override
|
|
||||||
public void filter(ClientRequestContext requestContext) throws IOException {
|
|
||||||
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Client client = ClientBuilder.newBuilder().register(authFilter).build();
|
|
||||||
UriBuilder authBase = UriBuilder.fromUri("http://localhost:8081/auth");
|
|
||||||
WebTarget adminRealms = client.target(AdminRoot.realmsUrl(authBase));
|
|
||||||
|
|
||||||
|
admin.toRepresentation();
|
||||||
|
|
||||||
MultipartFormDataOutput formData = new MultipartFormDataOutput();
|
ClientRepresentation clientRep = admin.convertClientDescription(IOUtils.toString(SamlBindingTest.class.getResourceAsStream("/saml/sp-metadata.xml")));
|
||||||
InputStream is = SamlBindingTest.class.getResourceAsStream("/saml/sp-metadata.xml");
|
Response response = admin.clients().create(clientRep);
|
||||||
Assert.assertNotNull(is);
|
|
||||||
formData.addFormData("file", is, MediaType.APPLICATION_XML_TYPE);
|
|
||||||
|
|
||||||
WebTarget upload = adminRealms.path("demo/client-importers/saml2-entity-descriptor/upload");
|
assertEquals(201, response.getStatus());
|
||||||
System.out.println(upload.getUri());
|
|
||||||
Response response = upload.request().post(Entity.entity(formData, MediaType.MULTIPART_FORM_DATA));
|
keycloak.close();
|
||||||
Assert.assertEquals(204, response.getStatus());
|
} catch (IOException e) {
|
||||||
response.close();
|
throw new RuntimeException(e);
|
||||||
client.close();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"client_name": "Name",
|
||||||
|
"redirect_uris": [
|
||||||
|
"http://localhost"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
<EntityDescriptor
|
||||||
|
xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
|
||||||
|
entityID="loadbalancer-9.siroe.com">
|
||||||
|
<SPSSODescriptor
|
||||||
|
AuthnRequestsSigned="false"
|
||||||
|
WantAssertionsSigned="false"
|
||||||
|
protocolSupportEnumeration=
|
||||||
|
"urn:oasis:names:tc:SAML:2.0:protocol">
|
||||||
|
<KeyDescriptor use="signing">
|
||||||
|
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
|
||||||
|
<X509Data>
|
||||||
|
<X509Certificate>
|
||||||
|
MIICYDCCAgqgAwIBAgICBoowDQYJKoZIhvcNAQEEBQAwgZIxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
|
||||||
|
EwpDYWxpZm9ybmlhMRQwEgYDVQQHEwtTYW50YSBDbGFyYTEeMBwGA1UEChMVU3VuIE1pY3Jvc3lz
|
||||||
|
dGVtcyBJbmMuMRowGAYDVQQLExFJZGVudGl0eSBTZXJ2aWNlczEcMBoGA1UEAxMTQ2VydGlmaWNh
|
||||||
|
dGUgTWFuYWdlcjAeFw0wNjExMDIxOTExMzRaFw0xMDA3MjkxOTExMzRaMDcxEjAQBgNVBAoTCXNp
|
||||||
|
cm9lLmNvbTEhMB8GA1UEAxMYbG9hZGJhbGFuY2VyLTkuc2lyb2UuY29tMIGfMA0GCSqGSIb3DQEB
|
||||||
|
AQUAA4GNADCBiQKBgQCjOwa5qoaUuVnknqf5pdgAJSEoWlvx/jnUYbkSDpXLzraEiy2UhvwpoBgB
|
||||||
|
EeTSUaPPBvboCItchakPI6Z/aFdH3Wmjuij9XD8r1C+q//7sUO0IGn0ORycddHhoo0aSdnnxGf9V
|
||||||
|
tREaqKm9dJ7Yn7kQHjo2eryMgYxtr/Z5Il5F+wIDAQABo2AwXjARBglghkgBhvhCAQEEBAMCBkAw
|
||||||
|
DgYDVR0PAQH/BAQDAgTwMB8GA1UdIwQYMBaAFDugITflTCfsWyNLTXDl7cMDUKuuMBgGA1UdEQQR
|
||||||
|
MA+BDW1hbGxhQHN1bi5jb20wDQYJKoZIhvcNAQEEBQADQQB/6DOB6sRqCZu2OenM9eQR0gube85e
|
||||||
|
nTTxU4a7x1naFxzYXK1iQ1vMARKMjDb19QEJIEJKZlDK4uS7yMlf1nFS
|
||||||
|
</X509Certificate>
|
||||||
|
</X509Data>
|
||||||
|
</KeyInfo>
|
||||||
|
</KeyDescriptor>
|
||||||
|
<KeyDescriptor use="encryption">
|
||||||
|
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
|
||||||
|
<X509Data>
|
||||||
|
<X509Certificate>
|
||||||
|
MIICTDCCAfagAwIBAgICBo8wDQYJKoZIhvcNAQEEBQAwgZIxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
|
||||||
|
EwpDYWxpZm9ybmlhMRQwEgYDVQQHEwtTYW50YSBDbGFyYTEeMBwGA1UEChMVU3VuIE1pY3Jvc3lz
|
||||||
|
dGVtcyBJbmMuMRowGAYDVQQLExFJZGVudGl0eSBTZXJ2aWNlczEcMBoGA1UEAxMTQ2VydGlmaWNh
|
||||||
|
dGUgTWFuYWdlcjAeFw0wNjExMDcyMzU2MTdaFw0xMDA4MDMyMzU2MTdaMCMxITAfBgNVBAMTGGxv
|
||||||
|
YWRiYWxhbmNlci05LnNpcm9lLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAw574iRU6
|
||||||
|
HsSO4LXW/OGTXyfsbGv6XRVOoy3v+J1pZ51KKejcDjDJXNkKGn3/356AwIaqbcymWd59T0zSqYfR
|
||||||
|
Hn+45uyjYxRBmVJseLpVnOXLub9jsjULfGx0yjH4w+KsZSZCXatoCHbj/RJtkzuZY6V9to/hkH3S
|
||||||
|
InQB4a3UAgMCAwEAAaNgMF4wEQYJYIZIAYb4QgEBBAQDAgZAMA4GA1UdDwEB/wQEAwIE8DAfBgNV
|
||||||
|
HSMEGDAWgBQ7oCE35Uwn7FsjS01w5e3DA1CrrjAYBgNVHREEETAPgQ1tYWxsYUBzdW4uY29tMA0G
|
||||||
|
CSqGSIb3DQEBBAUAA0EAMlbfBg/ff0Xkv4DOR5LEqmfTZKqgdlD81cXynfzlF7XfnOqI6hPIA90I
|
||||||
|
x5Ql0ejivIJAYcMGUyA+/YwJg2FGoA==
|
||||||
|
</X509Certificate>
|
||||||
|
</X509Data>
|
||||||
|
</KeyInfo>
|
||||||
|
<EncryptionMethod Algorithm=
|
||||||
|
"https://www.w3.org/2001/04/xmlenc#aes128-cbc">
|
||||||
|
<KeySize xmlns="https://www.w3.org/2001/04/xmlenc#">128</KeySize>
|
||||||
|
</EncryptionMethod>
|
||||||
|
</KeyDescriptor>
|
||||||
|
<SingleLogoutService
|
||||||
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
||||||
|
Location="https://LoadBalancer-9.siroe.com:3443/federation/SPSloRedirect/metaAlias/sp"
|
||||||
|
ResponseLocation="https://LoadBalancer-9.siroe.com:3443/federation/SPSloRedirect/metaAlias/sp"/>
|
||||||
|
<SingleLogoutService
|
||||||
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
|
||||||
|
Location="https://LoadBalancer-9.siroe.com:3443/federation/SPSloSoap/metaAlias/sp"/>
|
||||||
|
<ManageNameIDService
|
||||||
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
||||||
|
Location="https://LoadBalancer-9.siroe.com:3443/federation/SPMniRedirect/metaAlias/sp"
|
||||||
|
ResponseLocation="https://LoadBalancer-9.siroe.com:3443/federation/SPMniRedirect/metaAlias/sp"/>
|
||||||
|
<ManageNameIDService
|
||||||
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
|
||||||
|
Location="https://LoadBalancer-9.siroe.com:3443/federation/SPMniSoap/metaAlias/sp"
|
||||||
|
ResponseLocation="https://LoadBalancer-9.siroe.com:3443/federation/SPMniSoap/metaAlias/sp"/>
|
||||||
|
<NameIDFormat>
|
||||||
|
urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
|
||||||
|
</NameIDFormat>
|
||||||
|
<NameIDFormat>
|
||||||
|
urn:oasis:names:tc:SAML:2.0:nameid-format:transient
|
||||||
|
</NameIDFormat>
|
||||||
|
<AssertionConsumerService
|
||||||
|
isDefault="true"
|
||||||
|
index="0"
|
||||||
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
|
||||||
|
Location="https://LoadBalancer-9.siroe.com:3443/federation/Consumer/metaAlias/sp"/>
|
||||||
|
<AssertionConsumerService
|
||||||
|
index="1"
|
||||||
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
||||||
|
Location="https://LoadBalancer-9.siroe.com:3443/federation/Consumer/metaAlias/sp"/>
|
||||||
|
</SPSSODescriptor>
|
||||||
|
</EntityDescriptor>
|
|
@ -30,7 +30,6 @@ import org.junit.Test;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.testsuite.adapter.AdapterTestStrategy;
|
|
||||||
import org.keycloak.testsuite.keycloaksaml.SamlAdapterTestStrategy;
|
import org.keycloak.testsuite.keycloaksaml.SamlAdapterTestStrategy;
|
||||||
import org.keycloak.testsuite.rule.AbstractKeycloakRule;
|
import org.keycloak.testsuite.rule.AbstractKeycloakRule;
|
||||||
import org.openqa.selenium.WebDriver;
|
import org.openqa.selenium.WebDriver;
|
||||||
|
@ -73,7 +72,7 @@ public class TomcatSamlTest {
|
||||||
tomcat.deploySaml("/bad-client-sales-post-sig", "bad-client-signed-post");
|
tomcat.deploySaml("/bad-client-sales-post-sig", "bad-client-signed-post");
|
||||||
tomcat.deploySaml("/bad-realm-sales-post-sig", "bad-realm-signed-post");
|
tomcat.deploySaml("/bad-realm-sales-post-sig", "bad-realm-signed-post");
|
||||||
tomcat.deploySaml("/sales-post-enc", "encrypted-post");
|
tomcat.deploySaml("/sales-post-enc", "encrypted-post");
|
||||||
SamlAdapterTestStrategy.uploadSP("http://localhost:8081/auth", keycloakRule);
|
SamlAdapterTestStrategy.uploadSP("http://localhost:8081/auth");
|
||||||
|
|
||||||
|
|
||||||
tomcat.start();
|
tomcat.start();
|
||||||
|
|
Loading…
Reference in a new issue