Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
ec1ba40f4a
151 changed files with 2061 additions and 871 deletions
|
@ -73,6 +73,12 @@
|
|||
</column>
|
||||
<column name="RETRIEVE_TOKEN" type="BOOLEAN" defaultValueBoolean="false"/>
|
||||
</createTable>
|
||||
<createTable tableName="REALM_SUPPORTED_LOCALES">
|
||||
<column name="REALM_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="VARCHAR(255)"/>
|
||||
</createTable>
|
||||
<addColumn tableName="CLIENT">
|
||||
<column name="FRONTCHANNEL_LOGOUT" type="BOOLEAN" defaultValueBoolean="false"/>
|
||||
</addColumn>
|
||||
|
@ -88,11 +94,14 @@
|
|||
<addForeignKeyConstraint baseColumnNames="PROTOCOL_MAPPER_ID" baseTableName="PROTOCOL_MAPPER_CONFIG" constraintName="FK_PMConfig" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="PROTOCOL_MAPPER"/>
|
||||
<addForeignKeyConstraint baseColumnNames="IDENTITY_PROVIDER_ID" baseTableName="CLIENT_IDENTITY_PROVIDER_MAPPING" constraintName="FK_7CELWNIBJI49AVXSRTUF6XJ12" referencedColumnNames="INTERNAL_ID" referencedTableName="IDENTITY_PROVIDER"/>
|
||||
<addForeignKeyConstraint baseColumnNames="CLIENT_ID" baseTableName="CLIENT_IDENTITY_PROVIDER_MAPPING" constraintName="FK_56ELWNIBJI49AVXSRTUF6XJ23" referencedColumnNames="ID" referencedTableName="CLIENT"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_SUPPORTED_LOCALES" constraintName="FK_SUPPORTED_LOCALES_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
<addUniqueConstraint columnNames="PROVIDER_NONIMAL_ID" constraintName="UK_2DAELWNIBJI49AVXSRTUF6XJ33" tableName="IDENTITY_PROVIDER"/>
|
||||
<addUniqueConstraint columnNames="IDENTITY_PROVIDER_ID,CLIENT_ID" constraintName="UK_7CAELWNIBJI49AVXSRTUF6XJ12" tableName="CLIENT_IDENTITY_PROVIDER_MAPPING"/>
|
||||
|
||||
<addColumn tableName="REALM">
|
||||
<column name="LOGIN_LIFESPAN" type="INT"/>
|
||||
<column name="INTERNATIONALIZATION_ENABLED" type="BOOLEAN" defaultValueBoolean="false"/>
|
||||
<column name="DEFAULT_LOCALE" type="VARCHAR(255)" />
|
||||
<column name="REGISTRATION_EMAIL_AS_USERNAME" type="BOOLEAN" defaultValueBoolean="false"/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
|
|
@ -18,8 +18,7 @@ public class AbstractOAuthClient {
|
|||
protected String clientId;
|
||||
protected Map<String, String> credentials;
|
||||
protected String authUrl;
|
||||
protected String codeUrl;
|
||||
protected String refreshUrl;
|
||||
protected String tokenUrl;
|
||||
protected RelativeUrlsUsed relativeUrlsUsed;
|
||||
protected String scope;
|
||||
protected String stateCookieName = OAUTH_TOKEN_REQUEST_STATE;
|
||||
|
@ -54,20 +53,12 @@ public class AbstractOAuthClient {
|
|||
this.authUrl = authUrl;
|
||||
}
|
||||
|
||||
public String getCodeUrl() {
|
||||
return codeUrl;
|
||||
public String getTokenUrl() {
|
||||
return tokenUrl;
|
||||
}
|
||||
|
||||
public void setCodeUrl(String codeUrl) {
|
||||
this.codeUrl = codeUrl;
|
||||
}
|
||||
|
||||
public String getRefreshUrl() {
|
||||
return refreshUrl;
|
||||
}
|
||||
|
||||
public void setRefreshUrl(String refreshUrl) {
|
||||
this.refreshUrl = refreshUrl;
|
||||
public void setTokenUrl(String tokenUrl) {
|
||||
this.tokenUrl = tokenUrl;
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
|
|
|
@ -6,11 +6,9 @@ package org.keycloak.constants;
|
|||
*/
|
||||
public interface ServiceUrlConstants {
|
||||
|
||||
public static final String TOKEN_SERVICE_LOGIN_PATH = "/realms/{realm-name}/protocol/openid-connect/login";
|
||||
public static final String TOKEN_SERVICE_ACCESS_CODE_PATH = "/realms/{realm-name}/protocol/openid-connect/access/codes";
|
||||
public static final String TOKEN_SERVICE_REFRESH_PATH = "/realms/{realm-name}/protocol/openid-connect/refresh";
|
||||
public static final String AUTH_PATH = "/realms/{realm-name}/protocol/openid-connect/auth";
|
||||
public static final String TOKEN_PATH = "/realms/{realm-name}/protocol/openid-connect/token";
|
||||
public static final String TOKEN_SERVICE_LOGOUT_PATH = "/realms/{realm-name}/protocol/openid-connect/logout";
|
||||
public static final String TOKEN_SERVICE_DIRECT_GRANT_PATH = "/realms/{realm-name}/protocol/openid-connect/grants/access";
|
||||
public static final String ACCOUNT_SERVICE_PATH = "/realms/{realm-name}/account";
|
||||
public static final String REALM_INFO_PATH = "/realms/{realm-name}";
|
||||
public static final String CLIENTS_MANAGEMENT_REGISTER_NODE_PATH = "/realms/{realm-name}/clients-managements/register-node";
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
package org.keycloak.representations.idm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -68,6 +64,10 @@ public class RealmRepresentation {
|
|||
private List<IdentityProviderRepresentation> identityProviders;
|
||||
private List<ProtocolMapperRepresentation> protocolMappers;
|
||||
private Boolean identityFederationEnabled;
|
||||
protected Boolean internationalizationEnabled;
|
||||
protected Set<String> supportedLocales;
|
||||
protected String defaultLocale;
|
||||
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
|
@ -513,4 +513,31 @@ public class RealmRepresentation {
|
|||
public void setProtocolMappers(List<ProtocolMapperRepresentation> protocolMappers) {
|
||||
this.protocolMappers = protocolMappers;
|
||||
}
|
||||
|
||||
public Boolean isInternationalizationEnabled() {
|
||||
return internationalizationEnabled;
|
||||
}
|
||||
|
||||
public void setInternationalizationEnabled(Boolean internationalizationEnabled) {
|
||||
this.internationalizationEnabled = internationalizationEnabled;
|
||||
}
|
||||
|
||||
public Set<String> getSupportedLocales() {
|
||||
if(supportedLocales == null){
|
||||
supportedLocales = new HashSet<String>();
|
||||
}
|
||||
return supportedLocales;
|
||||
}
|
||||
|
||||
public void setSupportedLocales(Set<String> supportedLocales) {
|
||||
this.supportedLocales = supportedLocales;
|
||||
}
|
||||
|
||||
public String getDefaultLocale() {
|
||||
return defaultLocale;
|
||||
}
|
||||
|
||||
public void setDefaultLocale(String defaultLocale) {
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
<feature name="keycloak-adapter-core" version="${project.version}" resolver="(obr)">
|
||||
<details>The keycloak adapter core stuff</details>
|
||||
<bundle dependency="true">mvn:org.keycloak/keycloak-osgi-thirdparty/${project.version}</bundle>
|
||||
<bundle dependency="true">mvn:org.bouncycastle/bcprov-jdk16/${bouncycastle.version}</bundle>
|
||||
<bundle dependency="true">mvn:org.bouncycastle/bcprov-jdk15on/${bouncycastle.crypto.version}</bundle>
|
||||
<bundle dependency="true">mvn:org.bouncycastle/bcpkix-jdk15on/${bouncycastle.crypto.version}</bundle>
|
||||
<bundle dependency="true">mvn:org.codehaus.jackson/jackson-core-asl/${jackson.version}</bundle>
|
||||
<bundle dependency="true">mvn:org.codehaus.jackson/jackson-mapper-asl/${jackson.version}</bundle>
|
||||
<bundle dependency="true">mvn:org.codehaus.jackson/jackson-xc/${jackson.version}</bundle>
|
||||
|
|
|
@ -76,7 +76,7 @@ public class AdminClient {
|
|||
|
||||
try {
|
||||
HttpPost post = new HttpPost(KeycloakUriBuilder.fromUri(getBaseUrl(request) + "/auth")
|
||||
.path(ServiceUrlConstants.TOKEN_SERVICE_DIRECT_GRANT_PATH).build("demo"));
|
||||
.path(ServiceUrlConstants.TOKEN_PATH).build("demo"));
|
||||
List <NameValuePair> formparams = new ArrayList <NameValuePair>();
|
||||
formparams.add(new BasicNameValuePair("username", "admin"));
|
||||
formparams.add(new BasicNameValuePair("password", "password"));
|
||||
|
|
|
@ -109,7 +109,7 @@ public class DatabaseClient {
|
|||
return UriUtils.getOrigin(request.getRequestURL().toString());
|
||||
case BROWSER_ONLY:
|
||||
// Resolve baseURI from the codeURL (This is already non-relative and based on our hostname)
|
||||
return UriUtils.getOrigin(oauthClient.getCodeUrl());
|
||||
return UriUtils.getOrigin(oauthClient.getTokenUrl());
|
||||
case NEVER:
|
||||
return "";
|
||||
default:
|
||||
|
|
|
@ -104,7 +104,7 @@ public class ProductDatabaseClient {
|
|||
return UriUtils.getOrigin(request.getRequestURL().toString());
|
||||
case BROWSER_ONLY:
|
||||
// Resolve baseURI from the codeURL (This is already non-relative and based on our hostname)
|
||||
return UriUtils.getOrigin(oAuthClient.getCodeUrl());
|
||||
return UriUtils.getOrigin(oAuthClient.getTokenUrl());
|
||||
case NEVER:
|
||||
return "";
|
||||
default:
|
||||
|
|
|
@ -31,7 +31,8 @@ Base steps
|
|||
----------
|
||||
|
||||
* Run external instance of Keycloak server on WildFly 8 or JBoss EAP 6.3 . Fuse demo suppose that server is running on [http://localhost:8080/auth](http://localhost:8080/auth)
|
||||
* Import realm `demo` from the file testrealm.json on `examples/fuse/testrealm.json` .
|
||||
* Import realm `demo` from the file testrealm.json on `examples/fuse/testrealm.json` . See [here](../demo-template/README.md#step-3-import-the-test-realm)
|
||||
the details on how to import the realm
|
||||
* Then build examples, which is needed so the feature repository is added to your local maven repo:
|
||||
|
||||
```
|
||||
|
|
|
@ -30,6 +30,11 @@
|
|||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.camel</groupId>
|
||||
<artifactId>camel-core</artifactId>
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
package org.keycloak.example;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class CamelHelloBean {
|
||||
|
||||
public String hello() {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
return "Hello admin! It's " + sdf.format(new Date());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package org.keycloak.example;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Hashtable;
|
||||
|
||||
import javax.naming.Context;
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.directory.Attributes;
|
||||
import javax.naming.directory.DirContext;
|
||||
import javax.naming.directory.InitialDirContext;
|
||||
import javax.security.sasl.Sasl;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.camel.Exchange;
|
||||
import org.apache.camel.Processor;
|
||||
import org.ietf.jgss.GSSCredential;
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.constants.KerberosConstants;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.util.KerberosSerializationUtils;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class CamelHelloProcessor implements Processor {
|
||||
|
||||
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
@Override
|
||||
public void process(Exchange exchange) throws Exception {
|
||||
HttpServletRequest req = exchange.getIn().getBody(HttpServletRequest.class);
|
||||
KeycloakPrincipal keycloakPrincipal = (KeycloakPrincipal) req.getUserPrincipal();
|
||||
AccessToken accessToken = keycloakPrincipal.getKeycloakSecurityContext().getToken();
|
||||
String username = accessToken.getPreferredUsername();
|
||||
|
||||
// send a html response with fullName from LDAP
|
||||
exchange.getOut().setBody("<html><body>Hello " + username + "! It's " + sdf.format(new Date()) + "</body></html>");
|
||||
}
|
||||
}
|
|
@ -49,18 +49,15 @@
|
|||
<property name="handler" ref="securityHandler" />
|
||||
</bean>
|
||||
|
||||
<bean id="helloBean" class="org.keycloak.example.CamelHelloBean" />
|
||||
<bean id="helloProcessor" class="org.keycloak.example.CamelHelloProcessor" />
|
||||
|
||||
<camelContext id="blueprintContext"
|
||||
trace="false"
|
||||
xmlns="http://camel.apache.org/schema/blueprint">
|
||||
<route id="httpBridge">
|
||||
<from uri="jetty:http://0.0.0.0:8383/admin-camel-endpoint?handlers=sessionHandler&matchOnUriPrefix=true" />
|
||||
<setBody>
|
||||
<method ref="helloBean" method="hello"/>
|
||||
</setBody>
|
||||
<process ref="helloProcessor" />
|
||||
<log message="The message from camel endpoint contains ${body}"/>
|
||||
<to uri="mock:result"/>
|
||||
</route>
|
||||
</camelContext>
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ public class CamelClient {
|
|||
try {
|
||||
HttpResponse response = client.execute(get);
|
||||
if (response.getStatusLine().getStatusCode() != 200) {
|
||||
return "There was a failure processing request. You either didn't configure Keycloak properly or you don't have enought permission? Status code is "
|
||||
return "There was a failure processing request. You either didn't configure Keycloak properly or you don't have admin permission? Status code is "
|
||||
+ response.getStatusLine().getStatusCode();
|
||||
}
|
||||
HttpEntity entity = response.getEntity();
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<title>Camel page</title>
|
||||
</head>
|
||||
<body bgcolor="#E3F6CE">
|
||||
<p>You will receive info from camel endpoint. Endpoint is accessible just for admin user</p>
|
||||
<p>You will receive info from camel endpoint. Endpoint is accessible just for user with admin role</p>
|
||||
<p>Response from camel: <b><%= CamelClient.sendRequest(request) %></b> </p>
|
||||
<br><br>
|
||||
</body>
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package org.keycloak.account;
|
||||
|
||||
import org.apache.http.client.methods.HttpHead;
|
||||
import org.keycloak.events.Event;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
@ -18,13 +20,15 @@ public interface AccountProvider extends Provider {
|
|||
|
||||
AccountProvider setUriInfo(UriInfo uriInfo);
|
||||
|
||||
AccountProvider setHttpHeaders(HttpHeaders httpHeaders);
|
||||
|
||||
Response createResponse(AccountPages page);
|
||||
|
||||
AccountProvider setError(String message);
|
||||
AccountProvider setError(String message, Object ... parameters);
|
||||
|
||||
AccountProvider setSuccess(String message);
|
||||
AccountProvider setSuccess(String message, Object ... parameters);
|
||||
|
||||
AccountProvider setWarning(String message);
|
||||
AccountProvider setWarning(String message, Object ... parameters);
|
||||
|
||||
AccountProvider setUser(UserModel user);
|
||||
|
||||
|
|
|
@ -3,38 +3,21 @@ package org.keycloak.account.freemarker;
|
|||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.account.AccountPages;
|
||||
import org.keycloak.account.AccountProvider;
|
||||
import org.keycloak.account.freemarker.model.AccountBean;
|
||||
import org.keycloak.account.freemarker.model.AccountFederatedIdentityBean;
|
||||
import org.keycloak.account.freemarker.model.FeaturesBean;
|
||||
import org.keycloak.account.freemarker.model.LogBean;
|
||||
import org.keycloak.account.freemarker.model.MessageBean;
|
||||
import org.keycloak.account.freemarker.model.PasswordBean;
|
||||
import org.keycloak.account.freemarker.model.ReferrerBean;
|
||||
import org.keycloak.account.freemarker.model.SessionsBean;
|
||||
import org.keycloak.account.freemarker.model.TotpBean;
|
||||
import org.keycloak.account.freemarker.model.UrlBean;
|
||||
import org.keycloak.account.freemarker.model.*;
|
||||
import org.keycloak.events.Event;
|
||||
import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
|
||||
import org.keycloak.freemarker.FreeMarkerException;
|
||||
import org.keycloak.freemarker.FreeMarkerUtil;
|
||||
import org.keycloak.freemarker.Theme;
|
||||
import org.keycloak.freemarker.ThemeProvider;
|
||||
import org.keycloak.freemarker.*;
|
||||
import org.keycloak.freemarker.beans.TextFormatterBean;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.services.resources.flows.Urls;
|
||||
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import javax.ws.rs.core.*;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -57,12 +40,14 @@ public class FreeMarkerAccountProvider implements AccountProvider {
|
|||
private boolean passwordSet;
|
||||
private KeycloakSession session;
|
||||
private FreeMarkerUtil freeMarker;
|
||||
private HttpHeaders headers;
|
||||
|
||||
public static enum MessageType {SUCCESS, WARNING, ERROR}
|
||||
|
||||
private UriInfo uriInfo;
|
||||
|
||||
private String message;
|
||||
private Object[] parameters;
|
||||
private MessageType messageType;
|
||||
|
||||
public FreeMarkerAccountProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
|
||||
|
@ -75,6 +60,12 @@ public class FreeMarkerAccountProvider implements AccountProvider {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountProvider setHttpHeaders(HttpHeaders httpHeaders) {
|
||||
this.headers = httpHeaders;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response createResponse(AccountPages page) {
|
||||
Map<String, Object> attributes = new HashMap<String, Object>();
|
||||
|
@ -94,9 +85,14 @@ public class FreeMarkerAccountProvider implements AccountProvider {
|
|||
logger.warn("Failed to load properties", e);
|
||||
}
|
||||
|
||||
Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, headers);
|
||||
if(locale != null){
|
||||
attributes.put("locale", locale);
|
||||
attributes.put("formatter", new TextFormatterBean(locale));
|
||||
}
|
||||
Properties messages;
|
||||
try {
|
||||
messages = theme.getMessages();
|
||||
messages = theme.getMessages(locale);
|
||||
attributes.put("rb", messages);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Failed to load messages", e);
|
||||
|
@ -115,13 +111,23 @@ public class FreeMarkerAccountProvider implements AccountProvider {
|
|||
}
|
||||
|
||||
if (message != null) {
|
||||
attributes.put("message", new MessageBean(messages.containsKey(message) ? messages.getProperty(message) : message, messageType));
|
||||
String formattedMessage;
|
||||
if(messages.containsKey(message)){
|
||||
formattedMessage = new MessageFormat(messages.getProperty(message).replace("'","''"),locale).format(parameters);
|
||||
}else{
|
||||
formattedMessage = message;
|
||||
}
|
||||
attributes.put("message", new MessageBean(formattedMessage, messageType));
|
||||
}
|
||||
|
||||
if (referrer != null) {
|
||||
attributes.put("referrer", new ReferrerBean(referrer));
|
||||
}
|
||||
|
||||
if(realm != null){
|
||||
attributes.put("realm", new RealmBean(realm));
|
||||
}
|
||||
|
||||
attributes.put("url", new UrlBean(realm, theme, baseUri, baseQueryUri, uriInfo.getRequestUri(), stateChecker));
|
||||
|
||||
attributes.put("features", new FeaturesBean(identityProviderEnabled, eventsEnabled, passwordUpdateSupported));
|
||||
|
@ -150,6 +156,7 @@ public class FreeMarkerAccountProvider implements AccountProvider {
|
|||
String result = freeMarker.processTemplate(attributes, Templates.getTemplate(page), theme);
|
||||
Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML).entity(result);
|
||||
BrowserSecurityHeaderSetup.headers(builder, realm);
|
||||
LocaleHelper.updateLocaleCookie(builder, locale, realm, uriInfo, Urls.localeCookiePath(baseUri,realm.getName()));
|
||||
return builder.build();
|
||||
} catch (FreeMarkerException e) {
|
||||
logger.error("Failed to process template", e);
|
||||
|
@ -163,22 +170,25 @@ public class FreeMarkerAccountProvider implements AccountProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public AccountProvider setError(String message) {
|
||||
public AccountProvider setError(String message, Object ... parameters) {
|
||||
this.message = message;
|
||||
this.parameters = parameters;
|
||||
this.messageType = MessageType.ERROR;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountProvider setSuccess(String message) {
|
||||
public AccountProvider setSuccess(String message, Object ... parameters) {
|
||||
this.message = message;
|
||||
this.parameters = parameters;
|
||||
this.messageType = MessageType.SUCCESS;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountProvider setWarning(String message) {
|
||||
public AccountProvider setWarning(String message, Object ... parameters) {
|
||||
this.message = message;
|
||||
this.parameters = parameters;
|
||||
this.messageType = MessageType.WARNING;
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* JBoss, Home of Professional Open Source.
|
||||
* Copyright 2012, Red Hat, Inc., and individual contributors
|
||||
* as indicated by the @author tags. See the copyright.txt file in the
|
||||
* distribution for a full listing of individual contributors.
|
||||
*
|
||||
* This is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as
|
||||
* published by the Free Software Foundation; either version 2.1 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this software; if not, write to the Free
|
||||
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
|
||||
*/
|
||||
package org.keycloak.account.freemarker.model;
|
||||
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RequiredCredentialModel;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:gerbermichi@me.com">Michael Gerber</a>
|
||||
*/
|
||||
public class RealmBean {
|
||||
|
||||
private RealmModel realm;
|
||||
|
||||
public RealmBean(RealmModel realmModel) {
|
||||
realm = realmModel;
|
||||
}
|
||||
|
||||
public boolean isInternationalizationEnabled() {
|
||||
return realm.isInternationalizationEnabled();
|
||||
}
|
||||
|
||||
public Set<String> getSupportedLocales(){
|
||||
return realm.getSupportedLocales();
|
||||
}
|
||||
|
||||
}
|
|
@ -59,6 +59,10 @@ public class UrlBean {
|
|||
return Urls.accountSessionsLogoutPage(baseQueryURI, realm, stateChecker).toString();
|
||||
}
|
||||
|
||||
public String getLocaleCookiePath(){
|
||||
return Urls.localeCookiePath(baseURI, realm);
|
||||
}
|
||||
|
||||
public String getTotpRemoveUrl() {
|
||||
return Urls.accountTotpRemove(baseQueryURI, realm, stateChecker).toString();
|
||||
}
|
||||
|
|
|
@ -7,14 +7,7 @@ import org.keycloak.models.KeycloakSession;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
|
@ -228,11 +221,11 @@ public class ExtendingThemeManager implements ThemeProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Properties getMessages() throws IOException {
|
||||
public Properties getMessages(Locale locale) throws IOException {
|
||||
Properties messages = new Properties();
|
||||
ListIterator<Theme> itr = themes.listIterator(themes.size());
|
||||
while (itr.hasPrevious()) {
|
||||
Properties m = itr.previous().getMessages();
|
||||
Properties m = itr.previous().getMessages(locale);
|
||||
if (m != null) {
|
||||
messages.putAll(m);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
package org.keycloak.freemarker;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.NewCookie;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:gerbermichi@me.com">Michael Gerber</a>
|
||||
*/
|
||||
public class LocaleHelper {
|
||||
public final static String LOCALE_COOKIE = "KEYCLOAK_LOCALE";
|
||||
public static final String LOCALE_PARAM = "ui_locale";
|
||||
|
||||
private final static Logger LOGGER = Logger.getLogger(LocaleHelper.class);
|
||||
|
||||
public static Locale getLocale(RealmModel realm, UserModel user) {
|
||||
return getLocale(realm, user, null, null);
|
||||
}
|
||||
|
||||
public static Locale getLocale(RealmModel realm, UserModel user, UriInfo uriInfo, HttpHeaders httpHeaders) {
|
||||
if(!realm.isInternationalizationEnabled()){
|
||||
return Locale.ENGLISH;
|
||||
}
|
||||
|
||||
//1. Locale cookie
|
||||
if(httpHeaders != null && httpHeaders.getCookies().containsKey(LOCALE_COOKIE)){
|
||||
String localeString = httpHeaders.getCookies().get(LOCALE_COOKIE).getValue();
|
||||
Locale locale = findLocale(localeString, realm.getSupportedLocales());
|
||||
if(locale != null){
|
||||
if(user != null){
|
||||
user.setAttribute(UserModel.LOCALE, locale.toLanguageTag());
|
||||
}
|
||||
return locale;
|
||||
}else{
|
||||
LOGGER.infof("Locale %s is not supported.", localeString);
|
||||
}
|
||||
}
|
||||
|
||||
//2. User profile
|
||||
if(user != null && user.getAttributes().containsKey(UserModel.LOCALE)){
|
||||
String localeString = user.getAttribute(UserModel.LOCALE);
|
||||
Locale locale = findLocale(localeString, realm.getSupportedLocales());
|
||||
if(locale != null){
|
||||
|
||||
return locale;
|
||||
}else{
|
||||
LOGGER.infof("Locale %s is not supported.", localeString);
|
||||
}
|
||||
}
|
||||
|
||||
//3. ui_locales query parameter
|
||||
if(uriInfo != null && uriInfo.getQueryParameters().containsKey(LOCALE_PARAM)){
|
||||
String localeString = uriInfo.getQueryParameters().getFirst(LOCALE_PARAM);
|
||||
Locale locale = findLocale(localeString, realm.getSupportedLocales());
|
||||
if(locale != null){
|
||||
return locale;
|
||||
}else{
|
||||
LOGGER.infof("Locale %s is not supported.", localeString);
|
||||
}
|
||||
}
|
||||
|
||||
//4. Accept-Language http header
|
||||
if(httpHeaders !=null && httpHeaders.getAcceptableLanguages() != null && !httpHeaders.getAcceptableLanguages().isEmpty()){
|
||||
for(Locale l : httpHeaders.getAcceptableLanguages()){
|
||||
String localeString = l.toLanguageTag();
|
||||
Locale locale = findLocale(localeString, realm.getSupportedLocales());
|
||||
if(locale != null){
|
||||
return locale;
|
||||
}else{
|
||||
LOGGER.infof("Locale %s is not supported.", localeString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//5. Default realm locale
|
||||
if(realm.getDefaultLocale() != null){
|
||||
return Locale.forLanguageTag(realm.getDefaultLocale());
|
||||
}
|
||||
|
||||
return Locale.ENGLISH;
|
||||
}
|
||||
|
||||
public static void updateLocaleCookie(Response.ResponseBuilder builder, Locale locale, RealmModel realm, UriInfo uriInfo, String path) {
|
||||
if (locale == null) {
|
||||
return;
|
||||
}
|
||||
boolean secure = realm.getSslRequired().isRequired(uriInfo.getRequestUri().getHost());
|
||||
builder.cookie(new NewCookie(LocaleHelper.LOCALE_COOKIE, locale.toLanguageTag(), path, null, null, 31536000, secure));
|
||||
}
|
||||
|
||||
public static Locale findLocale(String localeString, Set<String> supportedLocales) {
|
||||
Locale result = null;
|
||||
Locale search = Locale.forLanguageTag(localeString);
|
||||
for(String languageTag : supportedLocales) {
|
||||
Locale locale = Locale.forLanguageTag(languageTag);
|
||||
if(locale.getLanguage().equals(search.getLanguage())){
|
||||
if(locale.getCountry().equals("") && result == null){
|
||||
result = locale;
|
||||
}
|
||||
if(locale.getCountry().equals(search.getCountry())){
|
||||
return locale;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ package org.keycloak.freemarker;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
|
@ -28,7 +29,7 @@ public interface Theme {
|
|||
|
||||
public InputStream getResourceAsStream(String path) throws IOException;
|
||||
|
||||
public Properties getMessages() throws IOException;
|
||||
public Properties getMessages(Locale locale) throws IOException;
|
||||
|
||||
public Properties getProperties() throws IOException;
|
||||
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package org.keycloak.freemarker.beans;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:gerbermichi@me.com">Michael Gerber</a>
|
||||
*/
|
||||
public class TextFormatterBean {
|
||||
private Locale locale;
|
||||
|
||||
public TextFormatterBean(Locale locale) {
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
public String format(String pattern, Object ... parameters){
|
||||
return new MessageFormat(pattern.replace("'","''"),locale).format(parameters);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package org.keycloak.freemarke;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.freemarker.LocaleHelper;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:gerbermichi@me.com">Michael Gerber</a>
|
||||
*/
|
||||
public class LocaleHelperTest {
|
||||
@Test
|
||||
public void findLocaleTest(){
|
||||
Assert.assertEquals("de", LocaleHelper.findLocale("de", new HashSet<>(Arrays.asList("de","en"))).toLanguageTag());
|
||||
Assert.assertEquals("en", LocaleHelper.findLocale("en", new HashSet<>(Arrays.asList("de","en"))).toLanguageTag());
|
||||
Assert.assertEquals("de", LocaleHelper.findLocale("de-CH", new HashSet<>(Arrays.asList("de","en"))).toLanguageTag());
|
||||
Assert.assertEquals("de-CH", LocaleHelper.findLocale("de-CH", new HashSet<>(Arrays.asList("de","de-CH","de-DE"))).toLanguageTag());
|
||||
Assert.assertEquals("de-DE", LocaleHelper.findLocale("de-DE", new HashSet<>(Arrays.asList("de","de-CH","de-DE"))).toLanguageTag());
|
||||
Assert.assertEquals("de", LocaleHelper.findLocale("de", new HashSet<>(Arrays.asList("de","de-CH","de-DE"))).toLanguageTag());
|
||||
Assert.assertNull(LocaleHelper.findLocale("de", new HashSet<>(Arrays.asList("de-CH","de-DE"))));
|
||||
}
|
||||
}
|
|
@ -42,6 +42,11 @@
|
|||
<artifactId>resteasy-jaxrs</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -2,10 +2,13 @@ package org.keycloak.theme;
|
|||
|
||||
import org.keycloak.freemarker.Theme;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -26,7 +29,7 @@ public class ClassLoaderTheme implements Theme {
|
|||
|
||||
private String resourceRoot;
|
||||
|
||||
private String messages;
|
||||
private String messageRoot;
|
||||
|
||||
private Properties properties;
|
||||
|
||||
|
@ -43,7 +46,7 @@ public class ClassLoaderTheme implements Theme {
|
|||
|
||||
this.templateRoot = themeRoot;
|
||||
this.resourceRoot = themeRoot + "resources/";
|
||||
this.messages = themeRoot + "messages/messages.properties";
|
||||
this.messageRoot = themeRoot + "messages/";
|
||||
this.properties = new Properties();
|
||||
|
||||
URL p = classLoader.getResource(themeRoot + "theme.properties");
|
||||
|
@ -102,9 +105,13 @@ public class ClassLoaderTheme implements Theme {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Properties getMessages() throws IOException {
|
||||
public Properties getMessages(Locale locale) throws IOException {
|
||||
if(locale == null){
|
||||
return null;
|
||||
}
|
||||
Properties m = new Properties();
|
||||
URL url = classLoader.getResource(this.messages);
|
||||
|
||||
URL url = classLoader.getResource(this.messageRoot + "messages_" + locale.toString() + ".properties");
|
||||
if (url != null) {
|
||||
m.load(url.openStream());
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import java.io.FileInputStream;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
|
@ -81,9 +82,14 @@ public class FolderTheme implements Theme {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Properties getMessages() throws IOException {
|
||||
public Properties getMessages(Locale locale) throws IOException {
|
||||
if(locale == null){
|
||||
return null;
|
||||
}
|
||||
|
||||
Properties m = new Properties();
|
||||
File file = new File(themeDir, "messages" + File.separator + "messages.properties");
|
||||
|
||||
File file = new File(themeDir, "messages" + File.separator + "messages_" + locale.toString() + ".properties");
|
||||
if (file.isFile()) {
|
||||
m.load(new FileInputStream(file));
|
||||
}
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
authenticatorCode=One-time code
|
||||
email=Email
|
||||
errorHeader=Error!
|
||||
firstName=First name
|
||||
lastName=Last name
|
||||
password=Password
|
||||
passwordConfirm=Confirmation
|
||||
passwordNew=New Password
|
||||
successHeader=Success!
|
||||
username=Username
|
||||
street=Street
|
||||
locality=City or Locality
|
||||
region=State, Province, or Region
|
||||
postal_code=Zip or Postal code
|
||||
country=Country
|
||||
|
||||
missingFirstName=Please specify first name
|
||||
invalidEmail=Invalid email address
|
||||
missingLastName=Please specify last name
|
||||
missingEmail=Please specify email
|
||||
missingPassword=Please specify password.
|
||||
notMatchPassword=Passwords don't match
|
||||
|
||||
missingTotp=Please specify authenticator code
|
||||
invalidPasswordExisting=Invalid existing password
|
||||
invalidPasswordConfirm=Password confirmation doesn't match
|
||||
invalidTotp=Invalid authenticator code
|
||||
readOnlyUser=You can't update your account as it is read only
|
||||
readOnlyPassword=You can't update your password as your account is read only
|
||||
|
||||
successTotp=Mobile authenticator configured.
|
||||
successTotpRemoved=Mobile authenticator removed.
|
||||
|
||||
accountUpdated=Your account has been updated
|
||||
accountPasswordUpdated=Your password has been updated
|
||||
|
||||
missingIdentityProvider=Identity provider not specified
|
||||
invalidFederatedIdentityAction=Invalid or missing action
|
||||
identityProviderNotFound=Specified identity provider not found
|
||||
federatedIdentityLinkNotActive=This identity is not active anymore
|
||||
federatedIdentityRemovingLastProvider=You can't remove last federated identity as you don't have password
|
||||
identityProviderRedirectError=Failed to redirect to identity provider
|
||||
identityProviderRemoved=Identity provider removed successfully
|
||||
|
||||
accountDisabled=Account is disabled, contact admin\
|
||||
accountTemporarilyDisabled=Account is temporarily disabled, contact admin or try again later
|
||||
|
||||
logOutAllSessions=Log out all sessions
|
|
@ -0,0 +1,57 @@
|
|||
authenticatorCode=One-time code
|
||||
email=E-Mail
|
||||
firstName=Vorname
|
||||
lastName=Nachname
|
||||
password=Passwort
|
||||
passwordConfirm=Passwort bestätigung
|
||||
passwordNew=Neues Passwort
|
||||
username=Benutzernamen
|
||||
street=Strasse
|
||||
region=Staat, Provinz, Region
|
||||
postal_code=PLZ
|
||||
locality=Stadt oder Ortschaft
|
||||
country=Land
|
||||
|
||||
missingFirstNameMessage=Bitte geben Sie einen Vornamen ein.
|
||||
missingEmailMessage=Bitte geben Sie eine E-Mail Adresse ein.
|
||||
missingLastNameMessage=Bitte geben Sie einen Nachnamen ein.
|
||||
missingPasswordMessage=Bitte geben Sie ein Passwort ein.
|
||||
notMatchPasswordMessage=Passwörter sind nicht identisch.
|
||||
|
||||
missingTotpMessage=Bitte geben Sie den One-time Code ein.
|
||||
invalidPasswordExistingMessage=Das aktuelle Passwort is ungültig.
|
||||
invalidPasswordConfirmMessage=Die Passwort bestätigung ist nicht identisch.
|
||||
invalidTotpMessage=Ungültiger One-time Code.
|
||||
invalidEmailMessage=Ungültige E-Mail Adresse.
|
||||
|
||||
readOnlyUserMessage=Sie können dieses Benutzerkonto nicht ändern, da es schreibgeschützt ist.
|
||||
readOnlyPasswordMessage=Sie können dieses Passwort nicht ändern, da es schreibgeschützt ist.
|
||||
|
||||
successTotpMessage=Mobile Authentifizierung eingerichtet.
|
||||
successTotpRemovedMessage=Mobile Authentifizierung entfernt.
|
||||
|
||||
accountUpdatedMessage=Ihr Benutzerkonto wurde aktualisiert.
|
||||
accountPasswordUpdatedMessage=Ihr Passwort wurde aktualisiert.
|
||||
|
||||
missingIdentityProviderMessage=Identity Provider nicht angegeben.
|
||||
invalidFederatedIdentityActionMessage=Ungültige oder fehlende Aktion.
|
||||
identityProviderNotFoundMessage=Angegebener Identity Provider nicht gefunden.
|
||||
federatedIdentityLinkNotActiveMessage=Diese Identität ist nicht mehr aktiv.
|
||||
federatedIdentityRemovingLastProviderMessage=Sie können den letzen Eintrag nicht enfernen, da Sie kein Passwort haben.
|
||||
identityProviderRedirectErrorMessage=Fehler bei der Weiterleitung zum Identity Proivder.
|
||||
identityProviderRemovedMessage=Identity Provider erfolgreich entfernt.
|
||||
|
||||
accountDisabledMessage=Benutzerkonto ist gesperrt, bitte kontaktieren Sie den Admin.
|
||||
|
||||
doLogOutAllSessions=Alle Sessionen abmelden
|
||||
|
||||
accountTemporarilyDisabledMessage=Benutzerkonto ist temporär gespert, bitte kontaktieren Sie den Admin oder versuchen Sie es später nocheinmal.
|
||||
invalidPasswordMinLengthMessage=Ungültiges Passwort: minimum länge {0}.
|
||||
invalidPasswordMinDigitsMessage=Ungültiges Passwort: muss mindestens {0} Zahl(en) beinhalten.
|
||||
invalidPasswordMinLowerCaseCharsMessage=Ungültiges Passwort: muss mindestens {0} Kleinbuchstaben beinhalten.
|
||||
invalidPasswordMinUpperCaseCharsMessage=Ungültiges Passwort: muss mindestens {0} Grossbuchstaben beinhalten.
|
||||
invalidPasswordMinSpecialCharsMessage=Ungültiges Passwort\: muss mindestens {0} Spezialzeichen beinhalten.
|
||||
invalidPasswordNotUsernameMessage=Ungültiges Passwort\: darf nicht gleich sein wie Benutzername.
|
||||
|
||||
locale_de=Deutsch
|
||||
locale_en=Englisch
|
|
@ -0,0 +1,57 @@
|
|||
authenticatorCode=One-time code
|
||||
email=Email
|
||||
firstName=First name
|
||||
lastName=Last name
|
||||
password=Password
|
||||
passwordConfirm=Confirmation
|
||||
passwordNew=New Password
|
||||
username=Username
|
||||
street=Street
|
||||
locality=City or Locality
|
||||
region=State, Province, or Region
|
||||
postal_code=Zip or Postal code
|
||||
country=Country
|
||||
|
||||
missingFirstNameMessage=Please specify first name.
|
||||
invalidEmailMessage=Invalid email address.
|
||||
missingLastNameMessage=Please specify last name.
|
||||
missingEmailMessage=Please specify email.
|
||||
missingPasswordMessage=Please specify password.
|
||||
notMatchPasswordMessage=Passwords don't match.
|
||||
|
||||
missingTotpMessage=Please specify authenticator code
|
||||
invalidPasswordExistingMessage=Invalid existing password
|
||||
invalidPasswordConfirmMessage=Password confirmation doesn't match
|
||||
invalidTotpMessage=Invalid authenticator code
|
||||
|
||||
readOnlyUserMessage=You can't update your account as it is read only
|
||||
readOnlyPasswordMessage=You can't update your password as your account is read only
|
||||
|
||||
successTotpMessage=Mobile authenticator configured.
|
||||
successTotpRemovedMessage=Mobile authenticator removed.
|
||||
|
||||
accountUpdatedMessage=Your account has been updated
|
||||
accountPasswordUpdatedMessage=Your password has been updated
|
||||
|
||||
missingIdentityProviderMessage=Identity provider not specified
|
||||
invalidFederatedIdentityActionMessage=Invalid or missing action
|
||||
identityProviderNotFoundMessage=Specified identity provider not found
|
||||
federatedIdentityLinkNotActiveMessage=This identity is not active anymore
|
||||
federatedIdentityRemovingLastProviderMessage=You can't remove last federated identity as you don't have password
|
||||
identityProviderRedirectErrorMessage=Failed to redirect to identity provider
|
||||
identityProviderRemovedMessage=Identity provider removed successfully
|
||||
|
||||
accountDisabledMessage=Account is disabled, contact admin
|
||||
|
||||
doLogOutAllSessions=Log out all sessions
|
||||
|
||||
accountTemporarilyDisabledMessage=Account is temporarily disabled, contact admin or try again later
|
||||
invalidPasswordMinLengthMessage=Invalid password: minimum length {0}
|
||||
invalidPasswordMinLowerCaseCharsMessage=Invalid password: must contain at least {0} lower case characters
|
||||
invalidPasswordMinDigitsMessage=Invalid password: must contain at least {0} numerical digits
|
||||
invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least {0} upper case characters
|
||||
invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters
|
||||
invalidPasswordNotUsernameMessage=Invalid password\: must not be equal to the username
|
||||
|
||||
locale_de=German
|
||||
locale_en=English
|
|
@ -42,6 +42,6 @@
|
|||
|
||||
</table>
|
||||
|
||||
<a id="logout-all-sessions" href="${url.sessionsLogoutUrl}">${rb.logOutAllSessions}</a>
|
||||
<a id="logout-all-sessions" href="${url.sessionsLogoutUrl}">${rb.doLogOutAllSessions}</a>
|
||||
|
||||
</@layout.mainLayout>
|
||||
|
|
|
@ -15,6 +15,19 @@
|
|||
<script type="text/javascript" src="${url.resourcesPath}/${script}"></script>
|
||||
</#list>
|
||||
</#if>
|
||||
<#if realm.internationalizationEnabled>
|
||||
<script type="text/javascript">
|
||||
window.onload = function () {
|
||||
var select = document.querySelector(".kc-locale-select");
|
||||
select.onchange = function (event) {
|
||||
document.cookie = "KEYCLOAK_LOCALE=" + select.value+"; path=${url.localeCookiePath}";
|
||||
setTimeout(function () {
|
||||
window.location.reload();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</#if>
|
||||
</head>
|
||||
<body class="admin-console user ${bodyClass}">
|
||||
|
||||
|
@ -28,6 +41,15 @@
|
|||
<div class="navbar-collapse navbar-collapse-1">
|
||||
<div class="container">
|
||||
<ul class="nav navbar-nav navbar-utility">
|
||||
<#if realm.internationalizationEnabled>
|
||||
<li>
|
||||
<select class="kc-locale-select">
|
||||
<#list realm.supportedLocales as l>
|
||||
<option value="${l}" <#if locale.toLanguageTag()==l>selected="selected"</#if>>${rb["locale_" + l]}</option>
|
||||
</#list>
|
||||
</select>
|
||||
<li>
|
||||
</#if>
|
||||
<#if referrer?has_content && referrer.url?has_content><li><a href="${referrer.url}" id="referrer">Back to ${referrer.name}</a></li></#if>
|
||||
<li><a href="${url.logoutUrl}">Sign Out</a></li>
|
||||
</ul>
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
<script src="${resourceUrl}/lib/fileupload/angular-file-upload.min.js"></script>
|
||||
<script src="${resourceUrl}/lib/filesaver/FileSaver.js"></script>
|
||||
|
||||
<script src="/auth/js/keycloak.js" type="text/javascript"></script>
|
||||
<script src="/auth/js/${resourceVersion}/keycloak.js" type="text/javascript"></script>
|
||||
|
||||
<script src="${resourceUrl}/js/app.js" type="text/javascript"></script>
|
||||
<script src="${resourceUrl}/js/controllers/realm.js" type="text/javascript"></script>
|
||||
|
|
|
@ -403,6 +403,18 @@ module.controller('RealmLoginSettingsCtrl', function($scope, Current, Realm, rea
|
|||
|
||||
module.controller('RealmThemeCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications) {
|
||||
genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, "/realms/" + realm.realm + "/theme-settings");
|
||||
|
||||
$scope.supportedLocalesOptions = {
|
||||
'multiple' : true,
|
||||
'simple_tags' : true,
|
||||
'tags' : ['en', 'de']
|
||||
};
|
||||
|
||||
$scope.$watch('realm.supportedLocales', function(oldVal, newVal) {
|
||||
if(angular.isUndefined(newVal) || (angular.isArray(newVal) && newVal.length == 0)){
|
||||
$scope.realm.defaultLocale = undefined;
|
||||
}
|
||||
}, true);
|
||||
});
|
||||
|
||||
module.controller('RealmCacheCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications) {
|
||||
|
|
|
@ -1042,7 +1042,8 @@ module.factory('PasswordPolicy', function() {
|
|||
digits: "Minimal number (integer type) of digits in password. Default value is 1.",
|
||||
lowerCase: "Minimal number (integer type) of lowercase characters in password. Default value is 1.",
|
||||
upperCase: "Minimal number (integer type) of uppercase characters in password. Default value is 1.",
|
||||
specialChars: "Minimal number (integer type) of special characters in password. Default value is 1."
|
||||
specialChars: "Minimal number (integer type) of special characters in password. Default value is 1.",
|
||||
notUsername: "Block passwords that are equal to the username"
|
||||
}
|
||||
|
||||
p.allPolicies = [
|
||||
|
@ -1051,7 +1052,8 @@ module.factory('PasswordPolicy', function() {
|
|||
{ name: 'digits', value: 1 },
|
||||
{ name: 'lowerCase', value: 1 },
|
||||
{ name: 'upperCase', value: 1 },
|
||||
{ name: 'specialChars', value: 1 }
|
||||
{ name: 'specialChars', value: 1 },
|
||||
{ name: 'notUsername', value: 1 }
|
||||
];
|
||||
|
||||
p.parse = function(policyString) {
|
||||
|
@ -1068,9 +1070,9 @@ module.factory('PasswordPolicy', function() {
|
|||
var re = /(\w+)\(*(\d*)\)*/;
|
||||
|
||||
var policyEntry = re.exec(policyToken);
|
||||
|
||||
policies.push({ name: policyEntry[1], value: parseInt(policyEntry[2]) });
|
||||
|
||||
if (null !== policyEntry) {
|
||||
policies.push({ name: policyEntry[1], value: parseInt(policyEntry[2]) });
|
||||
}
|
||||
}
|
||||
|
||||
return policies;
|
||||
|
|
|
@ -46,7 +46,9 @@
|
|||
<input class="form-control disabled" type="text" value="{{p.name|capitalize}}" readonly>
|
||||
</td>
|
||||
<td>
|
||||
<input class="form-control" ng-model="p.value" type="number" placeholder="No value assigned" min="1">
|
||||
<input class="form-control" ng-model="p.value" ng-show="p.name != 'notUsername' "
|
||||
placeholder="No value assigned"
|
||||
min="1">
|
||||
</td>
|
||||
<td class="actions">
|
||||
<div class="action-div"><i class="pficon pficon-delete" ng-click="removePolicy($index)" tooltip-placement="right" tooltip="Remove Policy"></i></div>
|
||||
|
|
|
@ -59,6 +59,33 @@
|
|||
</div>
|
||||
<span tooltip-placement="right" tooltip="Select theme for emails that are sent by the server." class="fa fa-info-circle"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="internationalizationEnabled">Internationalization Enabled</label>
|
||||
<div class="col-sm-4">
|
||||
<input ng-model="realm.internationalizationEnabled" name="internationalizationEnabled" id="internationalizationEnabled" onoffswitch />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="supportedLocales" class="control-label two-lines">Supported Locales</label>
|
||||
|
||||
<div class="col-sm-4">
|
||||
<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">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="defaultLocale">Default Locale</label>
|
||||
<div class="col-sm-4">
|
||||
<div class="select-kc">
|
||||
<select id="defaultLocale"
|
||||
ng-model="realm.defaultLocale"
|
||||
ng-options="o as o for o in realm.supportedLocales"
|
||||
ng-required="realm.internationalizationEnabled"
|
||||
ng-disabled="!realm.internationalizationEnabled">
|
||||
<option value="" disabled selected>Select one...</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="pull-right form-actions" data-ng-show="access.manageRealm">
|
||||
<button kc-reset data-ng-show="changed">Clear changes</button>
|
||||
|
|
|
@ -94,12 +94,24 @@
|
|||
<label class="col-sm-2 control-label" for="reqActions">Required User Actions</label>
|
||||
|
||||
<div class="col-sm-5">
|
||||
<select ui-select2 ng-model="user.requiredActions" data-placeholder="Select an action..." multiple>
|
||||
<select ui-select2 id="reqActions" ng-model="user.requiredActions" data-placeholder="Select an action..." multiple>
|
||||
<option ng-repeat="action in userReqActionList" value="{{action.id}}">{{action.text}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<span tooltip-placement="right" tooltip="Require an action when the user logs in. 'Verify email' sends an email to the user to verify their email address. 'Update profile' requires user to enter in new personal information. 'Update password' requires user to enter in a new password. 'Configure TOTP' requires setup of a mobile password generator." class="fa fa-info-circle"></span>
|
||||
</div>
|
||||
<div class="form-group clearfix" data-ng-if="realm.internationalizationEnabled">
|
||||
<label class="col-sm-2 control-label" for="locale">Locale</label>
|
||||
<div class="col-sm-5">
|
||||
<div class="select-kc">
|
||||
<select id="locale"
|
||||
ng-model="user.attributes.locale"
|
||||
ng-options="o as o for o in realm.supportedLocales">
|
||||
<option value="" disabled selected>Select one...</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div data-ng-include data-src="resourceUrl + '/partials/user-attribute-entry.html'"></div>
|
||||
<div class="pull-right form-actions" data-ng-show="create && access.manageUsers">
|
||||
|
|
|
@ -1,5 +1 @@
|
|||
Someone has created a Keycloak account with this email address. If this was you, click the link below to verify your email address:
|
||||
${link}
|
||||
This link will expire within ${linkExpiration} minutes.
|
||||
|
||||
If you didn't create this account, just ignore this message.
|
||||
${formatter.format(rb.emailVerificationBody,link, linkExpiration)}
|
|
@ -1 +1 @@
|
|||
A failed login attempt was dettected to your account on ${event.date?datetime} from ${event.ipAddress}. If this was not you, please contact an admin.
|
||||
${formatter.format(rb.eventLoginErrorBody,event.date,event.ipAddress)}
|
|
@ -1 +1 @@
|
|||
TOTP was removed from your account on ${event.date?datetime} from ${event.ipAddress}. If this was not you, please contact an admin.
|
||||
${formatter.format(rb.eventRemoveTotpBody,event.date, event.ipAddress)}
|
|
@ -1 +1 @@
|
|||
Your password was changed on ${event.date?datetime} from ${event.ipAddress}. If this was not you, please contact an admin.
|
||||
${formatter.format(rb.eventUpdatePasswordBody,event.date, event.ipAddress)}
|
|
@ -1 +1 @@
|
|||
TOTP was updated for your account on ${event.date?datetime} from ${event.ipAddress}. If this was not you, please contact an admin.
|
||||
${formatter.format(rb.eventUpdateTotpBody,event.date, event.ipAddress)}
|
|
@ -1,2 +0,0 @@
|
|||
emailVerificationSubject=Verify email
|
||||
passwordResetSubject=Reset password
|
|
@ -0,0 +1,12 @@
|
|||
emailVerificationSubject=E-Mail verifizieren
|
||||
passwordResetSubject=Passwort zurückzusetzen
|
||||
passwordResetBody=Jemand hat angeforder Ihr Keycloak Passwort zurückzusetzen. Falls das Sie waren, dann klicken Sie auf den folgenden Link um das Passwort zurückzusetzen.\n\n{0}\n\nDieser Link wird in {1} Minuten ablaufen.\n\nFalls Sie das Passwort nicht zurücksetzen möchten, dann können Sie diese E-Mail ignorieren.
|
||||
emailVerificationBody=Jemand hat ein Keycloak Konto mit dieser E-Mail Adresse erstellt. Fall das Sie waren, dann klicken Sie auf den Link um die E-Mail Adresse zu verifizieren.\n\n{0}\n\nDieser Link wird in {1} Minuten ablaufen.\n\nFalls Sie dieses Konto nicht erstellt haben, dann können sie diese Nachricht ignorieren.
|
||||
eventLoginErrorSubject=Fehlgeschlagene Anmeldung
|
||||
eventLoginErrorBody=Jemand hat um {0} von {1} versucht sich mit ihrem Konto anzumelden. Falls das nicht Sie waren, dann kontaktieren Sie bitte Ihren Admin.
|
||||
eventRemoveTotpSubject=TOTP Entfernt
|
||||
eventRemoveTotpBody=TOTP wurde von ihrem Konto am {0} von {1} entfernt. Falls das nicht Sie waren, dann kontaktieren Sie bitte Ihren Admin.
|
||||
eventUpdatePasswordSubject=Passwort Aktualisiert
|
||||
eventUpdatePasswordBody=Ihr Passwort wurde am {0} von {1} geändert. Falls das nicht Sie waren, dann kontaktieren Sie bitte Ihren Admin.
|
||||
eventUpdateTotpSubject=TOTP Aktualisiert
|
||||
eventUpdateTotpBody=TOTP wurde am {0} von {1} geändert. Falls das nicht Sie waren, dann kontaktieren Sie bitte Ihren Admin.
|
|
@ -0,0 +1,12 @@
|
|||
emailVerificationSubject=Verify email
|
||||
emailVerificationBody=Someone has created a Keycloak account with this email address. If this was you, click the link below to verify your email address\n\n{0}\n\nThis link will expire within {1} minutes.\n\nIf you didn't create this account, just ignore this message.
|
||||
passwordResetSubject=Reset password
|
||||
passwordResetBody=Someone just requested to change your Keycloak account's password. If this was you, click on the link below to set a new password\n\n{0}\n\nThis link will expire within {1} minutes.\n\nIf you don't want to reset your password, just ignore this message and nothing will be changed.
|
||||
eventLoginErrorSubject=Login error
|
||||
eventLoginErrorBody=A failed login attempt was dettected to your account on {0} from {1}. If this was not you, please contact an admin.
|
||||
eventRemoveTotpSubject=Remove TOTP
|
||||
eventRemoveTotpBody=TOTP was removed from your account on {0} from {1}. If this was not you, please contact an admin.
|
||||
eventUpdatePasswordSubject=Update password
|
||||
eventUpdatePasswordBody=Your password was changed on {0} from {1}. If this was not you, please contact an admin.
|
||||
eventUpdateTotpSubject=Update TOTP
|
||||
eventUpdateTotpBody=TOTP was updated for your account on {0} from {1}. If this was not you, please contact an admin.
|
|
@ -1,5 +1 @@
|
|||
Someone just requested to change your Keycloak account's password. If this was you, click on the link below to set a new password:
|
||||
${link}
|
||||
This link will expire within ${linkExpiration} minutes.
|
||||
|
||||
If you don't want to reset your password, just ignore this message and nothing will be changed.
|
||||
${formatter.format(rb.passwordResetBody,link, linkExpiration)}
|
|
@ -23,7 +23,7 @@
|
|||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<input class="btn btn-primary btn-lg" type="submit" value="${rb.submit}"/>
|
||||
<input class="btn btn-primary btn-lg" type="submit" value="${rb.doSubmit}"/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<#if section = "title">
|
||||
${rb.oauthGrantTitle}
|
||||
<#elseif section = "header">
|
||||
Temporary access for <strong>${(realm.name)!''}</strong> requested by <strong>${(client.clientId)!''}</strong>.
|
||||
${formatter.format(rb.oauthGrantTitleHtml,(realm.name!''), (client.clientId!''))}
|
||||
<#elseif section = "form">
|
||||
<div id="kc-oauth" class="content-area">
|
||||
<h3>${rb.oauthGrantRequest}</h3>
|
||||
|
@ -55,8 +55,8 @@
|
|||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<div class="${properties.kcFormButtonsWrapperClass!}">
|
||||
<input class="btn btn-primary btn-lg" name="accept" id="kc-login" type="submit" value="${rb.yes}"/>
|
||||
<input class="btn btn-default btn-lg" name="cancel" id="kc-cancel" type="submit" value="${rb.no}"/>
|
||||
<input class="btn btn-primary btn-lg" name="accept" id="kc-login" type="submit" value="${rb.doYes}"/>
|
||||
<input class="btn btn-default btn-lg" name="cancel" id="kc-cancel" type="submit" value="${rb.doNo}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout displayInfo=true; section>
|
||||
<#if section = "title">
|
||||
${rb.emailForgotHeader}
|
||||
${rb.emailForgotTitle}
|
||||
<#elseif section = "header">
|
||||
${rb.emailForgotHeader}
|
||||
${rb.emailForgotTitle}
|
||||
<#elseif section = "form">
|
||||
<form id="kc-reset-password-form" class="${properties.kcFormClass!}" action="${url.loginPasswordResetUrl}" method="post">
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
|
@ -23,7 +23,7 @@
|
|||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<input class="btn btn-primary btn-lg" type="submit" value="${rb.submit}"/>
|
||||
<input class="btn btn-primary btn-lg" type="submit" value="${rb.doSubmit}"/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout; section>
|
||||
<#if section = "title">
|
||||
${rb.loginTitle} ${realm.name}
|
||||
${formatter.format(rb.loginTitle,realm.name)}
|
||||
<#elseif section = "header">
|
||||
${rb.loginTitle} <strong>${realm.name}</strong>
|
||||
${formatter.format(rb.loginTitleHtml,realm.name)}
|
||||
<#elseif section = "form">
|
||||
<form id="kc-totp-login-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
|
||||
<input id="username" name="username" value="${login.username!''}" type="hidden" />
|
||||
|
@ -27,8 +27,8 @@
|
|||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<div class="${properties.kcFormButtonsWrapperClass!}">
|
||||
<input class="btn btn-primary btn-lg" name="login" id="kc-login" type="submit" value="${rb.logIn}"/>
|
||||
<input class="btn btn-default btn-lg" name="cancel" id="kc-cancel" type="submit" value="${rb.cancel}"/>
|
||||
<input class="btn btn-primary btn-lg" name="login" id="kc-login" type="submit" value="${rb.doLogIn}"/>
|
||||
<input class="btn btn-default btn-lg" name="cancel" id="kc-cancel" type="submit" value="${rb.doCancel}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout displayInfo=true; section>
|
||||
<#if section = "title">
|
||||
${rb.emailUpdateHeader}
|
||||
${rb.updatePasswordTitle}
|
||||
<#elseif section = "header">
|
||||
${rb.emailUpdateHeader}
|
||||
${rb.updatePasswordTitle}
|
||||
<#elseif section = "form">
|
||||
<form id="kc-passwd-update-form" class="${properties.kcFormClass!}" action="${url.loginUpdatePasswordUrl}" method="post">
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
|
@ -31,7 +31,7 @@
|
|||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<input class="btn btn-primary btn-lg" type="submit" value="${rb.submit}"/>
|
||||
<input class="btn btn-primary btn-lg" type="submit" value="${rb.doSubmit}"/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<input class="btn btn-primary btn-lg" type="submit" value="${rb.submit}" />
|
||||
<input class="btn btn-primary btn-lg" type="submit" value="${rb.doSubmit}" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
${rb.emailVerifyTitle}
|
||||
<#elseif section = "form">
|
||||
<p class="instruction">
|
||||
${rb.emailVerifyInstr}
|
||||
${rb.emailVerifyInstruction1}
|
||||
</p>
|
||||
<p class="instruction">${rb.emailVerifyInstrQ}
|
||||
<a href="${url.loginEmailVerificationUrl}">${rb.emailVerifyClick}</a> ${rb.emailVerifyResend}
|
||||
<p class="instruction">
|
||||
${rb.emailVerifyInstruction2} <a href="${url.loginEmailVerificationUrl}">${rb.doClickHere}</a> ${rb.emailVerifyInstruction3}
|
||||
</p>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -2,15 +2,15 @@
|
|||
<@layout.registrationLayout displayInfo=social.displayInfo; section>
|
||||
<#if section = "title">
|
||||
<#if client.application>
|
||||
${rb.loginTitle} ${realm.name}
|
||||
${formatter.format(rb.loginTitle,(realm.name!''))}
|
||||
<#elseif client.oauthClient>
|
||||
${realm.name} ${rb.loginOauthTitle}
|
||||
${formatter.format(rb.loginOauthTitle,(realm.name!''))}
|
||||
</#if>
|
||||
<#elseif section = "header">
|
||||
<#if client.application>
|
||||
${rb.loginTitle} <strong>${(realm.name)!''}</strong>
|
||||
${formatter.format(rb.loginTitleHtml,(realm.name!''))}
|
||||
<#elseif client.oauthClient>
|
||||
Temporary access for <strong>${(realm.name)!''}</strong> requested by <strong>${(client.clientId)!''}</strong>.
|
||||
${formatter.format(rb.loginOauthTitleHtml,(realm.name!''), (client.clientId!''))}
|
||||
</#if>
|
||||
<#elseif section = "form">
|
||||
<#if realm.password>
|
||||
|
@ -41,24 +41,24 @@
|
|||
<div class="checkbox">
|
||||
<label>
|
||||
<#if login.rememberMe??>
|
||||
<input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3" checked> Remember Me
|
||||
<input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3" checked> ${rb.rememberMe}
|
||||
<#else>
|
||||
<input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3"> Remember Me
|
||||
<input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3"> ${rb.rememberMe}
|
||||
</#if>
|
||||
</label>
|
||||
</div>
|
||||
</#if>
|
||||
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||
<#if realm.resetPasswordAllowed>
|
||||
<span>${rb.loginForgot} <a href="${url.loginPasswordResetUrl}">${rb.password}</a>?</span>
|
||||
<span><a href="${url.loginPasswordResetUrl}">${rb.doForgotPassword}</a></span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<div class="${properties.kcFormButtonsWrapperClass!}">
|
||||
<input class="btn btn-primary btn-lg" name="login" id="kc-login" type="submit" value="${rb.logIn}"/>
|
||||
<input class="btn btn-default btn-lg" name="cancel" id="kc-cancel" type="submit" value="${rb.cancel}"/>
|
||||
<input class="btn btn-primary btn-lg" name="login" id="kc-login" type="submit" value="${rb.doLogIn}"/>
|
||||
<input class="btn btn-default btn-lg" name="cancel" id="kc-cancel" type="submit" value="${rb.doCancel}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -75,7 +75,7 @@
|
|||
<#elseif section = "info" >
|
||||
<#if realm.password && realm.registrationAllowed>
|
||||
<div id="kc-registration">
|
||||
<span>${rb.noAccount} <a href="${url.registrationUrl}">${rb.register}</a></span>
|
||||
<span>${rb.noAccount} <a href="${url.registrationUrl}">${rb.doRegister}</a></span>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
|
|
|
@ -1,126 +0,0 @@
|
|||
logIn=Log in
|
||||
logInTo=Log in to
|
||||
logInWith=Log in with
|
||||
noAccount=New user?
|
||||
register=Register
|
||||
registerWith=Register with
|
||||
allRequired=All fields are required
|
||||
alreadyHaveAccount=Already have an account?
|
||||
street=Street
|
||||
locality=City or Locality
|
||||
region=State, Province, or Region
|
||||
postal_code=Zip or Postal code
|
||||
country=Country
|
||||
|
||||
poweredByKeycloak=Powered by Keycloak
|
||||
|
||||
username=Username
|
||||
usernameOrEmail=Username or email
|
||||
fullName=Full name
|
||||
firstName=First name
|
||||
lastName=Last name
|
||||
email=Email
|
||||
password=Password
|
||||
rememberMe=Remember me
|
||||
passwordConfirm=Confirm password
|
||||
passwordNew=New Password
|
||||
passwordNewConfirm=New Password confirmation
|
||||
passwordUpdated=Password updated
|
||||
cancel=Cancel
|
||||
accept=Accept
|
||||
submit=Submit
|
||||
yes=Yes
|
||||
no=No
|
||||
|
||||
authenticatorCode=One-time code
|
||||
clientCertificate=Client Certificate
|
||||
|
||||
invalidUser=Invalid username or password.
|
||||
invalidPassword=Invalid username or password.
|
||||
invalidEmail=Invalid email address
|
||||
accountDisabled=Account is disabled, contact admin
|
||||
accountTemporarilyDisabled=Account is temporarily disabled, contact admin or try again later
|
||||
expiredCode=Login timeout. Please login again
|
||||
|
||||
missingFirstName=Please specify first name
|
||||
missingLastName=Please specify last name
|
||||
missingEmail=Please specify email
|
||||
missingUsername=Please specify username
|
||||
missingPassword=Please specify password.
|
||||
notMatchPassword=Passwords don't match
|
||||
missingTotp=Please specify authenticator code
|
||||
|
||||
invalidPasswordExisting=Invalid existing password
|
||||
invalidPasswordConfirm=Password confirmation doesn't match
|
||||
invalidTotp=Invalid authenticator code
|
||||
|
||||
successTotp=Mobile authenticator configured.
|
||||
successTotpRemoved=Mobile authenticator removed.
|
||||
|
||||
usernameExists=Username already exists
|
||||
emailExists=Email already exists
|
||||
|
||||
federatedIdentityEmailExists=User with email already exists. Please login to account management to link the account.
|
||||
federatedIdentityUsernameExists=User with username already exists. Please login to account management to link the account.
|
||||
federatedIdentityRegistrationEmailMissing=Email is not provided. Use another provider to create account please.
|
||||
|
||||
loginTitle=Log in to
|
||||
loginOauthTitle=Temporary access.
|
||||
loginOauthTitleHtml=Temporary access requested. Login to grant access.
|
||||
loginForgot=Forgot
|
||||
|
||||
loginTotpTitle=Mobile Authenticator Setup
|
||||
loginTotpStep1=Install <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> or <a href="http://code.google.com/p/google-authenticator/" target="_blank">Google Authenticator</a> on your mobile
|
||||
loginTotpStep2=Open the application and scan the barcode or enter the key
|
||||
loginTotpStep3=Enter the one-time code provided by the application and click Submit to finish the setup
|
||||
loginTotpOneTime=One-time code
|
||||
|
||||
loginProfileTitle=Update Account Information
|
||||
loginProfileWarning=Your account is not enabled because you need to update your account information.
|
||||
loginProfileWarningFollow=Please follow the steps below.
|
||||
loginProfileError=Some required fields are empty or incorrect.
|
||||
loginProfileErrorSteps=Please correct the fields in red.
|
||||
|
||||
oauthGrantTitle=OAuth Grant
|
||||
oauthGrantTitleHtml=Temporary access requested
|
||||
oauthGrantTerms=Keycloak Central Login and Google will use this information in accordance with their respective terms of service and privacy policies.
|
||||
oauthGrantRequest=Do you grant these access privileges?
|
||||
oauthGrantLoginRequest=Do you grant access?
|
||||
|
||||
emailVerifyTitle=Email verification
|
||||
emailVerifyInstr=An email with instructions to verify your email address has been sent to you.
|
||||
emailVerifyInstrQ=Haven't received a verification code in your email?
|
||||
emailVerifyClick=Click here
|
||||
emailVerifyResend=to re-send the email.
|
||||
emailVerified=Email verified
|
||||
|
||||
error=A system error has occured, contact admin
|
||||
errorTitle=We're sorry...
|
||||
errorTitleHtml=We're <strong>sorry</strong> ...
|
||||
errorGenericMsg=Something happened and we could not process your request.
|
||||
actionWarningHeader=Your account is not enabled.
|
||||
actionTotpWarning=You need to set up Mobile Authenticator to activate your account.
|
||||
actionProfileWarning=You need to update your user profile to activate your account.
|
||||
actionPasswordWarning=You need to change your password to activate your account.
|
||||
actionEmailWarning=You need to verify your email address to activate your account.
|
||||
actionFollow=Please fill in the fields below.
|
||||
|
||||
errorKerberosLogin=Kerberos ticket not available. Authenticate with password.
|
||||
|
||||
successHeader=Success!
|
||||
errorHeader=Error!
|
||||
|
||||
# Forgot password part
|
||||
|
||||
emailForgotHeader=Forgot Your Password?
|
||||
backToLogin=« Back to Login
|
||||
backToApplication=« Back to Application
|
||||
emailUpdateHeader=Update password
|
||||
emailSent=You should receive an email shortly with further instructions.
|
||||
emailSendError=Failed to send email, please try again later
|
||||
emailError=Invalid email.
|
||||
emailErrorInfo=Please, fill in the fields again.
|
||||
emailInstruction=Enter your username or email address and we will send you instructions on how to create a new password.
|
||||
|
||||
accountUpdated=Your account has been updated
|
||||
accountPasswordUpdated=Your password has been updated
|
|
@ -0,0 +1,148 @@
|
|||
doLogIn=Anmelden
|
||||
doRegister=Registrieren
|
||||
doCancel=Abbrechen
|
||||
doSubmit=Absenden
|
||||
doYes=Ja
|
||||
doNo=Nein
|
||||
doForgotPassword=Passwort vergessen?
|
||||
doClickHere=hier klicken
|
||||
|
||||
registerWithTitle=Registrierung bei {0}
|
||||
registerWithTitleHtml=Registrierung bei <strong>{0}</strong>
|
||||
loginTitle=Anmeldung bei {0}
|
||||
loginTitleHtml=Anmeldung bei <strong>{0}</strong>
|
||||
loginOauthTitle=Temporärer zugriff auf {0}
|
||||
loginOauthTitleHtml=Temporärer zugriff auf <strong>{0}<strong> angefordert von <strong>{1}</strong>.
|
||||
loginTotpTitle=Mobile Authentifizierung Einrichten
|
||||
loginProfileTitle=Benutzer Konto Informatinen aktualisieren
|
||||
oauthGrantTitle=OAuth gewähren
|
||||
oauthGrantTitleHtml=Temporärer zugriff auf <strong>{0}<strong> angefordert von <strong>{1}</strong>.
|
||||
errorTitle=Es tut uns leid...
|
||||
errorTitleHtml=Es tut uns leid...
|
||||
emailVerifyTitle=E-Mail verifizieren
|
||||
emailForgotTitle=Passwort vergessen?
|
||||
updatePasswordTitle=Passwort aktualisieren
|
||||
|
||||
noAccount=Neuer Benutzer?
|
||||
username=Benutzername
|
||||
usernameOrEmail=Benutzername oder E-Mail
|
||||
firstName=Vorname
|
||||
fullName=Name
|
||||
lastName=Nachname
|
||||
email=E-Mail
|
||||
password=Passwort
|
||||
passwordConfirm=Passwort bestätigen
|
||||
passwordNew=Neues Passwort
|
||||
passwordNewConfirm=Neues Passwort bestätigen
|
||||
rememberMe=Angemeldet bleiben
|
||||
authenticatorCode=One-time Code
|
||||
street=Strasse
|
||||
region=Staat, Provinz, Region
|
||||
postal_code=PLZ
|
||||
locality=Stadt oder Ortschaft
|
||||
country=Land
|
||||
|
||||
loginTotpStep1=Installieren Sie <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> oder <a href="http://code.google.com/p/google-authenticator/" target="_blank">Google Authenticator</a> auf Ihrem Smartphone.
|
||||
loginTotpStep2=Öffnen Sie die Applikation und scannen Sie den Barcode oder geben sie den Code ein.
|
||||
loginTotpStep3=Geben Sie den One-time Code welcher die Applikation generiert hat ein und klicken Sie auf Absenden.
|
||||
loginTotpOneTime=One-time Code
|
||||
|
||||
oauthGrantRequest=Do you grant these access privileges?
|
||||
|
||||
emailVerifyInstruction1=Ein E-Mail mit weitern Anweisungen wurde an Sie versendet.
|
||||
emailVerifyInstruction2=Falls Sie kein E-Mail erhalten haben, dann können Sie
|
||||
emailVerifyInstruction3=um ein neues E-Mail zu verschicken.
|
||||
|
||||
backToLogin=« Zurück zur Anmeldung
|
||||
backToApplication=« Zurück zur Applikation
|
||||
|
||||
emailInstruction=Geben Sie ihren Benutzernamen oder E-Mail Adresse ein und klicken Sie auf Absenden. Danach werden wir ihnen ein E-Mail mit weiteren Instruktionen zusenden.
|
||||
|
||||
invalidUserMessage=Ungültiger Benutzername oder Passwort.
|
||||
invalidEmailMessage=Ungültige E-Mail Adresse.
|
||||
accountDisabledMessage=Benutzerkonto ist gesperrt, bitte kontaktieren Sie den Admin.
|
||||
accountTemporarilyDisabledMessage=Benutzerkonto ist temporär gespert, bitte kontaktieren Sie den Admin oder versuchen Sie es später nocheinmal.
|
||||
expiredCodeMessage=Zeitüberschreitung bei der Anmeldung. Bitter melden Sie sich erneut an.
|
||||
|
||||
missingFirstNameMessage=Bitte geben Sie einen Vornamen ein.
|
||||
missingLastNameMessage=Bitte geben Sie einen Nachnamen ein.
|
||||
missingEmailMessage=Bitte geben Sie eine E-Mail Adresse ein.
|
||||
missingUsernameMessage=Bitte geben Sie einen Benutzernamen ein.
|
||||
missingPasswordMessage=Bitte geben Sie ein Passwort ein.
|
||||
missingTotpMessage=Bitte geben Sie den One-time Code ein.
|
||||
notMatchPasswordMessage=Passwörter sind nicht identisch.
|
||||
|
||||
invalidPasswordExistingMessage=Das aktuelle Passwort is ungültig.
|
||||
invalidPasswordConfirmMessage=Die Passwort bestätigung ist nicht identisch.
|
||||
invalidTotpMessage=Ungültiger One-time Code.
|
||||
|
||||
usernameExistsMessage=Benutzermane exisitert bereits.
|
||||
emailExistsMessage=E-Mail existiert bereits.
|
||||
|
||||
federatedIdentityRegistrationEmailMissingMessage=Die E-Mail Adresse ist nicht vorhanden. Bitte verwenden Sie einen anderen Provider um das Benutzerkonto zu erstellen.
|
||||
federatedIdentityEmailExistsMessage=Es exisitert bereits ein Benutzer mit dieser E-Mail Adresse. Bitte melden Sie sich bei der Benutzerverwaltung an um das Benutzerkonto zu verknüpfen.
|
||||
federatedIdentityUsernameExistsMessage=Es exisitert bereits ein Benutzer mit diesem Benutzernamen. Bitte melden Sie sich bei der Benutzerverwaltung an um das Benutzerkonto zu verknüpfen.
|
||||
|
||||
configureTotpMessage=Sie müssen eine Mobile Authentifizierung einrichten um das Benutzerkonto zu aktivieren.
|
||||
updateProfileMessage=Sie müssen ihr Benutzerkonto aktualisieren um das Benutzerkonto zu aktivieren.
|
||||
updatePasswordMessage=Sie müssen ihr Passwort ändern um das Benutzerkonto zu aktivieren.
|
||||
verifyEmailMessage=Sie müssen ihre E-Mail Adresse verifizieren um das Benutzerkonto zu aktivieren.
|
||||
|
||||
emailSentMessage=Sie sollten in kürze ein E-Mail mit weiteren Instruktionen erhalten.
|
||||
emailSendErrorMessage=Das E-Mail konnte nicht versendet werden, bitte versuchen Sie es später nochmals.
|
||||
|
||||
accountUpdatedMessage=Ihr Benutzerkonto wurde aktualisiert.
|
||||
accountPasswordUpdatedMessage=Ihr Passwort wurde aktualisiert.
|
||||
|
||||
noAccessMessage=Kein Zugriff
|
||||
|
||||
invalidPasswordMinLengthMessage=Ungültiges Passwort: minimum länge {0}.
|
||||
invalidPasswordMinDigitsMessage=Ungültiges Passwort: muss mindestens {0} Zahl(en) beinhalten.
|
||||
invalidPasswordMinLowerCaseCharsMessage=Ungültiges Passwort: muss mindestens {0} Kleinbuchstaben beinhalten.
|
||||
invalidPasswordMinUpperCaseCharsMessage=Ungültiges Passwort: muss mindestens {0} Grossbuchstaben beinhalten.
|
||||
invalidPasswordMinSpecialCharsMessage=Ungültiges Passwort: muss mindestens {0} Spezialzeichen beinhalten.
|
||||
invalidPasswordNotUsernameMessage=Ungültiges Passwort\: darf nicht gleich sein wie Benutzername.
|
||||
|
||||
failedToProcessResponseMessage=Konnte Response nicht verarbeiten.
|
||||
httpsRequiredMessage=HTTPS erforderlich.
|
||||
realmNotEnabledMessage=Realm nicht aktiviert.
|
||||
invalidRequestMessage=Ungültiger Request.
|
||||
unknownLoginRequesterMessage=Ungültiger login requester
|
||||
loginRequesterNotEnabledMessage=Login requester nicht aktiviert.
|
||||
bearerOnlyMessage=Bearer-only Applikationen könne sich nicht via Browser anmelden.
|
||||
directGrantsOnlyMessage=Direct-grants-only Clients könne sich nicht via Browser anmelden.
|
||||
invalidRedirectUriMessage=Ungültige redirect uri.
|
||||
unsupportedNameIdFormatMessage=Nicht unterstütztes NameIDFormat.
|
||||
invlidRequesterMessage=Ungültiger requester.
|
||||
registrationNotAllowedMessage=Registrierung nicht erlaubt.
|
||||
|
||||
permissionNotApprovedMessage=Berechtigung nicht bestätigt.
|
||||
noRelayStateInResponseMessage=Kein relay state in der Antwort von dem Identity Provider [{0}].
|
||||
identityProviderAlreadyLinkedMessage=Die Identität welche von dem Identity Provider [{0}] zurückgegeben wurde, ist bereits mit einem anderen Benutzer verknüpft.
|
||||
insufficientPermissionMessage=Nicht genügtend Rechte um die Identität zu verknüpfen.
|
||||
couldNotProceedWithAuthenticationRequestMessage=Konnte den Authentifizierungs Request nicht weiter verarbeiten.
|
||||
couldNotObtainTokenMessage=Konnte kein token vom Identity Provider [{0}] entnehmen.
|
||||
unexpectedErrorRetrievingTokenMessage=Unerwarteter Fehler während dem Empfang des Token von dem Identity Provider [{0}].
|
||||
unexpectedErrorHandlingResponseMessage=Unerwarteter Fehler während der bearbeitung des Respons vom Identity Provider [{0}].
|
||||
identityProviderAuthenticationFailedMessage=Authentifizierung Fehlgeschlagen. Konnte sich mit dem Identity Provider [{0}] nicht authentifizieren.
|
||||
couldNotSendAuthenticationRequestMessage=Konnte Authentifizierungs Request nicht an den Identity Provider [{0}] schiken.
|
||||
unexpectedErrorHandlingRequestMessage=Unerwarteter Fehler während der bearbeitung des Requests zum Identity Provider [{0}].
|
||||
invalidAccessCodeMessage=Ungültiger Access-Code.
|
||||
sessionNotActiveMessage=Session nicht aktiv.
|
||||
unknownCodeMessage=Unbekannter Code, bitte melden Sie sich erneut über die Applikation an.
|
||||
invalidCodeMessage=Ungültiger Code, bitte melden Sie sich erneut über die Applikation an.
|
||||
identityProviderUnexpectedErrorMessage=Unerwarteter Fehler während der Authentifizierung mit dem Identity Provider.
|
||||
identityProviderNotFoundMessage=Konnte kein Identity Provider mit der Idenität [{0}] finden.
|
||||
realmSupportsNoCredentialsMessage=Realm [{0}] unterstützt keine Credential Typen..
|
||||
identityProviderNotUniqueMessage=Realm [{0}] unterstütz mehrere Identity Providers.
|
||||
|
||||
invalidParameterMessage=Invalid parameter\: {0}
|
||||
missingParameterMessage=Missing parameter\: {0}
|
||||
clientNotFoundMessage=Client not found.
|
||||
|
||||
emailVerifiedMessage=Ihr E-Mail Addresse wurde erfolgreich verifiziert.
|
||||
|
||||
locale_de=Deutsch
|
||||
locale_en=Englisch
|
||||
|
||||
poweredByKeycloak=Powered by Keycloak
|
|
@ -0,0 +1,146 @@
|
|||
doLogIn=Log in
|
||||
doRegister=Register
|
||||
doCancel=Cancel
|
||||
doSubmit=Submit
|
||||
doYes=Yes
|
||||
doNo=No
|
||||
doForgotPassword=Forgot Password?
|
||||
doClickHere=Click here
|
||||
|
||||
registerWithTitle=Register with {0}
|
||||
registerWithTitleHtml=Register with <strong>{0}</strong>
|
||||
loginTitle=Log in to {0}
|
||||
loginTitleHtml=Log in to <strong>{0}</strong>
|
||||
loginOauthTitle=Temporary access for {0}
|
||||
loginOauthTitleHtml=Temporary access for <strong>{0}<strong> requested by <strong>{1}</strong>.
|
||||
loginTotpTitle=Mobile Authenticator Setup
|
||||
loginProfileTitle=Update Account Information
|
||||
oauthGrantTitle=OAuth Grant
|
||||
oauthGrantTitleHtml=Temporary access for <strong>{0}</strong> requested by <strong>{1}</strong>.
|
||||
errorTitle=We're sorry...
|
||||
errorTitleHtml=We're <strong>sorry</strong> ...
|
||||
emailVerifyTitle=Email verification
|
||||
emailForgotTitle=Forgot Your Password?
|
||||
updatePasswordTitle=Update password
|
||||
|
||||
noAccount=New user?
|
||||
username=Username
|
||||
usernameOrEmail=Username or email
|
||||
firstName=First name
|
||||
fullName=Full name
|
||||
lastName=Last name
|
||||
email=Email
|
||||
password=Password
|
||||
passwordConfirm=Confirm password
|
||||
passwordNew=New Password
|
||||
passwordNewConfirm=New Password confirmation
|
||||
rememberMe=Remember me
|
||||
authenticatorCode=One-time code
|
||||
street=Street
|
||||
locality=City or Locality
|
||||
region=State, Province, or Region
|
||||
postal_code=Zip or Postal code
|
||||
country=Country
|
||||
|
||||
loginTotpStep1=Install <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> or <a href="http://code.google.com/p/google-authenticator/" target="_blank">Google Authenticator</a> on your mobile
|
||||
loginTotpStep2=Open the application and scan the barcode or enter the key
|
||||
loginTotpStep3=Enter the one-time code provided by the application and click Submit to finish the setup
|
||||
loginTotpOneTime=One-time code
|
||||
|
||||
oauthGrantRequest=Do you grant these access privileges?
|
||||
|
||||
emailVerifyInstruction1=An email with instructions to verify your email address has been sent to you.
|
||||
emailVerifyInstruction2=Haven't received a verification code in your email?
|
||||
emailVerifyInstruction3=to re-send the email.
|
||||
|
||||
backToLogin=« Back to Login
|
||||
|
||||
emailInstruction=Enter your username or email address and we will send you instructions on how to create a new password.
|
||||
|
||||
invalidUserMessage=Invalid username or password.
|
||||
invalidEmailMessage=Invalid email address.
|
||||
accountDisabledMessage=Account is disabled, contact admin.
|
||||
accountTemporarilyDisabledMessage=Account is temporarily disabled, contact admin or try again later.
|
||||
expiredCodeMessage=Login timeout. Please login again.
|
||||
|
||||
missingFirstNameMessage=Please specify first name.
|
||||
missingLastNameMessage=Please specify last name.
|
||||
missingEmailMessage=Please specify email.
|
||||
missingUsernameMessage=Please specify username.
|
||||
missingPasswordMessage=Please specify password.
|
||||
missingTotpMessage=Please specify authenticator code.
|
||||
notMatchPasswordMessage=Passwords don't match.
|
||||
|
||||
invalidPasswordExistingMessage=Invalid existing password.
|
||||
invalidPasswordConfirmMessage=Password confirmation doesn't match.
|
||||
invalidTotpMessage=Invalid authenticator code.
|
||||
|
||||
usernameExistsMessage=Username already exists.
|
||||
emailExistsMessage=Email already exists.
|
||||
|
||||
federatedIdentityEmailExistsMessage=User with email already exists. Please login to account management to link the account.
|
||||
federatedIdentityUsernameExistsMessage=User with username already exists. Please login to account management to link the account.
|
||||
|
||||
configureTotpMessage=You need to set up Mobile Authenticator to activate your account.
|
||||
updateProfileMessage=You need to update your user profile to activate your account.
|
||||
updatePasswordMessage=You need to change your password to activate your account.
|
||||
verifyEmailMessage=You need to verify your email address to activate your account.
|
||||
|
||||
emailSentMessage=You should receive an email shortly with further instructions.
|
||||
emailSendErrorMessage=Failed to send email, please try again later
|
||||
|
||||
accountUpdatedMessage=Your account has been updated
|
||||
accountPasswordUpdatedMessage=Your password has been updated
|
||||
|
||||
noAccessMessage=No access
|
||||
|
||||
invalidPasswordMinLengthMessage=Invalid password: minimum length {0}
|
||||
invalidPasswordMinDigitsMessage=Invalid password: must contain at least {0} numerical digits
|
||||
invalidPasswordMinLowerCaseCharsMessage=Invalid password: must contain at least {0} lower case characters
|
||||
invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least {0} upper case characters
|
||||
invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters
|
||||
invalidPasswordNotUsernameMessage=Invalid password\: must not be equal to the username
|
||||
|
||||
failedToProcessResponseMessage=Failed to process response
|
||||
httpsRequiredMessage=HTTPS required
|
||||
realmNotEnabledMessage=Realm not enabled
|
||||
invalidRequestMessage=Invalid Request
|
||||
unknownLoginRequesterMessage=Unknown login requester
|
||||
loginRequesterNotEnabledMessage=Login requester not enabled
|
||||
bearerOnlyMessage=Bearer-only applications are not allowed to initiate browser login
|
||||
directGrantsOnlyMessage=Direct-grants-only clients are not allowed to initiate browser login
|
||||
invalidRedirectUriMessage=Invalid redirect uri
|
||||
unsupportedNameIdFormatMessage=Unsupported NameIDFormat
|
||||
invlidRequesterMessage=Invalid requester
|
||||
registrationNotAllowedMessage=Registration not allowed
|
||||
|
||||
permissionNotApprovedMessage=Permission not approved.
|
||||
noRelayStateInResponseMessage=No relay state in response from identity provider [{0}].
|
||||
identityProviderAlreadyLinkedMessage=The identity returned by the identity provider [{0}] is already linked to another user.
|
||||
insufficientPermissionMessage=Insufficient permissions to link identities.
|
||||
couldNotProceedWithAuthenticationRequestMessage=Could not proceed with authentication request to identity provider.
|
||||
couldNotObtainTokenMessage=Could not obtain token from identity provider [{0}].
|
||||
unexpectedErrorRetrievingTokenMessage=Unexpected error when retrieving token from identity provider [{0}].
|
||||
unexpectedErrorHandlingResponseMessage=Unexpected error when handling response from identity provider [{0}].
|
||||
identityProviderAuthenticationFailedMessage=Authentication failed. Could not authenticate with identity provider [{0}].
|
||||
couldNotSendAuthenticationRequestMessage=Could not send authentication request to identity provider [{0}].
|
||||
unexpectedErrorHandlingRequestMessage=Unexpected error when handling authentication request to identity provider [{0}].
|
||||
invalidAccessCodeMessage=Invalid access code.
|
||||
sessionNotActiveMessage=Session not active.
|
||||
unknownCodeMessage=Unknown code, please login again through your application.
|
||||
invalidCodeMessage=Invalid code, please login again through your application.
|
||||
identityProviderUnexpectedErrorMessage=Unexpected error when authenticating with identity provider
|
||||
identityProviderNotFoundMessage=Could not find an identity provider with the identifier [{0}].
|
||||
realmSupportsNoCredentialsMessage=Realm [{0}] does not support any credential type.
|
||||
identityProviderNotUniqueMessage=Realm [{0}] supports multiple identity providers. Could not determine which identity provider should be used to authenticate with.
|
||||
emailVerifiedMessage=Your email address has been verified.
|
||||
|
||||
locale_de=German
|
||||
locale_en=English
|
||||
|
||||
poweredByKeycloak=Powered by Keycloak
|
||||
backToApplication=« Back to Application
|
||||
missingParameterMessage=Missing parameters\: {0}
|
||||
clientNotFoundMessage=Client not found.
|
||||
invalidParameterMessage=Invalid parameter\: {0}
|
||||
federatedIdentityRegistrationEmailMissingMessage=Email is not provided. Use another provider to create account please.
|
|
@ -1,9 +1,9 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout; section>
|
||||
<#if section = "title">
|
||||
${rb.registerWith} ${realm.name}
|
||||
${formatter.format(rb.registerWithTitle,(realm.name!''))}
|
||||
<#elseif section = "header">
|
||||
${rb.registerWith} <strong>${realm.name}</strong>
|
||||
${formatter.format(rb.registerWithTitleHtml,(realm.name!''))}
|
||||
<#elseif section = "form">
|
||||
<form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post">
|
||||
<#if !realm.registrationEmailAsUsername>
|
||||
|
@ -116,7 +116,7 @@
|
|||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<input class="btn btn-primary btn-lg" type="submit" value="${rb.register}"/>
|
||||
<input class="btn btn-primary btn-lg" type="submit" value="${rb.doRegister}"/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -21,6 +21,19 @@
|
|||
<script src="${url.resourcesPath}/${script}" type="text/javascript"></script>
|
||||
</#list>
|
||||
</#if>
|
||||
<#if realm.internationalizationEnabled>
|
||||
<script type="text/javascript">
|
||||
window.onload = function () {
|
||||
var select = document.querySelector(".kc-locale-select");
|
||||
select.onchange = function (event) {
|
||||
document.cookie = "KEYCLOAK_LOCALE=" + select.value+"; path=${url.localeCookiePath}";
|
||||
setTimeout(function () {
|
||||
window.location.reload();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</#if>
|
||||
</head>
|
||||
|
||||
<body class="${properties.kcBodyClass!}">
|
||||
|
@ -31,6 +44,15 @@
|
|||
|
||||
<div id="kc-header" class="${properties.kcHeaderClass!}">
|
||||
<div id="kc-header-wrapper" class="${properties.kcHeaderWrapperClass!}"><#nested "header"></div>
|
||||
<#if realm.internationalizationEnabled>
|
||||
<div id="kc-locale-wrapper" class="${properties.kcLocaleWrapperClass!}">
|
||||
<select class="kc-locale-select">
|
||||
<#list realm.supportedLocales as l>
|
||||
<option value="${l}" <#if locale.toLanguageTag()==l>selected="selected"</#if>>${rb["locale_" + l]}</option>
|
||||
</#list>
|
||||
</select>
|
||||
</div>
|
||||
</#if>
|
||||
</div>
|
||||
|
||||
<#if displayMessage && message?has_content>
|
||||
|
|
|
@ -5,9 +5,12 @@ import org.keycloak.email.EmailException;
|
|||
import org.keycloak.email.EmailProvider;
|
||||
import org.keycloak.email.freemarker.beans.EventBean;
|
||||
import org.keycloak.events.Event;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.freemarker.FreeMarkerUtil;
|
||||
import org.keycloak.freemarker.LocaleHelper;
|
||||
import org.keycloak.freemarker.Theme;
|
||||
import org.keycloak.freemarker.ThemeProvider;
|
||||
import org.keycloak.freemarker.beans.TextFormatterBean;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
@ -17,10 +20,7 @@ import javax.mail.Session;
|
|||
import javax.mail.Transport;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -56,7 +56,7 @@ public class FreeMarkerEmailProvider implements EmailProvider {
|
|||
Map<String, Object> attributes = new HashMap<String, Object>();
|
||||
attributes.put("event", new EventBean(event));
|
||||
|
||||
send("passwordResetSubject", "event-" + event.getType().toString().toLowerCase() + ".ftl", attributes);
|
||||
send(toCamelCase(event.getType()) + "Subject", "event-" + event.getType().toString().toLowerCase() + ".ftl", attributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -81,8 +81,12 @@ public class FreeMarkerEmailProvider implements EmailProvider {
|
|||
try {
|
||||
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
|
||||
Theme theme = themeProvider.getTheme(realm.getEmailTheme(), Theme.Type.EMAIL);
|
||||
|
||||
String subject = theme.getMessages().getProperty(subjectKey);
|
||||
Locale locale = LocaleHelper.getLocale(realm, user);
|
||||
attributes.put("locale", locale);
|
||||
Properties rb = theme.getMessages(locale);
|
||||
attributes.put("rb", rb);
|
||||
attributes.put("formatter", new TextFormatterBean(locale));
|
||||
String subject = rb.getProperty(subjectKey);
|
||||
String body = freeMarker.processTemplate(attributes, template, theme);
|
||||
|
||||
send(subject, body);
|
||||
|
@ -150,4 +154,12 @@ public class FreeMarkerEmailProvider implements EmailProvider {
|
|||
public void close() {
|
||||
}
|
||||
|
||||
private String toCamelCase(EventType event){
|
||||
StringBuilder sb = new StringBuilder("event");
|
||||
for(String s : event.name().toString().toLowerCase().split("_")){
|
||||
sb.append(s.substring(0,1).toUpperCase()).append(s.substring(1));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import org.keycloak.models.RoleModel;
|
|||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
@ -22,6 +23,8 @@ public interface LoginFormsProvider extends Provider {
|
|||
|
||||
public LoginFormsProvider setUriInfo(UriInfo uriInfo);
|
||||
|
||||
public LoginFormsProvider setHttpHeaders(HttpHeaders httpHeaders);
|
||||
|
||||
public Response createResponse(UserModel.RequiredAction action);
|
||||
|
||||
public Response createLogin();
|
||||
|
@ -45,11 +48,11 @@ public interface LoginFormsProvider extends Provider {
|
|||
public LoginFormsProvider setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String,RoleModel> resourceRolesRequested);
|
||||
public LoginFormsProvider setAccessRequest(String message);
|
||||
|
||||
public LoginFormsProvider setError(String message);
|
||||
public LoginFormsProvider setError(String message, Object ... parameters);
|
||||
|
||||
public LoginFormsProvider setSuccess(String message);
|
||||
public LoginFormsProvider setSuccess(String message, Object ... parameters);
|
||||
|
||||
public LoginFormsProvider setWarning(String message);
|
||||
public LoginFormsProvider setWarning(String message, Object ... parameters);
|
||||
|
||||
public LoginFormsProvider setUser(UserModel user);
|
||||
|
||||
|
|
|
@ -5,11 +5,8 @@ import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
|
|||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.email.EmailException;
|
||||
import org.keycloak.email.EmailProvider;
|
||||
import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
|
||||
import org.keycloak.freemarker.FreeMarkerException;
|
||||
import org.keycloak.freemarker.FreeMarkerUtil;
|
||||
import org.keycloak.freemarker.Theme;
|
||||
import org.keycloak.freemarker.ThemeProvider;
|
||||
import org.keycloak.freemarker.*;
|
||||
import org.keycloak.freemarker.beans.TextFormatterBean;
|
||||
import org.keycloak.login.LoginFormsPages;
|
||||
import org.keycloak.login.LoginFormsProvider;
|
||||
import org.keycloak.login.freemarker.model.ClientBean;
|
||||
|
@ -33,23 +30,17 @@ import org.keycloak.services.messages.Messages;
|
|||
import org.keycloak.services.resources.LoginActionsService;
|
||||
import org.keycloak.services.resources.flows.Urls;
|
||||
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import javax.ws.rs.core.*;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||
public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(FreeMarkerLoginFormsProvider.class);
|
||||
|
||||
|
@ -63,6 +54,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
private Map<String, String> httpResponseHeaders = new HashMap<String, String>();
|
||||
private String accessRequestMessage;
|
||||
private URI actionUri;
|
||||
private Object[] parameters;
|
||||
|
||||
private String message;
|
||||
private MessageType messageType = MessageType.ERROR;
|
||||
|
@ -80,6 +72,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
|
||||
private UriInfo uriInfo;
|
||||
|
||||
private HttpHeaders httpHeaders;
|
||||
|
||||
public FreeMarkerLoginFormsProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
|
||||
this.session = session;
|
||||
this.freeMarker = freeMarker;
|
||||
|
@ -95,21 +89,27 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginFormsProvider setHttpHeaders(HttpHeaders httpHeaders) {
|
||||
this.httpHeaders = httpHeaders;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Response createResponse(UserModel.RequiredAction action) {
|
||||
String actionMessage;
|
||||
LoginFormsPages page;
|
||||
|
||||
switch (action) {
|
||||
case CONFIGURE_TOTP:
|
||||
actionMessage = Messages.ACTION_WARN_TOTP;
|
||||
actionMessage = Messages.CONFIGURE_TOTP;
|
||||
page = LoginFormsPages.LOGIN_CONFIG_TOTP;
|
||||
break;
|
||||
case UPDATE_PROFILE:
|
||||
actionMessage = Messages.ACTION_WARN_PROFILE;
|
||||
actionMessage = Messages.UPDATE_PROFILE;
|
||||
page = LoginFormsPages.LOGIN_UPDATE_PROFILE;
|
||||
break;
|
||||
case UPDATE_PASSWORD:
|
||||
actionMessage = Messages.ACTION_WARN_PASSWD;
|
||||
actionMessage = Messages.UPDATE_PASSWORD;
|
||||
page = LoginFormsPages.LOGIN_UPDATE_PASSWORD;
|
||||
break;
|
||||
case VERIFY_EMAIL:
|
||||
|
@ -123,10 +123,10 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
session.getProvider(EmailProvider.class).setRealm(realm).setUser(user).sendVerifyEmail(link, expiration);
|
||||
} catch (EmailException e) {
|
||||
logger.error("Failed to send verification email", e);
|
||||
return setError("emailSendError").createErrorPage();
|
||||
return setError(Messages.EMAIL_SENT_ERROR).createErrorPage();
|
||||
}
|
||||
|
||||
actionMessage = Messages.ACTION_WARN_EMAIL;
|
||||
actionMessage = Messages.VERIFY_EMAIL;
|
||||
page = LoginFormsPages.LOGIN_VERIFY_EMAIL;
|
||||
break;
|
||||
default:
|
||||
|
@ -175,8 +175,13 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
}
|
||||
|
||||
Properties messages;
|
||||
Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, httpHeaders);
|
||||
if(locale != null){
|
||||
attributes.put("locale", locale);
|
||||
attributes.put("formatter", new TextFormatterBean(locale));
|
||||
}
|
||||
try {
|
||||
messages = theme.getMessages();
|
||||
messages = theme.getMessages(locale);
|
||||
attributes.put("rb", messages);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Failed to load messages", e);
|
||||
|
@ -184,7 +189,13 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
}
|
||||
|
||||
if (message != null) {
|
||||
attributes.put("message", new MessageBean(messages.containsKey(message) ? messages.getProperty(message) : message, messageType));
|
||||
String formattedMessage;
|
||||
if(messages.containsKey(message)){
|
||||
formattedMessage = new MessageFormat(messages.getProperty(message).replace("'","''"),locale).format(parameters);
|
||||
}else{
|
||||
formattedMessage = message;
|
||||
}
|
||||
attributes.put("message", new MessageBean(formattedMessage, messageType));
|
||||
}
|
||||
if (page == LoginFormsPages.OAUTH_GRANT) {
|
||||
// for some reason Resteasy 2.3.7 doesn't like query params and form params with the same name and will null out the code form param
|
||||
|
@ -233,6 +244,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
for (Map.Entry<String, String> entry : httpResponseHeaders.entrySet()) {
|
||||
builder.header(entry.getKey(), entry.getValue());
|
||||
}
|
||||
LocaleHelper.updateLocaleCookie(builder, locale, realm, uriInfo, Urls.localeCookiePath(baseUri, realm.getName()));
|
||||
return builder.build();
|
||||
} catch (FreeMarkerException e) {
|
||||
logger.error("Failed to process template", e);
|
||||
|
@ -277,21 +289,24 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
return createResponse(LoginFormsPages.CODE);
|
||||
}
|
||||
|
||||
public FreeMarkerLoginFormsProvider setError(String message) {
|
||||
public FreeMarkerLoginFormsProvider setError(String message, Object ... parameters) {
|
||||
this.message = message;
|
||||
this.messageType = MessageType.ERROR;
|
||||
this.parameters = parameters;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FreeMarkerLoginFormsProvider setSuccess(String message) {
|
||||
public FreeMarkerLoginFormsProvider setSuccess(String message, Object ... parameters) {
|
||||
this.message = message;
|
||||
this.messageType = MessageType.SUCCESS;
|
||||
this.parameters = parameters;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FreeMarkerLoginFormsProvider setWarning(String message) {
|
||||
public FreeMarkerLoginFormsProvider setWarning(String message, Object ... parameters) {
|
||||
this.message = message;
|
||||
this.messageType = MessageType.WARNING;
|
||||
this.parameters = parameters;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,8 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.models.RequiredCredentialModel;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
|
@ -60,6 +62,14 @@ public class RealmBean {
|
|||
return realm.isRememberMe();
|
||||
}
|
||||
|
||||
public boolean isInternationalizationEnabled() {
|
||||
return realm.isInternationalizationEnabled();
|
||||
}
|
||||
|
||||
public Set<String> getSupportedLocales(){
|
||||
return realm.getSupportedLocales();
|
||||
}
|
||||
|
||||
public boolean isPassword() {
|
||||
for (RequiredCredentialModel r : realm.getRequiredCredentials()) {
|
||||
if (r.getType().equals(CredentialRepresentation.PASSWORD)) {
|
||||
|
|
|
@ -86,6 +86,10 @@ public class UrlBean {
|
|||
return Urls.loginActionEmailVerification(baseURI, realm).toString();
|
||||
}
|
||||
|
||||
public String getLocaleCookiePath(){
|
||||
return Urls.localeCookiePath(baseURI, realm);
|
||||
}
|
||||
|
||||
public String getOauthAction() {
|
||||
if (this.actionuri != null) {
|
||||
return this.actionuri.getPath();
|
||||
|
|
|
@ -168,13 +168,8 @@ public class AdapterDeploymentContext {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getCodeUrl() {
|
||||
return (this.codeUrl != null) ? this.codeUrl : delegate.getCodeUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRefreshUrl() {
|
||||
return (this.refreshUrl != null) ? this.refreshUrl : delegate.getRefreshUrl();
|
||||
public String getTokenUrl() {
|
||||
return (this.tokenUrl != null) ? this.tokenUrl : delegate.getTokenUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -38,7 +38,7 @@ public class AdapterUtils {
|
|||
return UriUtils.getOrigin(browserRequestURL);
|
||||
case BROWSER_ONLY:
|
||||
// Resolve baseURI from the codeURL (This is already non-relative and based on our hostname)
|
||||
return UriUtils.getOrigin(deployment.getCodeUrl());
|
||||
return UriUtils.getOrigin(deployment.getTokenUrl());
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
|
|
@ -69,8 +69,9 @@ public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticat
|
|||
try {
|
||||
HttpPost post = new HttpPost(
|
||||
KeycloakUriBuilder.fromUri(deployment.getAuthServerBaseUrl())
|
||||
.path(ServiceUrlConstants.TOKEN_SERVICE_DIRECT_GRANT_PATH).build(deployment.getRealm()));
|
||||
.path(ServiceUrlConstants.TOKEN_PATH).build(deployment.getRealm()));
|
||||
java.util.List <NameValuePair> formparams = new java.util.ArrayList <NameValuePair>();
|
||||
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD));
|
||||
formparams.add(new BasicNameValuePair("username", username));
|
||||
formparams.add(new BasicNameValuePair("password", password));
|
||||
|
||||
|
|
|
@ -28,8 +28,7 @@ public class KeycloakDeployment {
|
|||
protected String authServerBaseUrl;
|
||||
protected String realmInfoUrl;
|
||||
protected KeycloakUriBuilder authUrl;
|
||||
protected String codeUrl;
|
||||
protected String refreshUrl;
|
||||
protected String tokenUrl;
|
||||
protected KeycloakUriBuilder logoutUrl;
|
||||
protected String accountUrl;
|
||||
protected String registerNodeUrl;
|
||||
|
@ -131,7 +130,7 @@ public class KeycloakDeployment {
|
|||
log.debug("resolveBrowserUrls");
|
||||
}
|
||||
|
||||
String login = authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_LOGIN_PATH).build(getRealm()).toString();
|
||||
String login = authUrlBuilder.clone().path(ServiceUrlConstants.AUTH_PATH).build(getRealm()).toString();
|
||||
authUrl = KeycloakUriBuilder.fromUri(login);
|
||||
}
|
||||
|
||||
|
@ -143,11 +142,10 @@ public class KeycloakDeployment {
|
|||
log.debug("resolveNonBrowserUrls");
|
||||
}
|
||||
|
||||
refreshUrl = authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_REFRESH_PATH).build(getRealm()).toString();
|
||||
tokenUrl = authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_PATH).build(getRealm()).toString();
|
||||
logoutUrl = KeycloakUriBuilder.fromUri(authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH).build(getRealm()).toString());
|
||||
accountUrl = authUrlBuilder.clone().path(ServiceUrlConstants.ACCOUNT_SERVICE_PATH).build(getRealm()).toString();
|
||||
realmInfoUrl = authUrlBuilder.clone().path(ServiceUrlConstants.REALM_INFO_PATH).build(getRealm()).toString();
|
||||
codeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_ACCESS_CODE_PATH).build(getRealm()).toString();
|
||||
registerNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_REGISTER_NODE_PATH).build(getRealm()).toString();
|
||||
unregisterNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_UNREGISTER_NODE_PATH).build(getRealm()).toString();
|
||||
}
|
||||
|
@ -164,12 +162,8 @@ public class KeycloakDeployment {
|
|||
return authUrl;
|
||||
}
|
||||
|
||||
public String getCodeUrl() {
|
||||
return codeUrl;
|
||||
}
|
||||
|
||||
public String getRefreshUrl() {
|
||||
return refreshUrl;
|
||||
public String getTokenUrl() {
|
||||
return tokenUrl;
|
||||
}
|
||||
|
||||
public KeycloakUriBuilder getLogoutUrl() {
|
||||
|
|
|
@ -85,7 +85,7 @@ public class KeycloakDeploymentBuilder {
|
|||
}
|
||||
deployment.setAuthServerBaseUrl(adapterConfig);
|
||||
|
||||
log.debug("Use authServerUrl: " + deployment.getAuthServerBaseUrl() + ", codeUrl: " + deployment.getCodeUrl() + ", relativeUrls: " + deployment.getRelativeUrls());
|
||||
log.debug("Use authServerUrl: " + deployment.getAuthServerBaseUrl() + ", tokenUrl: " + deployment.getTokenUrl() + ", relativeUrls: " + deployment.getRelativeUrls());
|
||||
return deployment;
|
||||
}
|
||||
|
||||
|
|
|
@ -136,6 +136,7 @@ public class OAuthRequestAuthenticator {
|
|||
url = UriUtils.stripQueryParam(url, K_IDP_HINT);
|
||||
|
||||
KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone()
|
||||
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
|
||||
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
|
||||
.queryParam(OAuth2Constants.REDIRECT_URI, url)
|
||||
.queryParam(OAuth2Constants.STATE, state)
|
||||
|
|
|
@ -87,15 +87,15 @@ public class ServerRequest {
|
|||
}
|
||||
|
||||
public static AccessTokenResponse invokeAccessCodeToToken(KeycloakDeployment deployment, String code, String redirectUri, String sessionId) throws HttpFailure, IOException {
|
||||
String codeUrl = deployment.getCodeUrl();
|
||||
String tokenUrl = deployment.getTokenUrl();
|
||||
String client_id = deployment.getResourceName();
|
||||
Map<String, String> credentials = deployment.getResourceCredentials();
|
||||
HttpClient client = deployment.getClient();
|
||||
|
||||
return invokeAccessCodeToToken(client, deployment.isPublicClient(), code, codeUrl, redirectUri, client_id, credentials, sessionId);
|
||||
return invokeAccessCodeToToken(client, deployment.isPublicClient(), code, tokenUrl, redirectUri, client_id, credentials, sessionId);
|
||||
}
|
||||
|
||||
public static AccessTokenResponse invokeAccessCodeToToken(HttpClient client, boolean publicClient, String code, String codeUrl, String redirectUri, String client_id, Map<String, String> credentials, String sessionId) throws IOException, HttpFailure {
|
||||
public static AccessTokenResponse invokeAccessCodeToToken(HttpClient client, boolean publicClient, String code, String tokenUrl, String redirectUri, String client_id, Map<String, String> credentials, String sessionId) throws IOException, HttpFailure {
|
||||
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
|
||||
redirectUri = stripOauthParametersFromRedirect(redirectUri);
|
||||
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, "authorization_code"));
|
||||
|
@ -106,7 +106,7 @@ public class ServerRequest {
|
|||
formparams.add(new BasicNameValuePair(AdapterConstants.APPLICATION_SESSION_HOST, HostUtils.getHostName()));
|
||||
}
|
||||
HttpResponse response = null;
|
||||
HttpPost post = new HttpPost(codeUrl);
|
||||
HttpPost post = new HttpPost(tokenUrl);
|
||||
if (!publicClient) {
|
||||
String clientSecret = credentials.get(CredentialRepresentation.SECRET);
|
||||
if (clientSecret != null) {
|
||||
|
@ -152,15 +152,15 @@ public class ServerRequest {
|
|||
}
|
||||
|
||||
public static AccessTokenResponse invokeRefresh(KeycloakDeployment deployment, String refreshToken) throws IOException, HttpFailure {
|
||||
String refreshUrl = deployment.getRefreshUrl();
|
||||
String tokenUrl = deployment.getTokenUrl();
|
||||
String client_id = deployment.getResourceName();
|
||||
Map<String, String> credentials = deployment.getResourceCredentials();
|
||||
HttpClient client = deployment.getClient();
|
||||
return invokeRefresh(client, deployment.isPublicClient(), refreshToken, refreshUrl, client_id, credentials);
|
||||
return invokeRefresh(client, deployment.isPublicClient(), refreshToken, tokenUrl, client_id, credentials);
|
||||
}
|
||||
|
||||
|
||||
public static AccessTokenResponse invokeRefresh(HttpClient client, boolean publicClient, String refreshToken, String refreshUrl, String client_id, Map<String, String> credentials) throws IOException, HttpFailure {
|
||||
public static AccessTokenResponse invokeRefresh(HttpClient client, boolean publicClient, String refreshToken, String tokenUrl, String client_id, Map<String, String> credentials) throws IOException, HttpFailure {
|
||||
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
|
||||
for (Map.Entry<String, String> entry : credentials.entrySet()) {
|
||||
formparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
|
||||
|
@ -168,7 +168,7 @@ public class ServerRequest {
|
|||
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.REFRESH_TOKEN));
|
||||
formparams.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken));
|
||||
HttpResponse response = null;
|
||||
HttpPost post = new HttpPost(refreshUrl);
|
||||
HttpPost post = new HttpPost(tokenUrl);
|
||||
if (!publicClient) {
|
||||
String clientSecret = credentials.get(CredentialRepresentation.SECRET);
|
||||
if (clientSecret != null) {
|
||||
|
|
|
@ -64,10 +64,11 @@ public class DirectAccessGrantsLoginModule extends AbstractKeycloakLoginModule {
|
|||
|
||||
protected Auth directGrantAuth(String username, String password) throws IOException, VerificationException {
|
||||
String authServerBaseUrl = deployment.getAuthServerBaseUrl();
|
||||
URI directGrantUri = KeycloakUriBuilder.fromUri(authServerBaseUrl).path(ServiceUrlConstants.TOKEN_SERVICE_DIRECT_GRANT_PATH).build(deployment.getRealm());
|
||||
URI directGrantUri = KeycloakUriBuilder.fromUri(authServerBaseUrl).path(ServiceUrlConstants.TOKEN_PATH).build(deployment.getRealm());
|
||||
HttpPost post = new HttpPost(directGrantUri);
|
||||
|
||||
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
|
||||
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD));
|
||||
formparams.add(new BasicNameValuePair("username", username));
|
||||
formparams.add(new BasicNameValuePair("password", password));
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ public class KeycloakDeploymentBuilderTest {
|
|||
assertEquals("demo", deployment.getRealm());
|
||||
assertEquals("customer-portal", deployment.getResourceName());
|
||||
assertEquals(PemUtils.decodePublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"), deployment.getRealmKey());
|
||||
assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/login", deployment.getAuthUrl().build().toString());
|
||||
assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/auth", deployment.getAuthUrl().build().toString());
|
||||
assertEquals(SslRequired.EXTERNAL, deployment.getSslRequired());
|
||||
assertTrue(deployment.isUseResourceRoleMappings());
|
||||
assertTrue(deployment.isCors());
|
||||
|
@ -33,7 +33,7 @@ public class KeycloakDeploymentBuilderTest {
|
|||
assertTrue(deployment.isExposeToken());
|
||||
assertEquals("234234-234234-234234", deployment.getResourceCredentials().get("secret"));
|
||||
assertEquals(20, ((ThreadSafeClientConnManager) deployment.getClient().getConnectionManager()).getMaxTotal());
|
||||
assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/refresh", deployment.getRefreshUrl());
|
||||
assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/token", deployment.getTokenUrl());
|
||||
assertTrue(deployment.isAlwaysRefreshToken());
|
||||
assertTrue(deployment.isRegisterNodeAtStartup());
|
||||
assertEquals(1000, deployment.getRegisterNodePeriod());
|
||||
|
|
|
@ -20,9 +20,10 @@ public class Keycloak {
|
|||
|
||||
private Keycloak(String serverUrl, String realm, String username, String password, String clientId, String clientSecret){
|
||||
config = new Config(serverUrl, realm, username, password, clientId, clientSecret);
|
||||
tokenManager = new TokenManager(config);
|
||||
|
||||
client = new ResteasyClientBuilder().build();
|
||||
|
||||
tokenManager = new TokenManager(config, client);
|
||||
|
||||
target = client.target(config.getServerUrl());
|
||||
|
||||
target.register(new BearerAuthFilter(tokenManager.getAccessTokenString()));
|
||||
|
|
|
@ -18,10 +18,12 @@ public class TokenManager {
|
|||
|
||||
private AccessTokenResponse currentToken;
|
||||
private Date expirationTime;
|
||||
private Config config;
|
||||
private final Config config;
|
||||
private final ResteasyClient client;
|
||||
|
||||
public TokenManager(Config config){
|
||||
public TokenManager(Config config, ResteasyClient client){
|
||||
this.config = config;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public String getAccessTokenString(){
|
||||
|
@ -38,7 +40,6 @@ public class TokenManager {
|
|||
}
|
||||
|
||||
public AccessTokenResponse grantToken(){
|
||||
ResteasyClient client = new ResteasyClientBuilder().build();
|
||||
ResteasyWebTarget target = client.target(config.getServerUrl());
|
||||
|
||||
Form form = new Form()
|
||||
|
@ -60,7 +61,6 @@ public class TokenManager {
|
|||
}
|
||||
|
||||
public AccessTokenResponse refreshToken(){
|
||||
ResteasyClient client = new ResteasyClientBuilder().build();
|
||||
ResteasyWebTarget target = client.target(config.getServerUrl());
|
||||
|
||||
Form form = new Form()
|
||||
|
|
|
@ -97,6 +97,7 @@ public class KeycloakInstalled {
|
|||
String state = UUID.randomUUID().toString();
|
||||
|
||||
String authUrl = deployment.getAuthUrl().clone()
|
||||
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
|
||||
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
|
||||
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
|
||||
.queryParam(OAuth2Constants.STATE, state)
|
||||
|
@ -153,6 +154,7 @@ public class KeycloakInstalled {
|
|||
String redirectUri = "urn:ietf:wg:oauth:2.0:oob";
|
||||
|
||||
String authUrl = deployment.getAuthUrl().clone()
|
||||
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
|
||||
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
|
||||
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
|
||||
.build().toString();
|
||||
|
|
|
@ -53,7 +53,7 @@ public class JaxrsOAuthClient extends AbstractOAuthClient {
|
|||
for (Map.Entry<String, String> entry : credentials.entrySet()) {
|
||||
codeForm.param(entry.getKey(), entry.getValue());
|
||||
}
|
||||
Response res = client.target(codeUrl).request().post(Entity.form(codeForm));
|
||||
Response res = client.target(tokenUrl).request().post(Entity.form(codeForm));
|
||||
try {
|
||||
if (res.getStatus() == 400) {
|
||||
throw new BadRequestException();
|
||||
|
|
|
@ -140,7 +140,7 @@
|
|||
|
||||
sessionStorage.oauthState = JSON.stringify({ state: state, redirectUri: encodeURIComponent(redirectUri) });
|
||||
|
||||
var action = 'login';
|
||||
var action = 'auth';
|
||||
if (options && options.action == 'register') {
|
||||
action = 'registrations';
|
||||
}
|
||||
|
@ -284,7 +284,7 @@
|
|||
promise.setSuccess(false);
|
||||
} else {
|
||||
var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken;
|
||||
var url = getRealmUrl() + '/protocol/openid-connect/refresh';
|
||||
var url = getRealmUrl() + '/protocol/openid-connect/token';
|
||||
|
||||
refreshQueue.push(promise);
|
||||
|
||||
|
@ -358,8 +358,8 @@
|
|||
var prompt = oauth.prompt;
|
||||
|
||||
if (code) {
|
||||
var params = 'code=' + code;
|
||||
var url = getRealmUrl() + '/protocol/openid-connect/access/codes';
|
||||
var params = 'code=' + code + '&grant_type=authorization_code';
|
||||
var url = getRealmUrl() + '/protocol/openid-connect/token';
|
||||
|
||||
var req = new XMLHttpRequest();
|
||||
req.open('POST', url, true);
|
||||
|
|
|
@ -42,7 +42,7 @@ public class ServletOAuthClient extends AbstractOAuthClient {
|
|||
|
||||
private AccessTokenResponse resolveBearerToken(HttpServletRequest request, String redirectUri, String code) throws IOException, ServerRequest.HttpFailure {
|
||||
// Don't send sessionId in oauth clients for now
|
||||
return ServerRequest.invokeAccessCodeToToken(client, publicClient, code, getUrl(request, codeUrl, false), redirectUri, clientId, credentials, null);
|
||||
return ServerRequest.invokeAccessCodeToToken(client, publicClient, code, getUrl(request, tokenUrl, false), redirectUri, clientId, credentials, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -148,7 +148,7 @@ public class ServletOAuthClient extends AbstractOAuthClient {
|
|||
}
|
||||
|
||||
public AccessTokenResponse refreshToken(HttpServletRequest request, String refreshToken) throws IOException, ServerRequest.HttpFailure {
|
||||
return ServerRequest.invokeRefresh(client, publicClient, refreshToken, getUrl(request, refreshUrl, false), clientId, credentials);
|
||||
return ServerRequest.invokeRefresh(client, publicClient, refreshToken, getUrl(request, tokenUrl, false), clientId, credentials);
|
||||
}
|
||||
|
||||
public static IDToken extractIdToken(String idToken) {
|
||||
|
|
|
@ -54,25 +54,20 @@ public class ServletOAuthClientBuilder {
|
|||
RelativeUrlsUsed useRelative = relativeUrls(serverBuilder, adapterConfig);
|
||||
oauthClient.setRelativeUrlsUsed(useRelative);
|
||||
|
||||
String authUrl = serverBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_LOGIN_PATH).build(adapterConfig.getRealm()).toString();
|
||||
String authUrl = serverBuilder.clone().path(ServiceUrlConstants.AUTH_PATH).build(adapterConfig.getRealm()).toString();
|
||||
|
||||
KeycloakUriBuilder tokenUrlBuilder;
|
||||
KeycloakUriBuilder refreshUrlBuilder;
|
||||
|
||||
if (useRelative == RelativeUrlsUsed.BROWSER_ONLY) {
|
||||
// Use absolute URI for refreshToken and codeToToken requests
|
||||
KeycloakUriBuilder nonBrowsersServerBuilder = KeycloakUriBuilder.fromUri(adapterConfig.getAuthServerUrlForBackendRequests());
|
||||
tokenUrlBuilder = nonBrowsersServerBuilder.clone();
|
||||
refreshUrlBuilder = nonBrowsersServerBuilder.clone();
|
||||
} else {
|
||||
tokenUrlBuilder = serverBuilder.clone();
|
||||
refreshUrlBuilder = serverBuilder.clone();
|
||||
}
|
||||
String tokenUrl = tokenUrlBuilder.path(ServiceUrlConstants.TOKEN_SERVICE_ACCESS_CODE_PATH).build(adapterConfig.getRealm()).toString();
|
||||
String refreshUrl = refreshUrlBuilder.path(ServiceUrlConstants.TOKEN_SERVICE_REFRESH_PATH).build(adapterConfig.getRealm()).toString();
|
||||
String tokenUrl = tokenUrlBuilder.path(ServiceUrlConstants.TOKEN_PATH).build(adapterConfig.getRealm()).toString();
|
||||
oauthClient.setAuthUrl(authUrl);
|
||||
oauthClient.setCodeUrl(tokenUrl);
|
||||
oauthClient.setRefreshUrl(refreshUrl);
|
||||
oauthClient.setTokenUrl(tokenUrl);
|
||||
}
|
||||
|
||||
private static RelativeUrlsUsed relativeUrls(KeycloakUriBuilder serverBuilder, AdapterConfig adapterConfig) {
|
||||
|
|
|
@ -5,6 +5,8 @@ package org.keycloak.models;
|
|||
*/
|
||||
public class ModelException extends RuntimeException {
|
||||
|
||||
private Object[] parameters;
|
||||
|
||||
public ModelException() {
|
||||
}
|
||||
|
||||
|
@ -12,6 +14,11 @@ public class ModelException extends RuntimeException {
|
|||
super(message);
|
||||
}
|
||||
|
||||
public ModelException(String message, Object ... parameters) {
|
||||
super(message);
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
public ModelException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
@ -20,4 +27,11 @@ public class ModelException extends RuntimeException {
|
|||
super(cause);
|
||||
}
|
||||
|
||||
public Object[] getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public void setParameters(Object[] parameters) {
|
||||
this.parameters = parameters;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.keycloak.models;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
@ -9,6 +10,13 @@ import java.util.List;
|
|||
*/
|
||||
public class PasswordPolicy {
|
||||
|
||||
public static final String INVALID_PASSWORD_MIN_LENGTH_MESSAGE = "invalidPasswordMinLengthMessage";
|
||||
public static final String INVALID_PASSWORD_MIN_DIGITS_MESSAGE = "invalidPasswordMinDigitsMessage";
|
||||
public static final String INVALID_PASSWORD_MIN_LOWER_CASE_CHARS_MESSAGE = "invalidPasswordMinLowerCaseCharsMessage";
|
||||
public static final String INVALID_PASSWORD_MIN_UPPER_CASE_CHARS_MESSAGE = "invalidPasswordMinUpperCaseCharsMessage";
|
||||
public static final String INVALID_PASSWORD_MIN_SPECIAL_CHARS_MESSAGE = "invalidPasswordMinSpecialCharsMessage";
|
||||
public static final String INVALID_PASSWORD_NOT_USERNAME = "invalidPasswordNotUsernameMessage";
|
||||
|
||||
private List<Policy> policies;
|
||||
private String policyString;
|
||||
|
||||
|
@ -52,6 +60,8 @@ public class PasswordPolicy {
|
|||
list.add(new UpperCase(args));
|
||||
} else if (name.equals(SpecialChars.NAME)) {
|
||||
list.add(new SpecialChars(args));
|
||||
} else if (name.equals(NotUsername.NAME)) {
|
||||
list.add(new NotUsername(args));
|
||||
} else if (name.equals(HashIterations.NAME)) {
|
||||
list.add(new HashIterations(args));
|
||||
}
|
||||
|
@ -74,9 +84,9 @@ public class PasswordPolicy {
|
|||
return -1;
|
||||
}
|
||||
|
||||
public String validate(String password) {
|
||||
public Error validate(String username, String password) {
|
||||
for (Policy p : policies) {
|
||||
String error = p.validate(password);
|
||||
Error error = p.validate(username, password);
|
||||
if (error != null) {
|
||||
return error;
|
||||
}
|
||||
|
@ -85,7 +95,25 @@ public class PasswordPolicy {
|
|||
}
|
||||
|
||||
private static interface Policy {
|
||||
public String validate(String password);
|
||||
public Error validate(String username, String password);
|
||||
}
|
||||
|
||||
public static class Error{
|
||||
private String message;
|
||||
private Object[] parameters;
|
||||
|
||||
private Error(String message, Object ... parameters){
|
||||
this.message = message;
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public Object[] getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
|
||||
private static class HashIterations implements Policy {
|
||||
|
@ -97,11 +125,23 @@ public class PasswordPolicy {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String validate(String password) {
|
||||
public Error validate(String username, String password) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class NotUsername implements Policy {
|
||||
private static final String NAME = "notUsername";
|
||||
|
||||
public NotUsername(String[] args) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Error validate(String username, String password) {
|
||||
return username.equals(password) ? new Error(INVALID_PASSWORD_NOT_USERNAME) : null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Length implements Policy {
|
||||
private static final String NAME = "length";
|
||||
private int min;
|
||||
|
@ -111,8 +151,8 @@ public class PasswordPolicy {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String validate(String password) {
|
||||
return password.length() < min ? "Invalid password: minimum length " + min : null;
|
||||
public Error validate(String username, String password) {
|
||||
return password.length() < min ? new Error(INVALID_PASSWORD_MIN_LENGTH_MESSAGE, min) : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,14 +165,14 @@ public class PasswordPolicy {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String validate(String password) {
|
||||
public Error validate(String username, String password) {
|
||||
int count = 0;
|
||||
for (char c : password.toCharArray()) {
|
||||
if (Character.isDigit(c)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count < min ? "Invalid password: must contain at least " + min + " numerical digits" : null;
|
||||
return count < min ? new Error(INVALID_PASSWORD_MIN_DIGITS_MESSAGE, min) : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,14 +185,14 @@ public class PasswordPolicy {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String validate(String password) {
|
||||
public Error validate(String username, String password) {
|
||||
int count = 0;
|
||||
for (char c : password.toCharArray()) {
|
||||
if (Character.isLowerCase(c)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count < min ? "Invalid password: must contain at least " + min + " lower case characters": null;
|
||||
return count < min ? new Error(INVALID_PASSWORD_MIN_LOWER_CASE_CHARS_MESSAGE, min): null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -165,14 +205,14 @@ public class PasswordPolicy {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String validate(String password) {
|
||||
public Error validate(String username, String password) {
|
||||
int count = 0;
|
||||
for (char c : password.toCharArray()) {
|
||||
if (Character.isUpperCase(c)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count < min ? "Invalid password: must contain at least " + min + " upper case characters" : null;
|
||||
return count < min ? new Error(INVALID_PASSWORD_MIN_UPPER_CASE_CHARS_MESSAGE, min) : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,14 +225,14 @@ public class PasswordPolicy {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String validate(String password) {
|
||||
public Error validate(String username, String password) {
|
||||
int count = 0;
|
||||
for (char c : password.toCharArray()) {
|
||||
if (!Character.isLetterOrDigit(c)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count < min ? "Invalid password: must contain at least " + min + " special characters" : null;
|
||||
return count < min ? new Error(INVALID_PASSWORD_MIN_SPECIAL_CHARS_MESSAGE, min) : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -244,4 +244,11 @@ public interface RealmModel extends RoleContainerModel {
|
|||
ClientModel findClientById(String id);
|
||||
|
||||
boolean isIdentityFederationEnabled();
|
||||
|
||||
boolean isInternationalizationEnabled();
|
||||
void setInternationalizationEnabled(boolean enabled);
|
||||
Set<String> getSupportedLocales();
|
||||
void setSupportedLocales(Set<String> locales);
|
||||
String getDefaultLocale();
|
||||
void setDefaultLocale(String locale);
|
||||
}
|
||||
|
|
|
@ -323,8 +323,8 @@ public class UserFederationManager implements UserProvider {
|
|||
public void updateCredential(RealmModel realm, UserModel user, UserCredentialModel credential) {
|
||||
if (credential.getType().equals(UserCredentialModel.PASSWORD)) {
|
||||
if (realm.getPasswordPolicy() != null) {
|
||||
String error = realm.getPasswordPolicy().validate(credential.getValue());
|
||||
if (error != null) throw new ModelException(error);
|
||||
PasswordPolicy.Error error = realm.getPasswordPolicy().validate(user.getUsername(), credential.getValue());
|
||||
if (error != null) throw new ModelException(error.getMessage(), error.getParameters());
|
||||
}
|
||||
}
|
||||
user.updateCredential(credential);
|
||||
|
|
|
@ -13,6 +13,7 @@ public interface UserModel {
|
|||
public static final String LAST_NAME = "lastName";
|
||||
public static final String FIRST_NAME = "firstName";
|
||||
public static final String EMAIL = "email";
|
||||
public static final String LOCALE = "locale";
|
||||
|
||||
String getId();
|
||||
|
||||
|
|
|
@ -65,6 +65,10 @@ public class RealmEntity extends AbstractIdentifiableEntity {
|
|||
|
||||
private String adminAppId;
|
||||
|
||||
private boolean internationalizationEnabled;
|
||||
private List<String> supportedLocales = new ArrayList<String>();
|
||||
private String defaultLocale;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
@ -407,6 +411,30 @@ public class RealmEntity extends AbstractIdentifiableEntity {
|
|||
public void setCertificatePem(String certificatePem) {
|
||||
this.certificatePem = certificatePem;
|
||||
}
|
||||
|
||||
public boolean isInternationalizationEnabled() {
|
||||
return internationalizationEnabled;
|
||||
}
|
||||
|
||||
public void setInternationalizationEnabled(boolean internationalizationEnabled) {
|
||||
this.internationalizationEnabled = internationalizationEnabled;
|
||||
}
|
||||
|
||||
public List<String> getSupportedLocales() {
|
||||
return supportedLocales;
|
||||
}
|
||||
|
||||
public void setSupportedLocales(List<String> supportedLocales) {
|
||||
this.supportedLocales = supportedLocales;
|
||||
}
|
||||
|
||||
public String getDefaultLocale() {
|
||||
return defaultLocale;
|
||||
}
|
||||
|
||||
public void setDefaultLocale(String defaultLocale) {
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -155,6 +155,10 @@ public class ModelToRepresentation {
|
|||
rep.addIdentityProvider(toRepresentation(provider));
|
||||
}
|
||||
|
||||
rep.setInternationalizationEnabled(realm.isInternationalizationEnabled());
|
||||
rep.getSupportedLocales().addAll(realm.getSupportedLocales());
|
||||
rep.setDefaultLocale(realm.getDefaultLocale());
|
||||
|
||||
return rep;
|
||||
}
|
||||
|
||||
|
|
|
@ -243,6 +243,16 @@ public class RepresentationToModel {
|
|||
UserModel user = createUser(session, newRealm, userRep, appMap);
|
||||
}
|
||||
}
|
||||
|
||||
if(rep.isInternationalizationEnabled() != null){
|
||||
newRealm.setInternationalizationEnabled(rep.isInternationalizationEnabled());
|
||||
}
|
||||
if(rep.getSupportedLocales() != null){
|
||||
newRealm.setSupportedLocales(new HashSet<String>(rep.getSupportedLocales()));
|
||||
}
|
||||
if(rep.getDefaultLocale() != null){
|
||||
newRealm.setDefaultLocale(rep.getDefaultLocale());
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateRealm(RealmRepresentation rep, RealmModel realm) {
|
||||
|
@ -304,6 +314,16 @@ public class RepresentationToModel {
|
|||
if ("GENERATE".equals(rep.getPublicKey())) {
|
||||
KeycloakModelUtils.generateRealmKeys(realm);
|
||||
}
|
||||
|
||||
if(rep.isInternationalizationEnabled() != null){
|
||||
realm.setInternationalizationEnabled(rep.isInternationalizationEnabled());
|
||||
}
|
||||
if(rep.getSupportedLocales() != null){
|
||||
realm.setSupportedLocales(new HashSet<String>(rep.getSupportedLocales()));
|
||||
}
|
||||
if(rep.getDefaultLocale() != null){
|
||||
realm.setDefaultLocale(rep.getDefaultLocale());
|
||||
}
|
||||
}
|
||||
|
||||
// Basic realm stuff
|
||||
|
|
|
@ -11,68 +11,86 @@ public class PasswordPolicyTest {
|
|||
@Test
|
||||
public void testLength() {
|
||||
PasswordPolicy policy = new PasswordPolicy("length");
|
||||
Assert.assertEquals("Invalid password: minimum length 8", policy.validate("1234567"));
|
||||
Assert.assertNull(policy.validate("12345678"));
|
||||
Assert.assertEquals("invalidPasswordMinLengthMessage", policy.validate("jdoe", "1234567").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{8}, policy.validate("jdoe", "1234567").getParameters());
|
||||
Assert.assertNull(policy.validate("jdoe", "12345678"));
|
||||
|
||||
policy = new PasswordPolicy("length(4)");
|
||||
Assert.assertEquals("Invalid password: minimum length 4", policy.validate("123"));
|
||||
Assert.assertNull(policy.validate("1234"));
|
||||
Assert.assertEquals("invalidPasswordMinLengthMessage", policy.validate("jdoe", "123").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{4}, policy.validate("jdoe", "123").getParameters());
|
||||
Assert.assertNull(policy.validate("jdoe", "1234"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDigits() {
|
||||
PasswordPolicy policy = new PasswordPolicy("digits");
|
||||
Assert.assertEquals("Invalid password: must contain at least 1 numerical digits", policy.validate("abcd"));
|
||||
Assert.assertNull(policy.validate("abcd1"));
|
||||
Assert.assertEquals("invalidPasswordMinDigitsMessage", policy.validate("jdoe", "abcd").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{1}, policy.validate("jdoe", "abcd").getParameters());
|
||||
Assert.assertNull(policy.validate("jdoe", "abcd1"));
|
||||
|
||||
policy = new PasswordPolicy("digits(2)");
|
||||
Assert.assertEquals("Invalid password: must contain at least 2 numerical digits", policy.validate("abcd1"));
|
||||
Assert.assertNull(policy.validate("abcd12"));
|
||||
Assert.assertEquals("invalidPasswordMinDigitsMessage", policy.validate("jdoe", "abcd1").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{2}, policy.validate("jdoe", "abcd1").getParameters());
|
||||
Assert.assertNull(policy.validate("jdoe", "abcd12"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLowerCase() {
|
||||
PasswordPolicy policy = new PasswordPolicy("lowerCase");
|
||||
Assert.assertEquals("Invalid password: must contain at least 1 lower case characters", policy.validate("ABCD1234"));
|
||||
Assert.assertNull(policy.validate("ABcD1234"));
|
||||
Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policy.validate("jdoe", "ABCD1234").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{1}, policy.validate("jdoe", "ABCD1234").getParameters());
|
||||
Assert.assertNull(policy.validate("jdoe", "ABcD1234"));
|
||||
|
||||
policy = new PasswordPolicy("lowerCase(2)");
|
||||
Assert.assertEquals("Invalid password: must contain at least 2 lower case characters", policy.validate("ABcD1234"));
|
||||
Assert.assertNull(policy.validate("aBcD1234"));
|
||||
Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policy.validate("jdoe", "ABcD1234").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{2}, policy.validate("jdoe", "ABcD1234").getParameters());
|
||||
Assert.assertNull(policy.validate("jdoe", "aBcD1234"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpperCase() {
|
||||
PasswordPolicy policy = new PasswordPolicy("upperCase");
|
||||
Assert.assertEquals("Invalid password: must contain at least 1 upper case characters", policy.validate("abcd1234"));
|
||||
Assert.assertNull(policy.validate("abCd1234"));
|
||||
Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policy.validate("jdoe", "abcd1234").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{1}, policy.validate("jdoe", "abcd1234").getParameters());
|
||||
Assert.assertNull(policy.validate("jdoe", "abCd1234"));
|
||||
|
||||
policy = new PasswordPolicy("upperCase(2)");
|
||||
Assert.assertEquals("Invalid password: must contain at least 2 upper case characters", policy.validate("abCd1234"));
|
||||
Assert.assertNull(policy.validate("AbCd1234"));
|
||||
Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policy.validate("jdoe", "abCd1234").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{2}, policy.validate("jdoe", "abCd1234").getParameters());
|
||||
Assert.assertNull(policy.validate("jdoe", "AbCd1234"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpecialChars() {
|
||||
PasswordPolicy policy = new PasswordPolicy("specialChars");
|
||||
Assert.assertEquals("Invalid password: must contain at least 1 special characters", policy.validate("abcd1234"));
|
||||
Assert.assertNull(policy.validate("ab&d1234"));
|
||||
Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policy.validate("jdoe", "abcd1234").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{1}, policy.validate("jdoe", "abcd1234").getParameters());
|
||||
Assert.assertNull(policy.validate("jdoe", "ab&d1234"));
|
||||
|
||||
policy = new PasswordPolicy("specialChars(2)");
|
||||
Assert.assertEquals("Invalid password: must contain at least 2 special characters", policy.validate("ab&d1234"));
|
||||
Assert.assertNull(policy.validate("ab&d-234"));
|
||||
Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policy.validate("jdoe", "ab&d1234").getMessage());
|
||||
Assert.assertArrayEquals(new Object[]{2}, policy.validate("jdoe", "ab&d1234").getParameters());
|
||||
Assert.assertNull(policy.validate("jdoe", "ab&d-234"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotUsername() {
|
||||
PasswordPolicy policy = new PasswordPolicy("notUsername");
|
||||
Assert.assertEquals("invalidPasswordNotUsernameMessage", policy.validate("jdoe", "jdoe").getMessage());
|
||||
Assert.assertNull(policy.validate("jdoe", "ab&d1234"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComplex() {
|
||||
PasswordPolicy policy = new PasswordPolicy("length(8) and digits(2) and lowerCase(2) and upperCase(2) and specialChars(2)");
|
||||
Assert.assertNotNull(policy.validate("12aaBB&"));
|
||||
Assert.assertNotNull(policy.validate("aaaaBB&-"));
|
||||
Assert.assertNotNull(policy.validate("12AABB&-"));
|
||||
Assert.assertNotNull(policy.validate("12aabb&-"));
|
||||
Assert.assertNotNull(policy.validate("12aaBBcc"));
|
||||
PasswordPolicy policy = new PasswordPolicy("length(8) and digits(2) and lowerCase(2) and upperCase(2) and specialChars(2) and notUsername()");
|
||||
Assert.assertNotNull(policy.validate("jdoe", "12aaBB&"));
|
||||
Assert.assertNotNull(policy.validate("jdoe", "aaaaBB&-"));
|
||||
Assert.assertNotNull(policy.validate("jdoe", "12AABB&-"));
|
||||
Assert.assertNotNull(policy.validate("jdoe", "12aabb&-"));
|
||||
Assert.assertNotNull(policy.validate("jdoe", "12aaBBcc"));
|
||||
Assert.assertNotNull(policy.validate("12aaBB&-", "12aaBB&-"));
|
||||
|
||||
Assert.assertNull(policy.validate("12aaBB&-"));
|
||||
Assert.assertNull(policy.validate("jdoe", "12aaBB&-"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1067,6 +1067,36 @@ public class RealmAdapter implements RealmModel {
|
|||
realm.setAccessCodeLifespanLogin(accessCodeLifespanLogin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInternationalizationEnabled() {
|
||||
return realm.isInternationalizationEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInternationalizationEnabled(boolean enabled) {
|
||||
realm.setInternationalizationEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getSupportedLocales() {
|
||||
return new HashSet<>(realm.getSupportedLocales());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSupportedLocales(Set<String> locales) {
|
||||
realm.setSupportedLocales(new ArrayList<>(locales));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultLocale() {
|
||||
return realm.getDefaultLocale();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefaultLocale(String locale) {
|
||||
realm.setDefaultLocale(locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
@ -888,4 +888,39 @@ public class RealmAdapter implements RealmModel {
|
|||
public int hashCode() {
|
||||
return getId().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInternationalizationEnabled() {
|
||||
if (updated != null) return updated.isInternationalizationEnabled();
|
||||
return cached.isInternationalizationEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInternationalizationEnabled(boolean enabled) {
|
||||
getDelegateForUpdate();
|
||||
updated.setInternationalizationEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getSupportedLocales() {
|
||||
if (updated != null) return updated.getSupportedLocales();
|
||||
return cached.getSupportedLocales();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSupportedLocales(Set<String> locales) {
|
||||
getDelegateForUpdate();
|
||||
updated.setSupportedLocales(locales);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultLocale() {
|
||||
if (updated != null) return updated.getDefaultLocale();
|
||||
return cached.getDefaultLocale();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefaultLocale(String locale) {
|
||||
updated.setDefaultLocale(locale);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,6 +83,9 @@ public class CachedRealm {
|
|||
private Map<String, String> realmRoles = new HashMap<String, String>();
|
||||
private Map<String, String> applications = new HashMap<String, String>();
|
||||
private Map<String, String> clients = new HashMap<String, String>();
|
||||
private boolean internationalizationEnabled;
|
||||
private Set<String> supportedLocales = new HashSet<String>();
|
||||
private String defaultLocale;
|
||||
|
||||
public CachedRealm() {
|
||||
}
|
||||
|
@ -164,6 +167,10 @@ public class CachedRealm {
|
|||
cache.addCachedOAuthClient(cachedApp);
|
||||
}
|
||||
|
||||
internationalizationEnabled = model.isInternationalizationEnabled();
|
||||
supportedLocales.addAll(model.getSupportedLocales());
|
||||
defaultLocale = model.getDefaultLocale();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -353,4 +360,16 @@ public class CachedRealm {
|
|||
public List<IdentityProviderModel> getIdentityProviders() {
|
||||
return identityProviders;
|
||||
}
|
||||
|
||||
public boolean isInternationalizationEnabled() {
|
||||
return internationalizationEnabled;
|
||||
}
|
||||
|
||||
public Set<String> getSupportedLocales() {
|
||||
return supportedLocales;
|
||||
}
|
||||
|
||||
public String getDefaultLocale() {
|
||||
return defaultLocale;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1238,4 +1238,36 @@ public class RealmAdapter implements RealmModel {
|
|||
return !this.realm.getIdentityProviders().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInternationalizationEnabled() {
|
||||
return realm.isInternationalizationEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInternationalizationEnabled(boolean enabled) {
|
||||
realm.setInternationalizationEnabled(enabled);
|
||||
em.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getSupportedLocales() {
|
||||
return realm.getSupportedLocales();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSupportedLocales(Set<String> locales) {
|
||||
realm.setSupportedLocales(locales);
|
||||
em.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultLocale() {
|
||||
return realm.getDefaultLocale();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefaultLocale(String locale) {
|
||||
realm.setDefaultLocale(locale);
|
||||
em.flush();
|
||||
}
|
||||
}
|
|
@ -137,6 +137,18 @@ public class RealmEntity {
|
|||
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
||||
protected List<IdentityProviderEntity> identityProviders = new ArrayList<IdentityProviderEntity>();
|
||||
|
||||
@Column(name="INTERNATIONALIZATION_ENABLED")
|
||||
protected boolean internationalizationEnabled;
|
||||
|
||||
@ElementCollection
|
||||
@Column(name="VALUE")
|
||||
@CollectionTable(name="REALM_SUPPORTED_LOCALES", joinColumns={ @JoinColumn(name="REALM_ID") })
|
||||
protected Set<String> supportedLocales = new HashSet<String>();
|
||||
|
||||
@Column(name="DEFAULT_LOCALE")
|
||||
protected String defaultLocale;
|
||||
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
@ -452,5 +464,28 @@ public class RealmEntity {
|
|||
getIdentityProviders().add(entity);
|
||||
}
|
||||
|
||||
public boolean isInternationalizationEnabled() {
|
||||
return internationalizationEnabled;
|
||||
}
|
||||
|
||||
public void setInternationalizationEnabled(boolean internationalizationEnabled) {
|
||||
this.internationalizationEnabled = internationalizationEnabled;
|
||||
}
|
||||
|
||||
public Set<String> getSupportedLocales() {
|
||||
return supportedLocales;
|
||||
}
|
||||
|
||||
public void setSupportedLocales(Set<String> supportedLocales) {
|
||||
this.supportedLocales = supportedLocales;
|
||||
}
|
||||
|
||||
public String getDefaultLocale() {
|
||||
return defaultLocale;
|
||||
}
|
||||
|
||||
public void setDefaultLocale(String defaultLocale) {
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1085,5 +1085,40 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
|||
return getId().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInternationalizationEnabled() {
|
||||
return realm.isInternationalizationEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInternationalizationEnabled(boolean enabled) {
|
||||
realm.setInternationalizationEnabled(enabled);
|
||||
updateRealm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getSupportedLocales() {
|
||||
return new HashSet<String>(realm.getSupportedLocales());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSupportedLocales(Set<String> locales) {
|
||||
if (locales != null) {
|
||||
realm.setEventsListeners(new ArrayList<String>(locales));
|
||||
} else {
|
||||
realm.setEventsListeners(Collections.EMPTY_LIST);
|
||||
}
|
||||
updateRealm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultLocale() {
|
||||
return realm.getDefaultLocale();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefaultLocale(String locale) {
|
||||
realm.setDefaultLocale(locale);
|
||||
updateRealm();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.keycloak.protocol.saml.mappers.SAMLLoginResponseMapper;
|
|||
import org.keycloak.protocol.saml.mappers.SAMLRoleListMapper;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.managers.ResourceAdminManager;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.resources.RealmsResource;
|
||||
import org.keycloak.services.resources.admin.ClientAttributeCertificateResource;
|
||||
import org.keycloak.services.resources.flows.Flows;
|
||||
|
@ -94,6 +95,7 @@ public class SamlProtocol implements LoginProtocol {
|
|||
|
||||
protected UriInfo uriInfo;
|
||||
|
||||
protected HttpHeaders headers;
|
||||
|
||||
|
||||
@Override
|
||||
|
@ -114,6 +116,12 @@ public class SamlProtocol implements LoginProtocol {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SamlProtocol setHttpHeaders(HttpHeaders headers){
|
||||
this.headers = headers;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response cancelLogin(ClientSessionModel clientSession) {
|
||||
return getErrorResponse(clientSession, JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get());
|
||||
|
@ -141,7 +149,7 @@ public class SamlProtocol implements LoginProtocol {
|
|||
return builder.redirectBinding().response();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.FAILED_TO_PROCESS_RESPONSE );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -295,7 +303,7 @@ public class SamlProtocol implements LoginProtocol {
|
|||
samlDocument = builder.buildDocument(samlModel);
|
||||
} catch (Exception e) {
|
||||
logger.error("failed", e);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo,headers, Messages.FAILED_TO_PROCESS_RESPONSE);
|
||||
}
|
||||
|
||||
SAML2BindingBuilder2 bindingBuilder = new SAML2BindingBuilder2();
|
||||
|
@ -317,7 +325,7 @@ public class SamlProtocol implements LoginProtocol {
|
|||
publicKey = SamlProtocolUtils.getEncryptionValidationKey(client);
|
||||
} catch (Exception e) {
|
||||
logger.error("failed", e);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.FAILED_TO_PROCESS_RESPONSE);
|
||||
}
|
||||
bindingBuilder.encrypt(publicKey);
|
||||
}
|
||||
|
@ -329,7 +337,7 @@ public class SamlProtocol implements LoginProtocol {
|
|||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("failed", e);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.FAILED_TO_PROCESS_RESPONSE );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
|||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.managers.HttpAuthenticationManager;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.resources.RealmsResource;
|
||||
import org.keycloak.services.resources.flows.Flows;
|
||||
import org.keycloak.util.StreamUtil;
|
||||
|
@ -106,18 +107,18 @@ public class SamlService {
|
|||
if (!checkSsl()) {
|
||||
event.event(EventType.LOGIN);
|
||||
event.error(Errors.SSL_REQUIRED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED );
|
||||
}
|
||||
if (!realm.isEnabled()) {
|
||||
event.event(EventType.LOGIN_ERROR);
|
||||
event.error(Errors.REALM_DISABLED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
|
||||
}
|
||||
|
||||
if (samlRequest == null && samlResponse == null) {
|
||||
event.event(EventType.LOGIN);
|
||||
event.error(Errors.INVALID_TOKEN);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST );
|
||||
|
||||
}
|
||||
return null;
|
||||
|
@ -131,7 +132,7 @@ public class SamlService {
|
|||
if (!uriInfo.getAbsolutePath().toString().equals(statusResponse.getDestination())) {
|
||||
event.error(Errors.INVALID_SAML_LOGOUT_RESPONSE);
|
||||
event.detail(Details.REASON, "invalid_destination");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid request.");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
|
||||
}
|
||||
|
||||
AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers, false);
|
||||
|
@ -139,7 +140,7 @@ public class SamlService {
|
|||
logger.warn("Unknown saml response.");
|
||||
event.event(EventType.LOGOUT);
|
||||
event.error(Errors.INVALID_TOKEN);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
|
||||
}
|
||||
// assume this is a logout response
|
||||
UserSessionModel userSession = authResult.getSession();
|
||||
|
@ -148,10 +149,10 @@ public class SamlService {
|
|||
logger.warn("UserSession is not tagged as logging out.");
|
||||
event.event(EventType.LOGOUT);
|
||||
event.error(Errors.INVALID_SAML_LOGOUT_RESPONSE);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
|
||||
}
|
||||
logger.debug("logout response");
|
||||
Response response = authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection);
|
||||
Response response = authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||
event.success();
|
||||
return response;
|
||||
}
|
||||
|
@ -161,7 +162,7 @@ public class SamlService {
|
|||
if (documentHolder == null) {
|
||||
event.event(EventType.LOGIN);
|
||||
event.error(Errors.INVALID_TOKEN);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
|
||||
}
|
||||
|
||||
SAML2Object samlObject = documentHolder.getSamlObject();
|
||||
|
@ -173,23 +174,23 @@ public class SamlService {
|
|||
if (client == null) {
|
||||
event.event(EventType.LOGIN);
|
||||
event.error(Errors.CLIENT_NOT_FOUND);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester.");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_LOGIN_REQUESTER);
|
||||
}
|
||||
|
||||
if (!client.isEnabled()) {
|
||||
event.event(EventType.LOGIN);
|
||||
event.error(Errors.CLIENT_DISABLED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled.");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.LOGIN_REQUESTER_NOT_ENABLED);
|
||||
}
|
||||
if ((client instanceof ApplicationModel) && ((ApplicationModel)client).isBearerOnly()) {
|
||||
event.event(EventType.LOGIN);
|
||||
event.error(Errors.NOT_ALLOWED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Bearer-only applications are not allowed to initiate browser login");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.BEARER_ONLY);
|
||||
}
|
||||
if (client.isDirectGrantsOnly()) {
|
||||
event.event(EventType.LOGIN);
|
||||
event.error(Errors.NOT_ALLOWED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "direct-grants-only clients are not allowed to initiate browser login");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.DIRECT_GRANTS_ONLY );
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -198,7 +199,7 @@ public class SamlService {
|
|||
SamlService.logger.error("request validation failed", e);
|
||||
event.event(EventType.LOGIN);
|
||||
event.error(Errors.INVALID_SIGNATURE);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid requester.");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUESTER);
|
||||
}
|
||||
logger.debug("verified request");
|
||||
if (samlObject instanceof AuthnRequestType) {
|
||||
|
@ -216,7 +217,7 @@ public class SamlService {
|
|||
} else {
|
||||
event.event(EventType.LOGIN);
|
||||
event.error(Errors.INVALID_TOKEN);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -230,7 +231,7 @@ public class SamlService {
|
|||
if (!uriInfo.getAbsolutePath().equals(requestAbstractType.getDestination())) {
|
||||
event.error(Errors.INVALID_SAML_AUTHN_REQUEST);
|
||||
event.detail(Details.REASON, "invalid_destination");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid request.");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
|
||||
}
|
||||
String bindingType = getBindingType(requestAbstractType);
|
||||
if ("true".equals(client.getAttribute(SamlProtocol.SAML_FORCE_POST_BINDING))) bindingType = SamlProtocol.SAML_POST_BINDING;
|
||||
|
@ -252,7 +253,7 @@ public class SamlService {
|
|||
|
||||
if (redirect == null) {
|
||||
event.error(Errors.INVALID_REDIRECT_URI);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect_uri.");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REDIRECT_URI );
|
||||
}
|
||||
|
||||
|
||||
|
@ -275,7 +276,7 @@ public class SamlService {
|
|||
} else {
|
||||
event.error(Errors.INVALID_SAML_AUTHN_REQUEST);
|
||||
event.detail(Details.REASON, "unsupported_nameid_format");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unsupported NameIDFormat.");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNSUPPORTED_NAME_ID_FORMAT);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -284,10 +285,10 @@ public class SamlService {
|
|||
|
||||
// SPNEGO/Kerberos authentication TODO: This should be somehow pluggable instead of hardcoded this way (Authentication interceptors?)
|
||||
HttpAuthenticationManager httpAuthManager = new HttpAuthenticationManager(session, clientSession, realm, uriInfo, request, clientConnection, event);
|
||||
HttpAuthenticationManager.HttpAuthOutput httpAuthOutput = httpAuthManager.spnegoAuthenticate();
|
||||
HttpAuthenticationManager.HttpAuthOutput httpAuthOutput = httpAuthManager.spnegoAuthenticate(headers);
|
||||
if (httpAuthOutput.getResponse() != null) return httpAuthOutput.getResponse();
|
||||
|
||||
LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo)
|
||||
LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers)
|
||||
.setClientSessionCode(new ClientSessionCode(realm, clientSession).getCode());
|
||||
|
||||
// Attach state from SPNEGO authentication
|
||||
|
@ -339,7 +340,7 @@ public class SamlService {
|
|||
if (!uriInfo.getAbsolutePath().equals(logoutRequest.getDestination())) {
|
||||
event.error(Errors.INVALID_SAML_LOGOUT_REQUEST);
|
||||
event.detail(Details.REASON, "invalid_destination");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid request.");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
|
||||
}
|
||||
|
||||
// authenticate identity cookie, but ignore an access token timeout as we're logging out anyways.
|
||||
|
@ -366,7 +367,7 @@ public class SamlService {
|
|||
}
|
||||
}
|
||||
logger.debug("browser Logout");
|
||||
return authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection);
|
||||
return authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||
}
|
||||
|
||||
|
||||
|
@ -379,7 +380,7 @@ public class SamlService {
|
|||
if (redirectUri != null) {
|
||||
redirectUri = RedirectUtils.verifyRedirectUri(uriInfo, redirectUri, realm, client);
|
||||
if (redirectUri == null) {
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect uri.");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REDIRECT_URI );
|
||||
}
|
||||
}
|
||||
if (redirectUri != null) {
|
||||
|
@ -391,7 +392,7 @@ public class SamlService {
|
|||
}
|
||||
|
||||
private Response logout(UserSessionModel userSession) {
|
||||
Response response = authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection);
|
||||
Response response = authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||
if (response == null) event.user(userSession.getUser()).session(userSession).success();
|
||||
return response;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import org.keycloak.models.UserSessionModel;
|
|||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
|
@ -21,6 +22,8 @@ public interface LoginProtocol extends Provider {
|
|||
|
||||
LoginProtocol setUriInfo(UriInfo uriInfo);
|
||||
|
||||
LoginProtocol setHttpHeaders(HttpHeaders headers);
|
||||
|
||||
Response cancelLogin(ClientSessionModel clientSession);
|
||||
Response invalidSessionError(ClientSessionModel clientSession);
|
||||
Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode);
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.keycloak.protocol.LoginProtocol;
|
|||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.managers.ResourceAdminManager;
|
||||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
@ -63,13 +64,17 @@ public class OIDCLoginProtocol implements LoginProtocol {
|
|||
|
||||
protected UriInfo uriInfo;
|
||||
|
||||
public OIDCLoginProtocol(KeycloakSession session, RealmModel realm, UriInfo uriInfo) {
|
||||
protected HttpHeaders headers;
|
||||
|
||||
public OIDCLoginProtocol(KeycloakSession session, RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
|
||||
this.session = session;
|
||||
this.realm = realm;
|
||||
this.uriInfo = uriInfo;
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public OIDCLoginProtocol() {
|
||||
public OIDCLoginProtocol(){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -90,6 +95,12 @@ public class OIDCLoginProtocol implements LoginProtocol {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OIDCLoginProtocol setHttpHeaders(HttpHeaders headers){
|
||||
this.headers = headers;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response cancelLogin(ClientSessionModel clientSession) {
|
||||
String redirect = clientSession.getRedirectUri();
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue