diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml index a723490bb6..b545aa3051 100755 --- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml +++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml @@ -73,6 +73,12 @@ + + + + + + @@ -88,11 +94,14 @@ + + + diff --git a/core/src/main/java/org/keycloak/AbstractOAuthClient.java b/core/src/main/java/org/keycloak/AbstractOAuthClient.java index 00fa0ed607..3b29907e69 100755 --- a/core/src/main/java/org/keycloak/AbstractOAuthClient.java +++ b/core/src/main/java/org/keycloak/AbstractOAuthClient.java @@ -18,8 +18,7 @@ public class AbstractOAuthClient { protected String clientId; protected Map 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() { diff --git a/core/src/main/java/org/keycloak/constants/ServiceUrlConstants.java b/core/src/main/java/org/keycloak/constants/ServiceUrlConstants.java index 939906adeb..ae0b686b53 100755 --- a/core/src/main/java/org/keycloak/constants/ServiceUrlConstants.java +++ b/core/src/main/java/org/keycloak/constants/ServiceUrlConstants.java @@ -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"; diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java index 023172f9ae..532807e53d 100755 --- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java @@ -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 Bill Burke @@ -68,6 +64,10 @@ public class RealmRepresentation { private List identityProviders; private List protocolMappers; private Boolean identityFederationEnabled; + protected Boolean internationalizationEnabled; + protected Set supportedLocales; + protected String defaultLocale; + public String getId() { return id; @@ -513,4 +513,31 @@ public class RealmRepresentation { public void setProtocolMappers(List protocolMappers) { this.protocolMappers = protocolMappers; } + + public Boolean isInternationalizationEnabled() { + return internationalizationEnabled; + } + + public void setInternationalizationEnabled(Boolean internationalizationEnabled) { + this.internationalizationEnabled = internationalizationEnabled; + } + + public Set getSupportedLocales() { + if(supportedLocales == null){ + supportedLocales = new HashSet(); + } + return supportedLocales; + } + + public void setSupportedLocales(Set supportedLocales) { + this.supportedLocales = supportedLocales; + } + + public String getDefaultLocale() { + return defaultLocale; + } + + public void setDefaultLocale(String defaultLocale) { + this.defaultLocale = defaultLocale; + } } diff --git a/distribution/osgi/features/src/main/resources/features.xml b/distribution/osgi/features/src/main/resources/features.xml index d56602b95b..7cda6d134a 100644 --- a/distribution/osgi/features/src/main/resources/features.xml +++ b/distribution/osgi/features/src/main/resources/features.xml @@ -4,7 +4,8 @@
The keycloak adapter core stuff
mvn:org.keycloak/keycloak-osgi-thirdparty/${project.version} - mvn:org.bouncycastle/bcprov-jdk16/${bouncycastle.version} + mvn:org.bouncycastle/bcprov-jdk15on/${bouncycastle.crypto.version} + mvn:org.bouncycastle/bcpkix-jdk15on/${bouncycastle.crypto.version} mvn:org.codehaus.jackson/jackson-core-asl/${jackson.version} mvn:org.codehaus.jackson/jackson-mapper-asl/${jackson.version} mvn:org.codehaus.jackson/jackson-xc/${jackson.version} diff --git a/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java b/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java index 9f7ec379d0..8f6c951b69 100755 --- a/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java +++ b/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java @@ -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 formparams = new ArrayList (); formparams.add(new BasicNameValuePair("username", "admin")); formparams.add(new BasicNameValuePair("password", "password")); diff --git a/examples/demo-template/third-party-cdi/src/main/java/org/keycloak/example/oauth/DatabaseClient.java b/examples/demo-template/third-party-cdi/src/main/java/org/keycloak/example/oauth/DatabaseClient.java index e642605f3c..01ede2eec9 100755 --- a/examples/demo-template/third-party-cdi/src/main/java/org/keycloak/example/oauth/DatabaseClient.java +++ b/examples/demo-template/third-party-cdi/src/main/java/org/keycloak/example/oauth/DatabaseClient.java @@ -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: diff --git a/examples/demo-template/third-party/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java b/examples/demo-template/third-party/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java index 24639085ae..3f69ef91f1 100755 --- a/examples/demo-template/third-party/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java +++ b/examples/demo-template/third-party/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java @@ -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: diff --git a/examples/fuse/README.md b/examples/fuse/README.md index 8b1508cc65..d9c81c519d 100644 --- a/examples/fuse/README.md +++ b/examples/fuse/README.md @@ -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: ``` diff --git a/examples/fuse/camel/pom.xml b/examples/fuse/camel/pom.xml index a81554e88c..568e281132 100644 --- a/examples/fuse/camel/pom.xml +++ b/examples/fuse/camel/pom.xml @@ -30,6 +30,11 @@ + + org.keycloak + keycloak-core + ${project.version} + org.apache.camel camel-core diff --git a/examples/fuse/camel/src/main/java/org/keycloak/example/CamelHelloBean.java b/examples/fuse/camel/src/main/java/org/keycloak/example/CamelHelloBean.java deleted file mode 100644 index df5c472e68..0000000000 --- a/examples/fuse/camel/src/main/java/org/keycloak/example/CamelHelloBean.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.keycloak.example; - -import java.text.SimpleDateFormat; -import java.util.Date; - -/** - * @author Marek Posolda - */ -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()); - } -} diff --git a/examples/fuse/camel/src/main/java/org/keycloak/example/CamelHelloProcessor.java b/examples/fuse/camel/src/main/java/org/keycloak/example/CamelHelloProcessor.java new file mode 100644 index 0000000000..1009c5cf69 --- /dev/null +++ b/examples/fuse/camel/src/main/java/org/keycloak/example/CamelHelloProcessor.java @@ -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 Marek Posolda + */ +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("Hello " + username + "! It's " + sdf.format(new Date()) + ""); + } +} diff --git a/examples/fuse/camel/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/examples/fuse/camel/src/main/resources/OSGI-INF/blueprint/blueprint.xml index 0dc0fdd1e4..f2cecf90a4 100644 --- a/examples/fuse/camel/src/main/resources/OSGI-INF/blueprint/blueprint.xml +++ b/examples/fuse/camel/src/main/resources/OSGI-INF/blueprint/blueprint.xml @@ -49,18 +49,15 @@ - + - - - + - diff --git a/examples/fuse/customer-app-fuse/src/main/java/org/keycloak/example/CamelClient.java b/examples/fuse/customer-app-fuse/src/main/java/org/keycloak/example/CamelClient.java index 9ddb8f19d5..6e27e30aa5 100644 --- a/examples/fuse/customer-app-fuse/src/main/java/org/keycloak/example/CamelClient.java +++ b/examples/fuse/customer-app-fuse/src/main/java/org/keycloak/example/CamelClient.java @@ -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(); diff --git a/examples/fuse/customer-app-fuse/src/main/webapp/customers/camel.jsp b/examples/fuse/customer-app-fuse/src/main/webapp/customers/camel.jsp index 1d29927f27..6b7c5913b6 100755 --- a/examples/fuse/customer-app-fuse/src/main/webapp/customers/camel.jsp +++ b/examples/fuse/customer-app-fuse/src/main/webapp/customers/camel.jsp @@ -8,7 +8,7 @@ Camel page -

You will receive info from camel endpoint. Endpoint is accessible just for admin user

+

You will receive info from camel endpoint. Endpoint is accessible just for user with admin role

Response from camel: <%= CamelClient.sendRequest(request) %>



diff --git a/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java b/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java index 7677a2091e..bc6920b5fa 100755 --- a/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java +++ b/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java @@ -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); diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java index 368b4591e3..881606f47f 100755 --- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java +++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java @@ -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 Stian Thorgersen @@ -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 attributes = new HashMap(); @@ -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; } diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/RealmBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/RealmBean.java new file mode 100755 index 0000000000..b0a5eb4959 --- /dev/null +++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/RealmBean.java @@ -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 Michael Gerber + */ +public class RealmBean { + + private RealmModel realm; + + public RealmBean(RealmModel realmModel) { + realm = realmModel; + } + + public boolean isInternationalizationEnabled() { + return realm.isInternationalizationEnabled(); + } + + public Set getSupportedLocales(){ + return realm.getSupportedLocales(); + } + +} diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/UrlBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/UrlBean.java index de7e432d6a..a357aadd78 100755 --- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/UrlBean.java +++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/UrlBean.java @@ -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(); } diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java index 533b63120d..1fefce1921 100644 --- a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java +++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java @@ -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 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); } diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/LocaleHelper.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/LocaleHelper.java new file mode 100644 index 0000000000..e153fcf511 --- /dev/null +++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/LocaleHelper.java @@ -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 Michael Gerber + */ +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 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; + } +} diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/Theme.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/Theme.java index dff92ee861..6a12a49f00 100644 --- a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/Theme.java +++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/Theme.java @@ -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; diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/TextFormatterBean.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/TextFormatterBean.java new file mode 100644 index 0000000000..5243d56cae --- /dev/null +++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/TextFormatterBean.java @@ -0,0 +1,19 @@ +package org.keycloak.freemarker.beans; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * @author Michael Gerber + */ +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); + } +} diff --git a/forms/common-freemarker/src/test/java/org/keycloak/freemarke/LocaleHelperTest.java b/forms/common-freemarker/src/test/java/org/keycloak/freemarke/LocaleHelperTest.java new file mode 100644 index 0000000000..01bb9986b2 --- /dev/null +++ b/forms/common-freemarker/src/test/java/org/keycloak/freemarke/LocaleHelperTest.java @@ -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 Michael Gerber + */ +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")))); + } +} diff --git a/forms/common-themes/pom.xml b/forms/common-themes/pom.xml index 8ad524fd80..04ea6bc30e 100755 --- a/forms/common-themes/pom.xml +++ b/forms/common-themes/pom.xml @@ -42,6 +42,11 @@ resteasy-jaxrs provided
+ + org.jboss.logging + jboss-logging + provided +
diff --git a/forms/common-themes/src/main/java/org/keycloak/theme/ClassLoaderTheme.java b/forms/common-themes/src/main/java/org/keycloak/theme/ClassLoaderTheme.java index 1a793eee7c..c95edd2ea7 100755 --- a/forms/common-themes/src/main/java/org/keycloak/theme/ClassLoaderTheme.java +++ b/forms/common-themes/src/main/java/org/keycloak/theme/ClassLoaderTheme.java @@ -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 Stian Thorgersen @@ -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()); } diff --git a/forms/common-themes/src/main/java/org/keycloak/theme/FolderTheme.java b/forms/common-themes/src/main/java/org/keycloak/theme/FolderTheme.java index aa2312e707..0edc92ee5f 100644 --- a/forms/common-themes/src/main/java/org/keycloak/theme/FolderTheme.java +++ b/forms/common-themes/src/main/java/org/keycloak/theme/FolderTheme.java @@ -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)); } diff --git a/forms/common-themes/src/main/resources/theme/account/base/messages/messages.properties b/forms/common-themes/src/main/resources/theme/account/base/messages/messages.properties deleted file mode 100755 index 9ada6842b3..0000000000 --- a/forms/common-themes/src/main/resources/theme/account/base/messages/messages.properties +++ /dev/null @@ -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 diff --git a/forms/common-themes/src/main/resources/theme/account/base/messages/messages_de.properties b/forms/common-themes/src/main/resources/theme/account/base/messages/messages_de.properties new file mode 100644 index 0000000000..4d3e4b37bc --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/account/base/messages/messages_de.properties @@ -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 diff --git a/forms/common-themes/src/main/resources/theme/account/base/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/account/base/messages/messages_en.properties new file mode 100755 index 0000000000..41072c8fd8 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/account/base/messages/messages_en.properties @@ -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 diff --git a/forms/common-themes/src/main/resources/theme/account/base/sessions.ftl b/forms/common-themes/src/main/resources/theme/account/base/sessions.ftl index 85c7a16702..435f188f98 100755 --- a/forms/common-themes/src/main/resources/theme/account/base/sessions.ftl +++ b/forms/common-themes/src/main/resources/theme/account/base/sessions.ftl @@ -42,6 +42,6 @@ - ${rb.logOutAllSessions} + ${rb.doLogOutAllSessions} diff --git a/forms/common-themes/src/main/resources/theme/account/base/template.ftl b/forms/common-themes/src/main/resources/theme/account/base/template.ftl index 11dc877bf4..0d862c3835 100644 --- a/forms/common-themes/src/main/resources/theme/account/base/template.ftl +++ b/forms/common-themes/src/main/resources/theme/account/base/template.ftl @@ -15,6 +15,19 @@ + <#if realm.internationalizationEnabled> + + @@ -28,6 +41,15 @@ +
+ +
+ +
+
+
+ + +
+ +
+
+
+ +
+
+ +
+
+
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-detail.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-detail.html index 59fa3b2e36..5007f12ac1 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-detail.html +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-detail.html @@ -94,12 +94,24 @@
-
+
+ +
+
+ +
+
+
diff --git a/forms/common-themes/src/main/resources/theme/email/keycloak/email-verification.ftl b/forms/common-themes/src/main/resources/theme/email/keycloak/email-verification.ftl index 38d150fd5d..301212fff9 100644 --- a/forms/common-themes/src/main/resources/theme/email/keycloak/email-verification.ftl +++ b/forms/common-themes/src/main/resources/theme/email/keycloak/email-verification.ftl @@ -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. \ No newline at end of file +${formatter.format(rb.emailVerificationBody,link, linkExpiration)} \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/email/keycloak/event-login_error.ftl b/forms/common-themes/src/main/resources/theme/email/keycloak/event-login_error.ftl index c1227aa882..cd9d247d9d 100644 --- a/forms/common-themes/src/main/resources/theme/email/keycloak/event-login_error.ftl +++ b/forms/common-themes/src/main/resources/theme/email/keycloak/event-login_error.ftl @@ -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. \ No newline at end of file +${formatter.format(rb.eventLoginErrorBody,event.date,event.ipAddress)} \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/email/keycloak/event-remove_totp.ftl b/forms/common-themes/src/main/resources/theme/email/keycloak/event-remove_totp.ftl index c62e174053..37ae2f73a3 100644 --- a/forms/common-themes/src/main/resources/theme/email/keycloak/event-remove_totp.ftl +++ b/forms/common-themes/src/main/resources/theme/email/keycloak/event-remove_totp.ftl @@ -1 +1 @@ -TOTP was removed from your account on ${event.date?datetime} from ${event.ipAddress}. If this was not you, please contact an admin. \ No newline at end of file +${formatter.format(rb.eventRemoveTotpBody,event.date, event.ipAddress)} \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/email/keycloak/event-update_password.ftl b/forms/common-themes/src/main/resources/theme/email/keycloak/event-update_password.ftl index 696a6e6181..2c88214097 100644 --- a/forms/common-themes/src/main/resources/theme/email/keycloak/event-update_password.ftl +++ b/forms/common-themes/src/main/resources/theme/email/keycloak/event-update_password.ftl @@ -1 +1 @@ -Your password was changed on ${event.date?datetime} from ${event.ipAddress}. If this was not you, please contact an admin. \ No newline at end of file +${formatter.format(rb.eventUpdatePasswordBody,event.date, event.ipAddress)} \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/email/keycloak/event-update_totp.ftl b/forms/common-themes/src/main/resources/theme/email/keycloak/event-update_totp.ftl index 531ae662be..b34a898086 100644 --- a/forms/common-themes/src/main/resources/theme/email/keycloak/event-update_totp.ftl +++ b/forms/common-themes/src/main/resources/theme/email/keycloak/event-update_totp.ftl @@ -1 +1 @@ -TOTP was updated for your account on ${event.date?datetime} from ${event.ipAddress}. If this was not you, please contact an admin. \ No newline at end of file +${formatter.format(rb.eventUpdateTotpBody,event.date, event.ipAddress)} \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages.properties b/forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages.properties deleted file mode 100755 index 3139aca340..0000000000 --- a/forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages.properties +++ /dev/null @@ -1,2 +0,0 @@ -emailVerificationSubject=Verify email -passwordResetSubject=Reset password \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages_de.properties b/forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages_de.properties new file mode 100644 index 0000000000..a5a85f9ecf --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages_de.properties @@ -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. diff --git a/forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages_en.properties new file mode 100755 index 0000000000..e64a00fb68 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages_en.properties @@ -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. diff --git a/forms/common-themes/src/main/resources/theme/email/keycloak/password-reset.ftl b/forms/common-themes/src/main/resources/theme/email/keycloak/password-reset.ftl index 5d277e5e2b..55f0138bef 100644 --- a/forms/common-themes/src/main/resources/theme/email/keycloak/password-reset.ftl +++ b/forms/common-themes/src/main/resources/theme/email/keycloak/password-reset.ftl @@ -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. \ No newline at end of file +${formatter.format(rb.passwordResetBody,link, linkExpiration)} \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/login/base/login-config-totp.ftl b/forms/common-themes/src/main/resources/theme/login/base/login-config-totp.ftl index d71536c965..c884ad4b75 100755 --- a/forms/common-themes/src/main/resources/theme/login/base/login-config-totp.ftl +++ b/forms/common-themes/src/main/resources/theme/login/base/login-config-totp.ftl @@ -23,7 +23,7 @@
- +
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login-oauth-grant.ftl b/forms/common-themes/src/main/resources/theme/login/base/login-oauth-grant.ftl index c881e9c9be..797b62d7ce 100755 --- a/forms/common-themes/src/main/resources/theme/login/base/login-oauth-grant.ftl +++ b/forms/common-themes/src/main/resources/theme/login/base/login-oauth-grant.ftl @@ -4,7 +4,7 @@ <#if section = "title"> ${rb.oauthGrantTitle} <#elseif section = "header"> - Temporary access for ${(realm.name)!''} requested by ${(client.clientId)!''}. + ${formatter.format(rb.oauthGrantTitleHtml,(realm.name!''), (client.clientId!''))} <#elseif section = "form">

${rb.oauthGrantRequest}

@@ -55,8 +55,8 @@
- - + +
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login-reset-password.ftl b/forms/common-themes/src/main/resources/theme/login/base/login-reset-password.ftl index aca7d76ac8..9fe7a15ac8 100755 --- a/forms/common-themes/src/main/resources/theme/login/base/login-reset-password.ftl +++ b/forms/common-themes/src/main/resources/theme/login/base/login-reset-password.ftl @@ -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">
@@ -23,7 +23,7 @@
- +
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login-totp.ftl b/forms/common-themes/src/main/resources/theme/login/base/login-totp.ftl index 613e8f2593..8661fa0fa5 100755 --- a/forms/common-themes/src/main/resources/theme/login/base/login-totp.ftl +++ b/forms/common-themes/src/main/resources/theme/login/base/login-totp.ftl @@ -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} ${realm.name} + ${formatter.format(rb.loginTitleHtml,realm.name)} <#elseif section = "form">
@@ -27,8 +27,8 @@
- - + +
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login-update-password.ftl b/forms/common-themes/src/main/resources/theme/login/base/login-update-password.ftl index 826ef31416..bbc69a27f4 100755 --- a/forms/common-themes/src/main/resources/theme/login/base/login-update-password.ftl +++ b/forms/common-themes/src/main/resources/theme/login/base/login-update-password.ftl @@ -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">
@@ -31,7 +31,7 @@
- +
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login-update-profile.ftl b/forms/common-themes/src/main/resources/theme/login/base/login-update-profile.ftl index 812e29eb70..c3e1d9b303 100755 --- a/forms/common-themes/src/main/resources/theme/login/base/login-update-profile.ftl +++ b/forms/common-themes/src/main/resources/theme/login/base/login-update-profile.ftl @@ -40,7 +40,7 @@
- +
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login-verify-email.ftl b/forms/common-themes/src/main/resources/theme/login/base/login-verify-email.ftl index 19e9bee15b..ac48f95c99 100755 --- a/forms/common-themes/src/main/resources/theme/login/base/login-verify-email.ftl +++ b/forms/common-themes/src/main/resources/theme/login/base/login-verify-email.ftl @@ -6,10 +6,10 @@ ${rb.emailVerifyTitle} <#elseif section = "form">

- ${rb.emailVerifyInstr} + ${rb.emailVerifyInstruction1}

-

${rb.emailVerifyInstrQ} - ${rb.emailVerifyClick} ${rb.emailVerifyResend} +

+ ${rb.emailVerifyInstruction2} ${rb.doClickHere} ${rb.emailVerifyInstruction3}

\ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/login/base/login.ftl b/forms/common-themes/src/main/resources/theme/login/base/login.ftl index e46cf9518d..e514b9ee6c 100755 --- a/forms/common-themes/src/main/resources/theme/login/base/login.ftl +++ b/forms/common-themes/src/main/resources/theme/login/base/login.ftl @@ -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!''))} <#elseif section = "header"> <#if client.application> - ${rb.loginTitle} ${(realm.name)!''} + ${formatter.format(rb.loginTitleHtml,(realm.name!''))} <#elseif client.oauthClient> - Temporary access for ${(realm.name)!''} requested by ${(client.clientId)!''}. + ${formatter.format(rb.loginOauthTitleHtml,(realm.name!''), (client.clientId!''))} <#elseif section = "form"> <#if realm.password> @@ -41,24 +41,24 @@
<#if realm.resetPasswordAllowed> - ${rb.loginForgot} ${rb.password}? + ${rb.doForgotPassword}
- - + +
@@ -75,7 +75,7 @@ <#elseif section = "info" > <#if realm.password && realm.registrationAllowed>
- ${rb.noAccount} ${rb.register} + ${rb.noAccount} ${rb.doRegister}
diff --git a/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties b/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties deleted file mode 100755 index 0a59a69c69..0000000000 --- a/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties +++ /dev/null @@ -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 FreeOTP or Google Authenticator 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 sorry ... -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 \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/login/base/messages/messages_de.properties b/forms/common-themes/src/main/resources/theme/login/base/messages/messages_de.properties new file mode 100644 index 0000000000..70d8aba28c --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/login/base/messages/messages_de.properties @@ -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 {0} +loginTitle=Anmeldung bei {0} +loginTitleHtml=Anmeldung bei {0} +loginOauthTitle=Temporärer zugriff auf {0} +loginOauthTitleHtml=Temporärer zugriff auf {0} angefordert von {1}. +loginTotpTitle=Mobile Authentifizierung Einrichten +loginProfileTitle=Benutzer Konto Informatinen aktualisieren +oauthGrantTitle=OAuth gewähren +oauthGrantTitleHtml=Temporärer zugriff auf {0} angefordert von {1}. +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 FreeOTP oder Google Authenticator 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 \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/login/base/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/login/base/messages/messages_en.properties new file mode 100755 index 0000000000..6f48c59dca --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/login/base/messages/messages_en.properties @@ -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 {0} +loginTitle=Log in to {0} +loginTitleHtml=Log in to {0} +loginOauthTitle=Temporary access for {0} +loginOauthTitleHtml=Temporary access for {0} requested by {1}. +loginTotpTitle=Mobile Authenticator Setup +loginProfileTitle=Update Account Information +oauthGrantTitle=OAuth Grant +oauthGrantTitleHtml=Temporary access for {0} requested by {1}. +errorTitle=We're sorry... +errorTitleHtml=We're sorry ... +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 FreeOTP or Google Authenticator 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. diff --git a/forms/common-themes/src/main/resources/theme/login/base/register.ftl b/forms/common-themes/src/main/resources/theme/login/base/register.ftl index 492fbabd52..8dcdcea481 100755 --- a/forms/common-themes/src/main/resources/theme/login/base/register.ftl +++ b/forms/common-themes/src/main/resources/theme/login/base/register.ftl @@ -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} ${realm.name} + ${formatter.format(rb.registerWithTitleHtml,(realm.name!''))} <#elseif section = "form">
<#if !realm.registrationEmailAsUsername> @@ -116,7 +116,7 @@
- +
diff --git a/forms/common-themes/src/main/resources/theme/login/base/template.ftl b/forms/common-themes/src/main/resources/theme/login/base/template.ftl index 49c02469b2..c09271da62 100644 --- a/forms/common-themes/src/main/resources/theme/login/base/template.ftl +++ b/forms/common-themes/src/main/resources/theme/login/base/template.ftl @@ -21,6 +21,19 @@ + <#if realm.internationalizationEnabled> + + @@ -31,6 +44,15 @@
<#nested "header">
+ <#if realm.internationalizationEnabled> +
+ +
+
<#if displayMessage && message?has_content> diff --git a/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java index df43d16fb2..21972b2c00 100755 --- a/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java +++ b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java @@ -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 Stian Thorgersen @@ -56,7 +56,7 @@ public class FreeMarkerEmailProvider implements EmailProvider { Map attributes = new HashMap(); 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(); + } + } diff --git a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java index 11c26fe767..36a5252515 100755 --- a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java +++ b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java @@ -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 realmRolesRequested, MultivaluedMap 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); diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java index be59af04a3..14579b9378 100755 --- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java +++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java @@ -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 Stian Thorgersen */ -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 httpResponseHeaders = new HashMap(); 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 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; } diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RealmBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RealmBean.java index e4ac27f406..3e36d08c9f 100755 --- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RealmBean.java +++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RealmBean.java @@ -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 Stian Thorgersen */ @@ -60,6 +62,14 @@ public class RealmBean { return realm.isRememberMe(); } + public boolean isInternationalizationEnabled() { + return realm.isInternationalizationEnabled(); + } + + public Set getSupportedLocales(){ + return realm.getSupportedLocales(); + } + public boolean isPassword() { for (RequiredCredentialModel r : realm.getRequiredCredentials()) { if (r.getType().equals(CredentialRepresentation.PASSWORD)) { diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java index 26d67a3bc8..259b09856c 100755 --- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java +++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java @@ -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(); diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java index d8a2141f69..50a13f962a 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java @@ -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 diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java index 28b3d7dd1d..2420721bf5 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java @@ -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 ""; } diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java index 55dc847bf7..51ddfd5efd 100644 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java @@ -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 formparams = new java.util.ArrayList (); + formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD)); formparams.add(new BasicNameValuePair("username", username)); formparams.add(new BasicNameValuePair("password", password)); diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java index e9dfaf608f..f792046bc4 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java @@ -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() { diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java index 9c2129ccf1..be358aa2b0 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java @@ -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; } diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java index c49fa2cf45..90a22047e6 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java @@ -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) diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java index eebac4f91a..27a2949520 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java @@ -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 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 credentials, String sessionId) throws IOException, HttpFailure { + public static AccessTokenResponse invokeAccessCodeToToken(HttpClient client, boolean publicClient, String code, String tokenUrl, String redirectUri, String client_id, Map credentials, String sessionId) throws IOException, HttpFailure { List formparams = new ArrayList(); 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 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 credentials) throws IOException, HttpFailure { + public static AccessTokenResponse invokeRefresh(HttpClient client, boolean publicClient, String refreshToken, String tokenUrl, String client_id, Map credentials) throws IOException, HttpFailure { List formparams = new ArrayList(); for (Map.Entry 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) { diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/DirectAccessGrantsLoginModule.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/DirectAccessGrantsLoginModule.java index da76aae422..a11e1ee4a3 100644 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/DirectAccessGrantsLoginModule.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/DirectAccessGrantsLoginModule.java @@ -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 formparams = new ArrayList(); + formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD)); formparams.add(new BasicNameValuePair("username", username)); formparams.add(new BasicNameValuePair("password", password)); diff --git a/integration/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java b/integration/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java index 40390f9968..a40b83ec3d 100644 --- a/integration/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java +++ b/integration/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java @@ -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()); diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java index 1488d38f3c..20914e5de0 100755 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java @@ -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())); diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java index 3e3d923ea2..f5a796dd24 100644 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java @@ -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() diff --git a/integration/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java b/integration/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java index d3ed5367cb..e69f6c82db 100644 --- a/integration/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java +++ b/integration/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java @@ -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(); diff --git a/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java b/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java index 6944782e2d..04ef9a2246 100755 --- a/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java +++ b/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java @@ -53,7 +53,7 @@ public class JaxrsOAuthClient extends AbstractOAuthClient { for (Map.Entry 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(); diff --git a/integration/js/src/main/resources/keycloak.js b/integration/js/src/main/resources/keycloak.js index 1b0813b0ed..34118c251f 100755 --- a/integration/js/src/main/resources/keycloak.js +++ b/integration/js/src/main/resources/keycloak.js @@ -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); diff --git a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java index 2420c5308a..390721dadb 100755 --- a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java +++ b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java @@ -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) { diff --git a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClientBuilder.java b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClientBuilder.java index 5620636aeb..d642e5c78b 100755 --- a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClientBuilder.java +++ b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClientBuilder.java @@ -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) { diff --git a/model/api/src/main/java/org/keycloak/models/ModelException.java b/model/api/src/main/java/org/keycloak/models/ModelException.java index 1c2f068dad..d3431b056b 100644 --- a/model/api/src/main/java/org/keycloak/models/ModelException.java +++ b/model/api/src/main/java/org/keycloak/models/ModelException.java @@ -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; + } } diff --git a/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java b/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java index dc82bcc5f5..cfcc108db8 100755 --- a/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java +++ b/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java @@ -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 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; } } diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java index ab260a13a3..7308565573 100755 --- a/model/api/src/main/java/org/keycloak/models/RealmModel.java +++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java @@ -244,4 +244,11 @@ public interface RealmModel extends RoleContainerModel { ClientModel findClientById(String id); boolean isIdentityFederationEnabled(); + + boolean isInternationalizationEnabled(); + void setInternationalizationEnabled(boolean enabled); + Set getSupportedLocales(); + void setSupportedLocales(Set locales); + String getDefaultLocale(); + void setDefaultLocale(String locale); } diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java index fe96e5af16..1016341e3a 100755 --- a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java +++ b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java @@ -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); diff --git a/model/api/src/main/java/org/keycloak/models/UserModel.java b/model/api/src/main/java/org/keycloak/models/UserModel.java index 1047ad252b..ee8ce80d0c 100755 --- a/model/api/src/main/java/org/keycloak/models/UserModel.java +++ b/model/api/src/main/java/org/keycloak/models/UserModel.java @@ -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(); diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java index 38baa89319..678fb3326a 100755 --- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java @@ -65,6 +65,10 @@ public class RealmEntity extends AbstractIdentifiableEntity { private String adminAppId; + private boolean internationalizationEnabled; + private List supportedLocales = new ArrayList(); + 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 getSupportedLocales() { + return supportedLocales; + } + + public void setSupportedLocales(List supportedLocales) { + this.supportedLocales = supportedLocales; + } + + public String getDefaultLocale() { + return defaultLocale; + } + + public void setDefaultLocale(String defaultLocale) { + this.defaultLocale = defaultLocale; + } } diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index 686eede868..736aff8b12 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -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; } diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index ce87adae13..c2090d3ce9 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -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(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(rep.getSupportedLocales())); + } + if(rep.getDefaultLocale() != null){ + realm.setDefaultLocale(rep.getDefaultLocale()); + } } // Basic realm stuff diff --git a/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java b/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java index 3481498884..3bde361113 100644 --- a/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java +++ b/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java @@ -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&-")); } } diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java index 5183b74735..99ab488fe4 100755 --- a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java +++ b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java @@ -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 getSupportedLocales() { + return new HashSet<>(realm.getSupportedLocales()); + } + + @Override + public void setSupportedLocales(Set 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; diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java index a58774d369..77b84e64aa 100755 --- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java +++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java @@ -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 getSupportedLocales() { + if (updated != null) return updated.getSupportedLocales(); + return cached.getSupportedLocales(); + } + + @Override + public void setSupportedLocales(Set 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); + } } diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java index dc062401c6..a4ce5bb653 100755 --- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java +++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java @@ -83,6 +83,9 @@ public class CachedRealm { private Map realmRoles = new HashMap(); private Map applications = new HashMap(); private Map clients = new HashMap(); + private boolean internationalizationEnabled; + private Set supportedLocales = new HashSet(); + 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 getIdentityProviders() { return identityProviders; } + + public boolean isInternationalizationEnabled() { + return internationalizationEnabled; + } + + public Set getSupportedLocales() { + return supportedLocales; + } + + public String getDefaultLocale() { + return defaultLocale; + } } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java index a17dd1791d..73add51137 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java @@ -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 getSupportedLocales() { + return realm.getSupportedLocales(); + } + + @Override + public void setSupportedLocales(Set locales) { + realm.setSupportedLocales(locales); + em.flush(); + } + + @Override + public String getDefaultLocale() { + return realm.getDefaultLocale(); + } + + @Override + public void setDefaultLocale(String locale) { + realm.setDefaultLocale(locale); + em.flush(); + } } \ No newline at end of file diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java index 1ba7a5573c..563791eaa0 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java @@ -137,6 +137,18 @@ public class RealmEntity { @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm") protected List identityProviders = new ArrayList(); + @Column(name="INTERNATIONALIZATION_ENABLED") + protected boolean internationalizationEnabled; + + @ElementCollection + @Column(name="VALUE") + @CollectionTable(name="REALM_SUPPORTED_LOCALES", joinColumns={ @JoinColumn(name="REALM_ID") }) + protected Set supportedLocales = new HashSet(); + + @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 getSupportedLocales() { + return supportedLocales; + } + + public void setSupportedLocales(Set supportedLocales) { + this.supportedLocales = supportedLocales; + } + + public String getDefaultLocale() { + return defaultLocale; + } + + public void setDefaultLocale(String defaultLocale) { + this.defaultLocale = defaultLocale; + } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java index 9bee5a52c0..655a45293c 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java @@ -1085,5 +1085,40 @@ public class RealmAdapter extends AbstractMongoAdapter impleme return getId().hashCode(); } + @Override + public boolean isInternationalizationEnabled() { + return realm.isInternationalizationEnabled(); + } + @Override + public void setInternationalizationEnabled(boolean enabled) { + realm.setInternationalizationEnabled(enabled); + updateRealm(); + } + + @Override + public Set getSupportedLocales() { + return new HashSet(realm.getSupportedLocales()); + } + + @Override + public void setSupportedLocales(Set locales) { + if (locales != null) { + realm.setEventsListeners(new ArrayList(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(); + } } diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java index 183ebd571c..0e4b089385 100755 --- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java @@ -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 ); } } diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java index 80420318ba..3e7cd2f300 100755 --- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java @@ -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; } diff --git a/services/src/main/java/org/keycloak/protocol/LoginProtocol.java b/services/src/main/java/org/keycloak/protocol/LoginProtocol.java index 7bd4d033c8..0290771d7a 100755 --- a/services/src/main/java/org/keycloak/protocol/LoginProtocol.java +++ b/services/src/main/java/org/keycloak/protocol/LoginProtocol.java @@ -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); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java index 3900f1aaf3..433acd7846 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java @@ -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(); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java index c2ec74bb4b..b4b0de573d 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java @@ -67,6 +67,9 @@ public class OIDCLoginProtocolService { @Context private KeycloakSession session; + @Context + private HttpHeaders headers; + public OIDCLoginProtocolService(RealmModel realm, EventBuilder event, AuthenticationManager authManager) { this.realm = realm; this.tokenManager = new TokenManager(); @@ -83,9 +86,19 @@ public class OIDCLoginProtocolService { return baseUriBuilder.path(RealmsResource.class).path("{realm}/protocol/" + OIDCLoginProtocol.LOGIN_PROTOCOL); } - public static UriBuilder accessCodeToTokenUrl(UriBuilder baseUriBuilder) { + public static UriBuilder authUrl(UriInfo uriInfo) { + UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder(); + return authUrl(baseUriBuilder); + } + + public static UriBuilder authUrl(UriBuilder baseUriBuilder) { UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder); - return uriBuilder.path(OIDCLoginProtocolService.class, "accessCodeToToken"); + return uriBuilder.path(OIDCLoginProtocolService.class, "auth"); + } + + public static UriBuilder tokenUrl(UriBuilder baseUriBuilder) { + UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder); + return uriBuilder.path(OIDCLoginProtocolService.class, "token"); } public static UriBuilder validateAccessTokenUrl(UriBuilder baseUriBuilder) { @@ -93,21 +106,6 @@ public class OIDCLoginProtocolService { return uriBuilder.path(OIDCLoginProtocolService.class, "validateAccessToken"); } - public static UriBuilder grantAccessTokenUrl(UriBuilder baseUriBuilder) { - UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder); - return uriBuilder.path(OIDCLoginProtocolService.class, "grantAccessToken"); - } - - public static UriBuilder loginPageUrl(UriInfo uriInfo) { - UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder(); - return loginPageUrl(baseUriBuilder); - } - - public static UriBuilder loginPageUrl(UriBuilder baseUriBuilder) { - UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder); - return uriBuilder.path(OIDCLoginProtocolService.class, "loginPage"); - } - public static UriBuilder logoutUrl(UriInfo uriInfo) { UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder(); return logoutUrl(baseUriBuilder); @@ -118,11 +116,6 @@ public class OIDCLoginProtocolService { return uriBuilder.path(OIDCLoginProtocolService.class, "logout"); } - public static UriBuilder refreshUrl(UriBuilder baseUriBuilder) { - UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder); - return uriBuilder.path(OIDCLoginProtocolService.class, "refreshAccessToken"); - } - /** * Authorization endpoint */ @@ -226,7 +219,7 @@ public class OIDCLoginProtocolService { @Path("oauth/oob") @GET public Response installedAppUrnCallback(final @QueryParam("code") String code, final @QueryParam("error") String error, final @QueryParam("error_description") String errorDescription) { - LoginFormsProvider forms = Flows.forms(session, realm, null, uriInfo); + LoginFormsProvider forms = Flows.forms(session, realm, null, uriInfo, headers); if (code != null) { return forms.setClientSessionCode(code).createCode(); } else { diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java index 07fd828c59..da9fdd83fa 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java @@ -30,6 +30,7 @@ import org.keycloak.representations.RefreshToken; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.util.Time; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.UriInfo; import java.io.IOException; import java.util.HashSet; @@ -73,7 +74,7 @@ public class TokenManager { } } - public TokenValidation validateToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, AccessToken oldToken) throws OAuthErrorException { + public TokenValidation validateToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, AccessToken oldToken, HttpHeaders headers) throws OAuthErrorException { UserModel user = session.users().getUserById(oldToken.getSubject(), realm); if (user == null) { throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", "Unknown user"); @@ -85,7 +86,7 @@ public class TokenManager { UserSessionModel userSession = session.sessions().getUserSession(realm, oldToken.getSessionState()); if (!AuthenticationManager.isSessionValid(realm, userSession)) { - AuthenticationManager.logout(session, realm, userSession, uriInfo, connection); + AuthenticationManager.logout(session, realm, userSession, uriInfo, connection, headers); throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Session not active", "Session not active"); } ClientSessionModel clientSession = null; @@ -124,12 +125,12 @@ public class TokenManager { } - public AccessTokenResponse refreshAccessToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, ClientModel authorizedClient, String encodedRefreshToken, EventBuilder event) throws OAuthErrorException { + public AccessTokenResponse refreshAccessToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, ClientModel authorizedClient, String encodedRefreshToken, EventBuilder event, HttpHeaders headers) throws OAuthErrorException { RefreshToken refreshToken = verifyRefreshToken(realm, encodedRefreshToken); event.user(refreshToken.getSubject()).session(refreshToken.getSessionState()).detail(Details.REFRESH_TOKEN_ID, refreshToken.getId()); - TokenValidation validation = validateToken(session, uriInfo, connection, realm, refreshToken); + TokenValidation validation = validateToken(session, uriInfo, connection, realm, refreshToken, headers); // validate authorizedClient is same as validated client if (!validation.clientSession.getClient().getId().equals(authorizedClient.getId())) { throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token. Token client and authorized client don't match"); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java index 6f6441f55e..bebf70341c 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java @@ -24,6 +24,7 @@ import org.keycloak.services.ErrorPageException; 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.flows.Flows; import org.keycloak.services.resources.flows.Urls; @@ -105,8 +106,7 @@ public class AuthorizationEndpoint { * @deprecated */ public AuthorizationEndpoint legacy(String legacyResponseType) { - // TODO Change to warn once adapters has been updated - logger.debugv("Invoking deprecated endpoint {0}", uriInfo.getRequestUri()); + logger.warnv("Invoking deprecated endpoint {0}", uriInfo.getRequestUri()); this.legacyResponseType = legacyResponseType; return this; } @@ -116,7 +116,7 @@ public class AuthorizationEndpoint { action = Action.REGISTER; if (!realm.isRegistrationAllowed()) { - throw new ErrorPageException(session, realm, uriInfo, "Registration not allowed"); + throw new ErrorPageException(session, realm, uriInfo, headers, Messages.REGISTRATION_NOT_ALLOWED); } return this; @@ -148,21 +148,21 @@ public class AuthorizationEndpoint { private void checkSsl() { if (!uriInfo.getBaseUri().getScheme().equals("https") && realm.getSslRequired().isRequired(clientConnection)) { event.error(Errors.SSL_REQUIRED); - throw new ErrorPageException(session, realm, uriInfo, "HTTPS required"); + throw new ErrorPageException(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED); } } private void checkRealm() { if (!realm.isEnabled()) { event.error(Errors.REALM_DISABLED); - throw new ErrorPageException(session, realm, uriInfo, "Realm not enabled"); + throw new ErrorPageException(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED); } } private void checkClient() { if (clientId == null) { event.error(Errors.INVALID_REQUEST); - throw new ErrorPageException(session, realm, uriInfo, "Missing paramater: " + OIDCLoginProtocol.CLIENT_ID_PARAM); + throw new ErrorPageException(session, realm, uriInfo, headers, Messages.MISSING_PARAMETER, OIDCLoginProtocol.CLIENT_ID_PARAM ); } event.client(clientId); @@ -170,17 +170,17 @@ public class AuthorizationEndpoint { client = realm.findClient(clientId); if (client == null) { event.error(Errors.CLIENT_NOT_FOUND); - throw new ErrorPageException(session, realm, uriInfo, "Client not found"); + throw new ErrorPageException(session, realm, uriInfo, headers, Messages.CLIENT_NOT_FOUND ); } if ((client instanceof ApplicationModel) && ((ApplicationModel) client).isBearerOnly()) { event.error(Errors.NOT_ALLOWED); - throw new ErrorPageException(session, realm, uriInfo, "Bearer only clients are not allowed to initiate browser login"); + throw new ErrorPageException(session, realm, uriInfo, headers, Messages.BEARER_ONLY ); } if (client.isDirectGrantsOnly()) { event.error(Errors.NOT_ALLOWED); - throw new ErrorPageException(session, realm, uriInfo, "Direct grants only clients are not allowed to initiate browser login"); + throw new ErrorPageException(session, realm, uriInfo, headers, Messages.DIRECT_GRANTS_ONLY); } } @@ -190,7 +190,7 @@ public class AuthorizationEndpoint { responseType = legacyResponseType; } else { event.error(Errors.INVALID_REQUEST); - throw new ErrorPageException(session, realm, uriInfo, "Missing query parameter: " + OIDCLoginProtocol.RESPONSE_TYPE_PARAM); + throw new ErrorPageException(session, realm, uriInfo, headers, Messages.MISSING_PARAMETER, OIDCLoginProtocol.RESPONSE_TYPE_PARAM ); } } @@ -200,7 +200,7 @@ public class AuthorizationEndpoint { action = Action.CODE; } else { event.error(Errors.INVALID_REQUEST); - throw new ErrorPageException(session, realm, uriInfo, "Invalid " + OIDCLoginProtocol.RESPONSE_TYPE_PARAM); + throw new ErrorPageException(session, realm, uriInfo, headers, Messages.INVALID_PARAMETER, OIDCLoginProtocol.RESPONSE_TYPE_PARAM ); } } @@ -210,7 +210,7 @@ public class AuthorizationEndpoint { redirectUri = RedirectUtils.verifyRedirectUri(uriInfo, redirectUriParam, realm, client); if (redirectUri == null) { event.error(Errors.INVALID_REDIRECT_URI); - throw new ErrorPageException(session, realm, uriInfo, "Invalid " + OIDCLoginProtocol.REDIRECT_URI_PARAM); + throw new ErrorPageException(session, realm, uriInfo, headers, Messages.INVALID_PARAMETER, OIDCLoginProtocol.REDIRECT_URI_PARAM); } } @@ -237,8 +237,8 @@ public class AuthorizationEndpoint { IdentityProviderModel identityProviderModel = realm.getIdentityProviderById(idpHint); if (identityProviderModel == null) { - return Flows.forms(session, realm, null, uriInfo) - .setError("Could not find an identity provider with the identifier [" + idpHint + "].") + return Flows.forms(session, realm, null, uriInfo, headers) + .setError(Messages.IDENTITY_PROVIDER_NOT_FOUND, idpHint) .createErrorPage(); } return buildRedirectToIdentityProvider(idpHint, accessCode); @@ -249,11 +249,11 @@ public class AuthorizationEndpoint { // 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(); if (prompt != null && prompt.equals("none")) { - OIDCLoginProtocol oauth = new OIDCLoginProtocol(session, realm, uriInfo); + OIDCLoginProtocol oauth = new OIDCLoginProtocol(session, realm, uriInfo, headers); return oauth.cancelLogin(clientSession); } @@ -271,13 +271,13 @@ public class AuthorizationEndpoint { return buildRedirectToIdentityProvider(identityProviders.get(0).getId(), accessCode); } - return Flows.forms(session, realm, null, uriInfo).setError("Realm [" + realm.getName() + "] supports multiple identity providers. Could not determine which identity provider should be used to authenticate with.").createErrorPage(); + return Flows.forms(session, realm, null, uriInfo, headers).setError(Messages.IDENTITY_PROVIDER_NOT_UNIQUE, realm.getName()).createErrorPage(); } - return Flows.forms(session, realm, null, uriInfo).setError("Realm [" + realm.getName() + "] does not support any credential type.").createErrorPage(); + return Flows.forms(session, realm, null, uriInfo, headers).setError(Messages.REALM_SUPPORTS_NO_CREDENTIALS, realm.getName()).createErrorPage(); } - LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo) + LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers) .setClientSessionCode(accessCode); // Attach state from SPNEGO authentication @@ -306,7 +306,7 @@ public class AuthorizationEndpoint { private Response buildRegister() { authManager.expireIdentityCookie(realm, uriInfo, clientConnection); - return Flows.forms(session, realm, client, uriInfo) + return Flows.forms(session, realm, client, uriInfo, headers) .setClientSessionCode(new ClientSessionCode(realm, clientSession).getCode()) .createRegistration(); } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java index 16ab80c043..a47aa6a2c1 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java @@ -21,6 +21,7 @@ import org.keycloak.protocol.oidc.utils.RedirectUtils; import org.keycloak.representations.RefreshToken; import org.keycloak.services.ErrorResponseException; import org.keycloak.services.managers.AuthenticationManager; +import org.keycloak.services.messages.Messages; import org.keycloak.services.resources.Cors; import org.keycloak.services.resources.flows.Flows; @@ -91,7 +92,7 @@ public class LogoutEndpoint { if (redirectUri != null) { String validatedRedirect = RedirectUtils.verifyRealmRedirectUri(uriInfo, redirectUri, realm); if (validatedRedirect == null) { - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect uri."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REDIRECT_URI); } return Response.status(302).location(UriBuilder.fromUri(validatedRedirect).build()).build(); } else { @@ -143,7 +144,7 @@ public class LogoutEndpoint { } private void logout(UserSessionModel userSession) { - authManager.logout(session, realm, userSession, uriInfo, clientConnection); + authManager.logout(session, realm, userSession, uriInfo, clientConnection, headers); event.user(userSession.getUser()).session(userSession).success(); } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java index ab78e6c7a9..d8f9db2815 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java @@ -47,6 +47,8 @@ import java.util.Map; public class TokenEndpoint { private static final Logger logger = Logger.getLogger(TokenEndpoint.class); + private MultivaluedMap formParams; + private ClientModel client; private enum Action { AUTHORIZATION_CODE, REFRESH_TOKEN, PASSWORD @@ -74,10 +76,7 @@ public class TokenEndpoint { private Action action; - private String clientId; private String grantType; - private String code; - private String redirectUri; private String legacyGrantType; @@ -89,15 +88,14 @@ public class TokenEndpoint { } @POST - @Consumes(MediaType.APPLICATION_FORM_URLENCODED) - public Response build(final MultivaluedMap formData) { + public Response build() { switch (action) { case AUTHORIZATION_CODE: - return buildAuthorizationCodeAccessTokenResponse(formData); + return buildAuthorizationCodeAccessTokenResponse(); case REFRESH_TOKEN: - return buildRefreshToken(formData); + return buildRefreshToken(); case PASSWORD: - return buildResourceOwnerPasswordCredentialsGrant(formData); + return buildResourceOwnerPasswordCredentialsGrant(); } throw new RuntimeException("Unknown action " + action); @@ -115,23 +113,19 @@ public class TokenEndpoint { * @deprecated */ public TokenEndpoint legacy(String legacyGrantType) { - // TODO Change to warn once adapters has been updated - logger.debugv("Invoking deprecated endpoint {0}", uriInfo.getRequestUri()); + logger.warnv("Invoking deprecated endpoint {0}", uriInfo.getRequestUri()); this.legacyGrantType = legacyGrantType; return this; } public TokenEndpoint init() { - MultivaluedMap params = uriInfo.getQueryParameters(); - - clientId = params.getFirst(OIDCLoginProtocol.CLIENT_ID_PARAM); - grantType = params.getFirst(OIDCLoginProtocol.GRANT_TYPE_PARAM); - code = params.getFirst(OIDCLoginProtocol.CODE_PARAM); - redirectUri = params.getFirst(OIDCLoginProtocol.REDIRECT_URI_PARAM); + formParams = request.getDecodedFormParameters(); + grantType = formParams.getFirst(OIDCLoginProtocol.GRANT_TYPE_PARAM); checkSsl(); checkRealm(); checkGrantType(); + checkClient(); return this; } @@ -148,15 +142,13 @@ public class TokenEndpoint { } } - private ClientModel authorizeClient(final MultivaluedMap formData) { + private void checkClient() { String authorizationHeader = headers.getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION); - ClientModel client = AuthorizeClientUtil.authorizeClient(authorizationHeader, formData, event, realm); + client = AuthorizeClientUtil.authorizeClient(authorizationHeader, formParams, event, realm); if ((client instanceof ApplicationModel) && ((ApplicationModel) client).isBearerOnly()) { throw new ErrorResponseException("invalid_client", "Bearer-only not allowed", Response.Status.BAD_REQUEST); } - - return client; } private void checkGrantType() { @@ -182,8 +174,8 @@ public class TokenEndpoint { } } - public Response buildAuthorizationCodeAccessTokenResponse(final MultivaluedMap formData) { - String code = formData.getFirst(OAuth2Constants.CODE); + public Response buildAuthorizationCodeAccessTokenResponse() { + String code = formParams.getFirst(OAuth2Constants.CODE); if (code == null) { event.error(Errors.INVALID_CODE); throw new ErrorResponseException("invalid_request", "Missing parameter: " + OAuth2Constants.CODE, Response.Status.BAD_REQUEST); @@ -214,10 +206,8 @@ public class TokenEndpoint { event.user(userSession.getUser()); event.session(userSession.getId()); - ClientModel client = authorizeClient(formData); - String redirectUri = clientSession.getNote(OIDCLoginProtocol.REDIRECT_URI_PARAM); - if (redirectUri != null && !redirectUri.equals(formData.getFirst(OAuth2Constants.REDIRECT_URI))) { + if (redirectUri != null && !redirectUri.equals(formParams.getFirst(OAuth2Constants.REDIRECT_URI))) { event.error(Errors.INVALID_CODE); throw new ErrorResponseException("invalid_grant", "Incorrect redirect_uri", Response.Status.BAD_REQUEST); } @@ -243,9 +233,9 @@ public class TokenEndpoint { throw new ErrorResponseException("invalid_grant", "Session not active", Response.Status.BAD_REQUEST); } - String adapterSessionId = formData.getFirst(AdapterConstants.APPLICATION_SESSION_STATE); + String adapterSessionId = formParams.getFirst(AdapterConstants.APPLICATION_SESSION_STATE); if (adapterSessionId != null) { - String adapterSessionHost = formData.getFirst(AdapterConstants.APPLICATION_SESSION_HOST); + String adapterSessionHost = formParams.getFirst(AdapterConstants.APPLICATION_SESSION_HOST); logger.debugf("Adapter Session '%s' saved in ClientSession for client '%s'. Host is '%s'", adapterSessionId, client.getClientId(), adapterSessionHost); event.detail(AdapterConstants.APPLICATION_SESSION_STATE, adapterSessionId); @@ -266,17 +256,15 @@ public class TokenEndpoint { return Cors.add(request, Response.ok(res).type(MediaType.APPLICATION_JSON_TYPE)).auth().allowedOrigins(client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build(); } - public Response buildRefreshToken(final MultivaluedMap formData) { - ClientModel client = authorizeClient(formData); - - String refreshToken = formData.getFirst(OAuth2Constants.REFRESH_TOKEN); + public Response buildRefreshToken() { + String refreshToken = formParams.getFirst(OAuth2Constants.REFRESH_TOKEN); if (refreshToken == null) { throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "No refresh token", Response.Status.BAD_REQUEST); } AccessTokenResponse res; try { - res = tokenManager.refreshAccessToken(session, uriInfo, clientConnection, realm, client, refreshToken, event); + res = tokenManager.refreshAccessToken(session, uriInfo, clientConnection, realm, client, refreshToken, event, headers); } catch (OAuthErrorException e) { event.error(Errors.INVALID_TOKEN); throw new ErrorResponseException(e.getError(), e.getDescription(), Response.Status.BAD_REQUEST); @@ -287,14 +275,14 @@ public class TokenEndpoint { return Cors.add(request, Response.ok(res, MediaType.APPLICATION_JSON_TYPE)).auth().allowedOrigins(client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build(); } - public Response buildResourceOwnerPasswordCredentialsGrant(final MultivaluedMap formData) { + public Response buildResourceOwnerPasswordCredentialsGrant() { if (!realm.isPasswordCredentialGrantAllowed()) { throw new ErrorResponseException("not_enabled", "Direct Grant REST API not enabled", Response.Status.FORBIDDEN); } event.detail(Details.AUTH_METHOD, "oauth_credentials").detail(Details.RESPONSE_TYPE, "token"); - String username = formData.getFirst(AuthenticationManager.FORM_USERNAME); + String username = formParams.getFirst(AuthenticationManager.FORM_USERNAME); if (username == null) { event.error(Errors.USERNAME_MISSING); throw new ErrorResponseException("invalid_request", "Missing parameter: username", Response.Status.UNAUTHORIZED); @@ -304,9 +292,7 @@ public class TokenEndpoint { UserModel user = KeycloakModelUtils.findUserByNameOrEmail(session, realm, username); if (user != null) event.user(user); - ClientModel client = authorizeClient(formData); - - AuthenticationManager.AuthenticationStatus authenticationStatus = authManager.authenticateForm(session, clientConnection, realm, formData); + AuthenticationManager.AuthenticationStatus authenticationStatus = authManager.authenticateForm(session, clientConnection, realm, formParams); Map err; switch (authenticationStatus) { @@ -324,7 +310,7 @@ public class TokenEndpoint { throw new ErrorResponseException("invalid_grant", "Invalid user credentials", Response.Status.UNAUTHORIZED); } - String scope = formData.getFirst(OAuth2Constants.SCOPE); + String scope = formParams.getFirst(OAuth2Constants.SCOPE); UserSessionProvider sessions = session.sessions(); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/ValidateTokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/ValidateTokenEndpoint.java index caef4361e0..8dad6fd490 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/ValidateTokenEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/ValidateTokenEndpoint.java @@ -20,10 +20,7 @@ import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; +import javax.ws.rs.core.*; import java.util.HashMap; import java.util.Map; @@ -43,6 +40,9 @@ public class ValidateTokenEndpoint { @Context private UriInfo uriInfo; + @Context + private HttpHeaders headers; + private TokenManager tokenManager; private RealmModel realm; private EventBuilder event; @@ -81,7 +81,7 @@ public class ValidateTokenEndpoint { event.user(token.getSubject()).session(token.getSessionState()).detail(Details.VALIDATE_ACCESS_TOKEN, token.getId()); try { - tokenManager.validateToken(session, uriInfo, clientConnection, realm, token); + tokenManager.validateToken(session, uriInfo, clientConnection, realm, token, headers); } catch (OAuthErrorException e) { Map error = new HashMap(); error.put(OAuth2Constants.ERROR, e.getError()); diff --git a/services/src/main/java/org/keycloak/services/ErrorPageException.java b/services/src/main/java/org/keycloak/services/ErrorPageException.java index 3f8d435ff0..634ea7a7ac 100644 --- a/services/src/main/java/org/keycloak/services/ErrorPageException.java +++ b/services/src/main/java/org/keycloak/services/ErrorPageException.java @@ -5,6 +5,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.services.resources.flows.Flows; import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; @@ -16,18 +17,22 @@ public class ErrorPageException extends WebApplicationException { private final KeycloakSession session; private final RealmModel realm; private final UriInfo uriInfo; + private final HttpHeaders httpHeaders; private final String errorMessage; + private final Object[] parameters; - public ErrorPageException(KeycloakSession session, RealmModel realm, UriInfo uriInfo, String errorMessage) { + public ErrorPageException(KeycloakSession session, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, String errorMessage, Object ... parameters) { this.session = session; this.realm = realm; this.uriInfo = uriInfo; + this.httpHeaders = headers; this.errorMessage = errorMessage; + this.parameters = parameters; } @Override public Response getResponse() { - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, errorMessage); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, httpHeaders, errorMessage, parameters); } } diff --git a/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java b/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java index e3bb56971a..9e0e4ab161 100755 --- a/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java @@ -42,7 +42,7 @@ public class AppAuthManager extends AuthenticationManager { public AuthResult authenticateBearerToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) { String tokenString = extractAuthorizationHeaderToken(headers); if (tokenString == null) return null; - AuthResult authResult = verifyIdentityToken(session, realm, uriInfo, connection, true, tokenString); + AuthResult authResult = verifyIdentityToken(session, realm, uriInfo, connection, true, tokenString, headers); return authResult; } diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java index aa27f17a94..f37b83781d 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -79,7 +79,7 @@ public class AuthenticationManager { return userSession != null && userSession.getLastSessionRefresh() + realm.getSsoSessionIdleTimeout() > currentTime && max > currentTime; } - public static void logout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection) { + public static void logout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) { if (userSession == null) return; UserModel user = userSession.getUser(); userSession.setState(UserSessionModel.State.LOGGING_OUT); @@ -95,6 +95,7 @@ public class AuthenticationManager { if (authMethod == null) continue; // must be a keycloak service like account LoginProtocol protocol = session.getProvider(LoginProtocol.class, authMethod); protocol.setRealm(realm) + .setHttpHeaders(headers) .setUriInfo(uriInfo); protocol.backchannelLogout(userSession, clientSession); clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT); @@ -105,7 +106,7 @@ public class AuthenticationManager { } - public static Response browserLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection) { + public static Response browserLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) { if (userSession == null) return null; UserModel user = userSession.getUser(); @@ -128,6 +129,7 @@ public class AuthenticationManager { if (authMethod == null) continue; // must be a keycloak service like account LoginProtocol protocol = session.getProvider(LoginProtocol.class, authMethod); protocol.setRealm(realm) + .setHttpHeaders(headers) .setUriInfo(uriInfo); try { logger.debugv("backchannel logout to: {0}", client.getClientId()); @@ -140,12 +142,13 @@ public class AuthenticationManager { } if (redirectClients.size() == 0) { - return finishBrowserLogout(session, realm, userSession, uriInfo, connection); + return finishBrowserLogout(session, realm, userSession, uriInfo, connection, headers); } for (ClientSessionModel nextRedirectClient : redirectClients) { String authMethod = nextRedirectClient.getAuthMethod(); LoginProtocol protocol = session.getProvider(LoginProtocol.class, authMethod); protocol.setRealm(realm) + .setHttpHeaders(headers) .setUriInfo(uriInfo); // setting this to logged out cuz I"m not sure protocols can always verify that the client was logged out or not nextRedirectClient.setAction(ClientSessionModel.Action.LOGGED_OUT); @@ -161,16 +164,17 @@ public class AuthenticationManager { } } - return finishBrowserLogout(session, realm, userSession, uriInfo, connection); + return finishBrowserLogout(session, realm, userSession, uriInfo, connection, headers); } - protected static Response finishBrowserLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection) { + protected static Response finishBrowserLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) { expireIdentityCookie(realm, uriInfo, connection); expireRememberMeCookie(realm, uriInfo, connection); userSession.setState(UserSessionModel.State.LOGGED_OUT); String method = userSession.getNote(KEYCLOAK_LOGOUT_PROTOCOL); LoginProtocol protocol = session.getProvider(LoginProtocol.class, method); protocol.setRealm(realm) + .setHttpHeaders(headers) .setUriInfo(uriInfo); Response response = protocol.finishLogout(userSession); session.sessions().removeUserSession(realm, userSession); @@ -285,7 +289,7 @@ public class AuthenticationManager { } String tokenString = cookie.getValue(); - AuthResult authResult = verifyIdentityToken(session, realm, uriInfo, connection, checkActive, tokenString); + AuthResult authResult = verifyIdentityToken(session, realm, uriInfo, connection, checkActive, tokenString, headers); if (authResult == null) { expireIdentityCookie(realm, uriInfo, connection); return null; @@ -335,6 +339,7 @@ public class AuthenticationManager { if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getUser().getUsername(), uriInfo, clientConnection); LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod()); protocol.setRealm(realm) + .setHttpHeaders(request.getHttpHeaders()) .setUriInfo(uriInfo); return protocol.authenticated(userSession, new ClientSessionCode(realm, clientSession)); @@ -364,7 +369,7 @@ public class AuthenticationManager { UserModel.RequiredAction action = user.getRequiredActions().iterator().next(); accessCode.setRequiredAction(action); - LoginFormsProvider loginFormsProvider = Flows.forms(session, realm, client, uriInfo).setClientSessionCode(accessCode.getCode()).setUser(user); + LoginFormsProvider loginFormsProvider = Flows.forms(session, realm, client, uriInfo, request.getHttpHeaders()).setClientSessionCode(accessCode.getCode()).setUser(user); if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL)) { event.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success(); LoginActionsService.createActionCookie(realm, uriInfo, clientConnection, userSession.getId()); @@ -387,7 +392,7 @@ public class AuthenticationManager { } } - return Flows.forms(session, realm, client, uriInfo) + return Flows.forms(session, realm, client, uriInfo, request.getHttpHeaders()) .setClientSessionCode(accessCode.getCode()) .setAccessRequest(realmRoles, resourceRoles) .setClient(client) @@ -415,7 +420,7 @@ public class AuthenticationManager { } } - protected AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, String tokenString) { + protected AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, String tokenString, HttpHeaders headers) { try { AccessToken token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), realm.getName(), checkActive); if (checkActive) { @@ -435,7 +440,7 @@ public class AuthenticationManager { UserSessionModel userSession = session.sessions().getUserSession(realm, token.getSessionState()); if (!isSessionValid(realm, userSession)) { - if (userSession != null) logout(session, realm, userSession, uriInfo, connection); + if (userSession != null) logout(session, realm, userSession, uriInfo, connection, headers); logger.debug("User session not active"); return null; } diff --git a/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java index 7dca9c1482..702b684aba 100644 --- a/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java @@ -58,7 +58,7 @@ public class HttpAuthenticationManager { } - public HttpAuthOutput spnegoAuthenticate() { + public HttpAuthOutput spnegoAuthenticate(HttpHeaders headers) { boolean kerberosSupported = false; for (RequiredCredentialModel c : realm.getRequiredCredentials()) { if (c.getType().equals(CredentialRepresentation.KERBEROS)) { @@ -96,7 +96,7 @@ public class HttpAuthenticationManager { CredentialValidationOutput output = session.users().validCredentials(realm, spnegoCredential); if (output.getAuthStatus() == CredentialValidationOutput.Status.AUTHENTICATED) { - return sendResponse(output.getAuthenticatedUser(), output.getState(), "spnego"); + return sendResponse(output.getAuthenticatedUser(), output.getState(), "spnego", headers); } else { String spnegoResponseToken = (String) output.getState().get(KerberosConstants.RESPONSE_TOKEN); return challengeNegotiation(spnegoResponseToken); @@ -106,7 +106,7 @@ public class HttpAuthenticationManager { // Send response after successful authentication - private HttpAuthOutput sendResponse(UserModel user, Map authState, String authMethod) { + private HttpAuthOutput sendResponse(UserModel user, Map authState, String authMethod, HttpHeaders headers) { if (logger.isTraceEnabled()) { logger.trace("User " + user.getUsername() + " authenticated with " + authMethod); } @@ -114,7 +114,7 @@ public class HttpAuthenticationManager { Response response; if (!user.isEnabled()) { event.error(Errors.USER_DISABLED); - response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, Messages.ACCOUNT_DISABLED); + response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.ACCOUNT_DISABLED); } else { UserSessionModel userSession = session.sessions().createUserSession(realm, user, user.getUsername(), clientConnection.getRemoteAddr(), authMethod, false); diff --git a/services/src/main/java/org/keycloak/services/messages/Messages.java b/services/src/main/java/org/keycloak/services/messages/Messages.java index 0ad03522f8..a1cc072e9b 100755 --- a/services/src/main/java/org/keycloak/services/messages/Messages.java +++ b/services/src/main/java/org/keycloak/services/messages/Messages.java @@ -26,67 +26,151 @@ package org.keycloak.services.messages; */ public class Messages { - public static final String ACCOUNT_DISABLED = "accountDisabled"; - public static final String ACCOUNT_TEMPORARILY_DISABLED = "accountTemporarilyDisabled"; + public static final String INVALID_USER = "invalidUserMessage"; - public static final String INVALID_PASSWORD = "invalidPassword"; + public static final String INVALID_EMAIL = "invalidEmailMessage"; - public static final String INVALID_PASSWORD_EXISTING = "invalidPasswordExisting"; + public static final String ACCOUNT_DISABLED = "accountDisabledMessage"; - public static final String INVALID_PASSWORD_CONFIRM = "invalidPasswordConfirm"; + public static final String ACCOUNT_TEMPORARILY_DISABLED = "accountTemporarilyDisabledMessage"; - public static final String INVALID_EMAIL = "invalidEmail"; + public static final String EXPIRED_CODE = "expiredCodeMessage"; - public static final String INVALID_USER = "invalidUser"; + public static final String MISSING_FIRST_NAME = "missingFirstNameMessage"; - public static final String EXPIRED_CODE = "expiredCode"; + public static final String MISSING_LAST_NAME = "missingLastNameMessage"; - public static final String READ_ONLY_USER = "readOnlyUser"; + public static final String MISSING_EMAIL = "missingEmailMessage"; - public static final String READ_ONLY_PASSWORD = "readOnlyPassword"; + public static final String MISSING_USERNAME = "missingUsernameMessage"; - public static final String MISSING_EMAIL = "missingEmail"; + public static final String MISSING_PASSWORD = "missingPasswordMessage"; - public static final String MISSING_FIRST_NAME = "missingFirstName"; + public static final String MISSING_TOTP = "missingTotpMessage"; - public static final String MISSING_LAST_NAME = "missingLastName"; + public static final String NOTMATCH_PASSWORD = "notMatchPasswordMessage"; - public static final String MISSING_PASSWORD = "missingPassword"; + public static final String INVALID_PASSWORD_EXISTING = "invalidPasswordExistingMessage"; - public static final String NOTMATCH_PASSWORD = "notMatchPassword"; + public static final String INVALID_PASSWORD_CONFIRM = "invalidPasswordConfirmMessage"; - public static final String MISSING_USERNAME = "missingUsername"; + public static final String INVALID_TOTP = "invalidTotpMessage"; - public static final String MISSING_TOTP = "missingTotp"; + public static final String USERNAME_EXISTS = "usernameExistsMessage"; - public static final String INVALID_TOTP = "invalidTotp"; + public static final String EMAIL_EXISTS = "emailExistsMessage"; - public static final String USERNAME_EXISTS = "usernameExists"; + public static final String FEDERATED_IDENTITY_EMAIL_EXISTS = "federatedIdentityEmailExistsMessage"; - public static final String EMAIL_EXISTS = "emailExists"; + public static final String FEDERATED_IDENTITY_USERNAME_EXISTS = "federatedIdentityUsernameExistsMessage"; - public static final String ACTION_WARN_TOTP = "actionTotpWarning"; + public static final String CONFIGURE_TOTP = "configureTotpMessage"; - public static final String ACTION_WARN_PROFILE = "actionProfileWarning"; + public static final String UPDATE_PROFILE = "updateProfileMessage"; - public static final String ACTION_WARN_PASSWD = "actionPasswordWarning"; + public static final String UPDATE_PASSWORD = "updatePasswordMessage"; - public static final String ACTION_WARN_EMAIL = "actionEmailWarning"; + public static final String VERIFY_EMAIL = "verifyEmailMessage"; - public static final String MISSING_IDENTITY_PROVIDER = "missingIdentityProvider"; + public static final String EMAIL_VERIFIED = "emailVerifiedMessage"; - public static final String INVALID_FEDERATED_IDENTITY_ACTION = "invalidFederatedIdentityAction"; + public static final String EMAIL_SENT = "emailSentMessage"; - public static final String IDENTITY_PROVIDER_NOT_FOUND = "identityProviderNotFound"; + public static final String EMAIL_SENT_ERROR = "emailSendErrorMessage"; - public static final String FEDERATED_IDENTITY_NOT_ACTIVE = "federatedIdentityLinkNotActive"; + public static final String ACCOUNT_UPDATED = "accountUpdatedMessage"; - public static final String FEDERATED_IDENTITY_REMOVING_LAST_PROVIDER = "federatedIdentityRemovingLastProvider"; + public static final String ACCOUNT_PASSWORD_UPDATED = "accountPasswordUpdatedMessage"; - public static final String IDENTITY_PROVIDER_REDIRECT_ERROR = "identityProviderRedirectError"; + public static final String NO_ACCESS = "noAccessMessage"; - public static final String IDENTITY_PROVIDER_REMOVED = "identityProviderRemoved"; + public static final String FAILED_TO_PROCESS_RESPONSE = "failedToProcessResponseMessage"; - public static final String ERROR = "error"; + public static final String HTTPS_REQUIRED = "httpsRequiredMessage"; + public static final String REALM_NOT_ENABLED = "realmNotEnabledMessage"; + + public static final String INVALID_REQUEST = "invalidRequestMessage"; + + public static final String INVALID_REQUESTER = "invalidRequesterMessage"; + + public static final String UNKNOWN_LOGIN_REQUESTER = "unknownLoginRequesterMessage"; + + public static final String LOGIN_REQUESTER_NOT_ENABLED = "loginRequesterNotEnabledMessage"; + + public static final String BEARER_ONLY = "bearerOnlyMessage"; + + public static final String DIRECT_GRANTS_ONLY = "directGrantsOnlyMessage"; + + public static final String INVALID_REDIRECT_URI = "invalidRedirectUriMessage"; + + public static final String UNSUPPORTED_NAME_ID_FORMAT = "unsupportedNameIdFormatMessage"; + + public static final String REGISTRATION_NOT_ALLOWED = "registrationNotAllowedMessage"; + + public static final String PERMISSION_NOT_APPROVED = "permissionNotApprovedMessage"; + + public static final String NO_RELAY_STATE_IN_RESPONSE = "noRelayStateInResponseMessage"; + + public static final String IDENTITY_PROVIDER_ALREADY_LINKED = "identityProviderAlreadyLinkedMessage"; + + public static final String INSUFFICIENT_PERMISSION = "insufficientPermissionMessage"; + + public static final String COULD_NOT_PROCEED_WITH_AUTHENTICATION_REQUEST = "couldNotProceedWithAuthenticationRequestMessage"; + + public static final String COULD_NOT_OBTAIN_TOKEN = "couldNotObtainTokenMessage"; + + public static final String UNEXPECTED_ERROR_RETRIEVING_TOKEN = "unexpectedErrorRetrievingTokenMessage"; + + public static final String IDENTITY_PROVIDER_AUTHENTICATION_FAILED = "identityProviderAuthenticationFailedMessage"; + + public static final String UNEXPECTED_ERROR_HANDLING_RESPONSE = "unexpectedErrorHandlingResponseMessage"; + + public static final String COULD_NOT_SEND_AUTHENTICATION_REQUEST = "couldNotSendAuthenticationRequestMessage"; + + public static final String UNEXPECTED_ERROR_HANDLING_REQUEST = "unexpectedErrorHandlingRequestMessage"; + + public static final String INVALID_ACCESS_CODE = "invalidAccessCodeMessage"; + + public static final String SESSION_NOT_ACTIVE = "sessionNotActiveMessage"; + + public static final String UNKNOWN_CODE = "unknownCodeMessage"; + + public static final String INVALID_CODE = "invalidCodeMessage"; + + public static final String IDENTITY_PROVIDER_UNEXPECTED_ERROR = "identityProviderUnexpectedErrorMessage"; + + public static final String IDENTITY_PROVIDER_NOT_FOUND = "identityProviderNotFoundMessage"; + + public static final String IDENTITY_PROVIDER_NOT_UNIQUE = "identityProviderNotUniqueMessage"; + + public static final String REALM_SUPPORTS_NO_CREDENTIALS = "realmSupportsNoCredentialsMessage"; + + public static final String READ_ONLY_USER = "readOnlyUserMessage"; + + public static final String READ_ONLY_PASSWORD = "readOnlyPasswordMessage"; + + public static final String SUCCESS_TOTP_REMOVED = "successTotpRemovedMessage"; + + public static final String SUCCESS_TOTP = "successTotpMessage"; + + public static final String MISSING_IDENTITY_PROVIDER = "missingIdentityProviderMessage"; + + public static final String INVALID_FEDERATED_IDENTITY_ACTION = "invalidFederatedIdentityActionMessage"; + + public static final String FEDERATED_IDENTITY_NOT_ACTIVE = "federatedIdentityLinkNotActiveMessage"; + + public static final String FEDERATED_IDENTITY_REMOVING_LAST_PROVIDER = "federatedIdentityRemovingLastProviderMessage"; + + public static final String IDENTITY_PROVIDER_REDIRECT_ERROR = "identityProviderRedirectErrorMessage"; + + public static final String IDENTITY_PROVIDER_REMOVED = "identityProviderRemovedMessage"; + + public static final String MISSING_PARAMETER = "missingParameterMessage"; + + public static final String CLIENT_NOT_FOUND = "clientNotFoundMessage"; + + public static final String INVALID_PARAMETER = "invalidParameterMessage"; + + public static final String FEDERATED_IDENTITY_REGISTRATION_EMAIL_MISSING = "federatedIdentityRegistrationEmailMissingMessage"; } diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java index 8b5297b298..fae89525c7 100755 --- a/services/src/main/java/org/keycloak/services/resources/AccountService.java +++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java @@ -25,6 +25,7 @@ import org.jboss.logging.Logger; import org.jboss.resteasy.spi.BadRequestException; import org.jboss.resteasy.spi.HttpRequest; import org.keycloak.ClientConnection; +import org.keycloak.OAuth2Constants; import org.keycloak.account.AccountPages; import org.keycloak.account.AccountProvider; import org.keycloak.events.Details; @@ -32,20 +33,7 @@ import org.keycloak.events.Event; import org.keycloak.events.EventBuilder; import org.keycloak.events.EventStoreProvider; import org.keycloak.events.EventType; -import org.keycloak.models.AccountRoles; -import org.keycloak.models.ApplicationModel; -import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientSessionModel; -import org.keycloak.models.Constants; -import org.keycloak.models.FederatedIdentityModel; -import org.keycloak.models.IdentityProviderModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelReadOnlyException; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserCredentialModel; -import org.keycloak.models.UserCredentialValueModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.UserSessionModel; +import org.keycloak.models.*; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.TimeBasedOTP; import org.keycloak.protocol.oidc.OIDCLoginProtocol; @@ -160,7 +148,7 @@ public class AccountService { public void init() { eventStore = session.getProvider(EventStoreProvider.class); - account = session.getProvider(AccountProvider.class).setRealm(realm).setUriInfo(uriInfo); + account = session.getProvider(AccountProvider.class).setRealm(realm).setUriInfo(uriInfo).setHttpHeaders(headers); AuthenticationManager.AuthResult authResult = authManager.authenticateBearerToken(session, realm, uriInfo, clientConnection, headers); if (authResult != null) { @@ -242,7 +230,7 @@ public class AccountService { try { require(AccountRoles.MANAGE_ACCOUNT); } catch (ForbiddenException e) { - return Flows.forms(session, realm, null, uriInfo).setError("No access").createErrorPage(); + return Flows.forms(session, realm, null, uriInfo, headers).setError(Messages.NO_ACCESS).createErrorPage(); } setReferrerOnPage(); @@ -442,7 +430,7 @@ public class AccountService { event.clone().event(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, email).success(); } setReferrerOnPage(); - return account.setSuccess("accountUpdated").createResponse(AccountPages.ACCOUNT); + return account.setSuccess(Messages.ACCOUNT_UPDATED).createResponse(AccountPages.ACCOUNT); } catch (ModelReadOnlyException roe) { setReferrerOnPage(); return account.setError(Messages.READ_ONLY_USER).setProfileFormData(formData).createResponse(AccountPages.ACCOUNT); @@ -466,7 +454,7 @@ public class AccountService { event.event(EventType.REMOVE_TOTP).client(auth.getClient()).user(auth.getUser()).success(); setReferrerOnPage(); - return account.setSuccess("successTotpRemoved").createResponse(AccountPages.TOTP); + return account.setSuccess(Messages.SUCCESS_TOTP_REMOVED).createResponse(AccountPages.TOTP); } @@ -545,7 +533,7 @@ public class AccountService { event.event(EventType.UPDATE_TOTP).client(auth.getClient()).user(auth.getUser()).success(); setReferrerOnPage(); - return account.setSuccess("successTotp").createResponse(AccountPages.TOTP); + return account.setSuccess(Messages.SUCCESS_TOTP).createResponse(AccountPages.TOTP); } /** @@ -614,7 +602,11 @@ public class AccountService { } catch (ModelReadOnlyException mre) { setReferrerOnPage(); return account.setError(Messages.READ_ONLY_PASSWORD).createResponse(AccountPages.PASSWORD); - } catch (Exception ape) { + }catch (ModelException me) { + logger.error("Failed to update password", me); + setReferrerOnPage(); + return account.setError(me.getMessage(), me.getParameters()).createResponse(AccountPages.PASSWORD); + }catch (Exception ape) { logger.error("Failed to update password", ape); setReferrerOnPage(); return account.setError(ape.getMessage()).createResponse(AccountPages.PASSWORD); @@ -631,7 +623,7 @@ public class AccountService { event.event(EventType.UPDATE_PASSWORD).client(auth.getClient()).user(auth.getUser()).success(); setReferrerOnPage(); - return account.setPasswordSet(true).setSuccess("accountPasswordUpdated").createResponse(AccountPages.PASSWORD); + return account.setPasswordSet(true).setSuccess(Messages.ACCOUNT_PASSWORD_UPDATED).createResponse(AccountPages.PASSWORD); } @Path("federated-identity-update") @@ -772,7 +764,7 @@ public class AccountService { private Response login(String path) { OAuthRedirect oauth = new OAuthRedirect(); - String authUrl = OIDCLoginProtocolService.loginPageUrl(uriInfo).build(realm.getName()).toString(); + String authUrl = OIDCLoginProtocolService.authUrl(uriInfo).build(realm.getName()).toString(); oauth.setAuthUrl(authUrl); oauth.setClientId(Constants.ACCOUNT_MANAGEMENT_APP); diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java index 0bf652b9a3..a0ea1cba1a 100755 --- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java +++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java @@ -46,6 +46,7 @@ import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationManager.AuthResult; import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.managers.EventsManager; +import org.keycloak.services.messages.Messages; import org.keycloak.services.resources.flows.Flows; import org.keycloak.services.resources.flows.Urls; import org.keycloak.social.SocialIdentityProvider; @@ -57,13 +58,8 @@ import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.QueryParam; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.Response; +import javax.ws.rs.core.*; import javax.ws.rs.core.Response.Status; -import javax.ws.rs.core.UriBuilder; -import javax.ws.rs.core.UriInfo; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -97,6 +93,10 @@ public class IdentityBrokerService { @Context private HttpRequest request; + + @Context + private HttpHeaders headers; + private EventBuilder event; public IdentityBrokerService(RealmModel realmModel) { @@ -135,12 +135,12 @@ public class IdentityBrokerService { return response; } } catch (IdentityBrokerException e) { - return redirectToErrorPage("Could not send authentication request to identity provider [" + providerId + "].", e); + return redirectToErrorPage(Messages.COULD_NOT_SEND_AUTHENTICATION_REQUEST, e, providerId); } catch (Exception e) { - return redirectToErrorPage("Unexpected error when handling authentication request to identity provider [" + providerId + "].", e); + return redirectToErrorPage(Messages.UNEXPECTED_ERROR_HANDLING_REQUEST, e, providerId); } - return redirectToErrorPage("Could not proceed with authentication request to identity provider."); + return redirectToErrorPage(Messages.COULD_NOT_PROCEED_WITH_AUTHENTICATION_REQUEST); } @GET @@ -202,11 +202,10 @@ public class IdentityBrokerService { } if (OAuthClientModel.class.isInstance(clientModel) && !forceRetrieval) { - return corsResponse(Flows.forms(this.session, this.realmModel, clientModel, this.uriInfo) + return corsResponse(Flows.forms(this.session, this.realmModel, clientModel, this.uriInfo, headers) .setClientSessionCode(authManager.extractAuthorizationHeaderToken(this.request.getHttpHeaders())) .setAccessRequest("Your information from " + providerId + " identity provider.") .setClient(clientModel) - .setUriInfo(this.uriInfo) .setActionUri(this.uriInfo.getRequestUri()) .createOAuthGrant(null), clientModel); } @@ -231,9 +230,9 @@ public class IdentityBrokerService { return badRequest("Invalid token."); } catch (IdentityBrokerException e) { - return redirectToErrorPage("Could not obtain token fron identity provider [" + providerId + "].", e); + return redirectToErrorPage(Messages.COULD_NOT_OBTAIN_TOKEN, e, providerId); } catch (Exception e) { - return redirectToErrorPage("Unexpected error when retrieving token from identity provider [" + providerId + "].", e); + return redirectToErrorPage(Messages.UNEXPECTED_ERROR_RETRIEVING_TOKEN, e, providerId); } } @@ -243,7 +242,7 @@ public class IdentityBrokerService { public Response consentTokenRetrieval(@PathParam("provider_id") String providerId, MultivaluedMap formData) { if (formData.containsKey("cancel")) { - return redirectToErrorPage("Permission not approved."); + return redirectToErrorPage(Messages.PERMISSION_NOT_APPROVED); } return getToken(providerId, true); @@ -262,7 +261,7 @@ public class IdentityBrokerService { String relayState = identityProvider.getRelayState(createAuthenticationRequest(providerId, null)); if (relayState == null) { - return redirectToErrorPage("No relay state in response from identity identity [" + providerId + "."); + return redirectToErrorPage(Messages.NO_RELAY_STATE_IN_RESPONSE, providerId); } if (isDebugEnabled()) { @@ -298,10 +297,10 @@ public class IdentityBrokerService { return performLocalAuthentication(identity, clientSessionCode); } catch (IdentityBrokerException e) { rollback(); - return redirectToErrorPage("Authentication failed. Could not authenticate with identity provider [" + providerId + "].", e); + return redirectToErrorPage(Messages.IDENTITY_PROVIDER_AUTHENTICATION_FAILED, e, providerId); } catch (Exception e) { rollback(); - return redirectToErrorPage("Unexpected error when handling response from identity provider [" + providerId + "].", e); + return redirectToErrorPage(Messages.UNEXPECTED_ERROR_HANDLING_RESPONSE, e, providerId); } finally { if (this.session.getTransaction().isActive()) { this.session.getTransaction().commit(); @@ -364,7 +363,7 @@ public class IdentityBrokerService { this.event.event(EventType.IDENTITY_PROVIDER_ACCCOUNT_LINKING); if (federatedUser != null) { - return redirectToErrorPage("The identity returned by the identity provider [" + providerId + "] is already linked to other user."); + return redirectToErrorPage(Messages.IDENTITY_PROVIDER_ALREADY_LINKED, providerId); } UserModel authenticatedUser = clientSession.getUserSession().getUser(); @@ -375,12 +374,12 @@ public class IdentityBrokerService { if (!authenticatedUser.isEnabled()) { fireErrorEvent(Errors.USER_DISABLED); - return redirectToErrorPage("User is disabled."); + return redirectToErrorPage(Messages.ACCOUNT_DISABLED); } if (!authenticatedUser.hasRole(this.realmModel.getApplicationByName(ACCOUNT_MANAGEMENT_APP).getRole(MANAGE_ACCOUNT))) { fireErrorEvent(Errors.NOT_ALLOWED); - return redirectToErrorPage("Insufficient permissions to link identities."); + return redirectToErrorPage(Messages.INSUFFICIENT_PERMISSION); } this.session.users().addFederatedIdentity(this.realmModel, authenticatedUser, federatedIdentityModel); @@ -449,28 +448,28 @@ public class IdentityBrokerService { return Urls.identityProviderAuthnResponse(this.uriInfo.getBaseUri(), providerId, this.realmModel.getName()).toString(); } - private Response redirectToErrorPage(String message) { - return redirectToErrorPage(message, null); + private Response redirectToErrorPage(String message, Object ... parameters) { + return redirectToErrorPage(message, null, parameters); } - private Response redirectToErrorPage(String message, Throwable throwable) { + private Response redirectToErrorPage(String message, Throwable throwable, Object ... parameters) { if (message == null) { - message = "Unexpected error when authenticating with identity provider"; + message = Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR; } fireErrorEvent(message, throwable); - return Flows.forwardToSecurityFailurePage(this.session, this.realmModel, this.uriInfo, message); + return Flows.forwardToSecurityFailurePage(this.session, this.realmModel, this.uriInfo, headers, message, parameters); } private Response redirectToLoginPage(Throwable t, ClientSessionCode clientCode) { String message = t.getMessage(); if (message == null) { - message = "Unexpected error when authenticating with identity provider"; + message = Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR; } fireErrorEvent(message); - return Flows.forms(this.session, this.realmModel, clientCode.getClientSession().getClient(), this.uriInfo) + return Flows.forms(this.session, this.realmModel, clientCode.getClientSession().getClient(), this.uriInfo, headers) .setClientSessionCode(clientCode.getCode()) .setError(message) .createLogin(); @@ -546,7 +545,7 @@ public class IdentityBrokerService { if (existingUser != null) { fireErrorEvent(Errors.FEDERATED_IDENTITY_EMAIL_EXISTS); - throw new IdentityBrokerException("federatedIdentityEmailExists"); + throw new IdentityBrokerException(Messages.FEDERATED_IDENTITY_EMAIL_EXISTS); } String username = updatedIdentity.getUsername(); @@ -554,7 +553,7 @@ public class IdentityBrokerService { username = updatedIdentity.getEmail(); if (username == null || username.trim().length() == 0) { fireErrorEvent(Errors.FEDERATED_IDENTITY_REGISTRATION_EMAIL_MISSING); - throw new IdentityBrokerException("federatedIdentityRegistrationEmailMissing"); + throw new IdentityBrokerException(Messages.FEDERATED_IDENTITY_REGISTRATION_EMAIL_MISSING); // TODO KEYCLOAK-1053 (ask user to enter email address) should be implemented instead of plain exception as better solution for this case } username = username.trim(); @@ -566,7 +565,7 @@ public class IdentityBrokerService { if (existingUser != null) { fireErrorEvent(Errors.FEDERATED_IDENTITY_USERNAME_EXISTS); - throw new IdentityBrokerException("federatedIdentityUsernameExists"); + throw new IdentityBrokerException(Messages.FEDERATED_IDENTITY_USERNAME_EXISTS); } if (isDebugEnabled()) { diff --git a/services/src/main/java/org/keycloak/services/resources/JsResource.java b/services/src/main/java/org/keycloak/services/resources/JsResource.java index 6c59c78c04..1b0128805a 100755 --- a/services/src/main/java/org/keycloak/services/resources/JsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/JsResource.java @@ -1,9 +1,11 @@ package org.keycloak.services.resources; import org.keycloak.Config; +import org.keycloak.Version; import javax.ws.rs.GET; import javax.ws.rs.Path; +import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.CacheControl; import javax.ws.rs.core.Response; @@ -38,6 +40,17 @@ public class JsResource { } } + @GET + @Path("/{version}/keycloak.js") + @Produces("text/javascript") + public Response getJsWithVersion(@PathParam("version") String version) { + if (!version.equals(Version.RESOURCES_VERSION)) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + return getJs(); + } + @GET @Path("/keycloak.min.js") @Produces("text/javascript") @@ -54,4 +67,15 @@ public class JsResource { } } + @GET + @Path("/{version}/keycloak.min.js") + @Produces("text/javascript") + public Response getMinJsWithVersion(@PathParam("version") String version) { + if (!version.equals(Version.RESOURCES_VERSION)) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + return getMinJs(); + } + } diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java index 812696f0d6..b4554ba8c9 100755 --- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java +++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java @@ -32,15 +32,8 @@ import org.keycloak.events.EventBuilder; import org.keycloak.events.EventType; import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.login.LoginFormsProvider; -import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientSessionModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RequiredCredentialModel; -import org.keycloak.models.UserCredentialModel; -import org.keycloak.models.UserModel; +import org.keycloak.models.*; import org.keycloak.models.UserModel.RequiredAction; -import org.keycloak.models.UserSessionModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.TimeBasedOTP; import org.keycloak.protocol.LoginProtocol; @@ -160,7 +153,7 @@ public class LoginActionsService { return false; } else if (!clientCode.isValid(requiredAction)) { event.error(Errors.INVALID_CODE); - response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid code, please login again through your application."); + response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_CODE); return false; } else { return true; @@ -172,7 +165,7 @@ public class LoginActionsService { return false; } else if (!(clientCode.isValid(requiredAction) || clientCode.isValid(alternativeRequiredAction))) { event.error(Errors.INVALID_CODE); - response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid code, please login again through your application."); + response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo,headers, Messages.INVALID_CODE); return false; } else { return true; @@ -182,18 +175,18 @@ public class LoginActionsService { public boolean check(String code) { if (!checkSsl()) { event.error(Errors.SSL_REQUIRED); - response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required"); + response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED); return false; } if (!realm.isEnabled()) { event.error(Errors.REALM_DISABLED); - response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled."); + response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED); return false; } clientCode = ClientSessionCode.parse(code, session, realm); if (clientCode == null) { event.error(Errors.INVALID_CODE); - response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application."); + response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE); return false; } return true; @@ -224,7 +217,7 @@ public class LoginActionsService { clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE); } - LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo) + LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers) .setClientSessionCode(clientSessionCode.getCode()); return forms.createLogin(); @@ -242,7 +235,7 @@ public class LoginActionsService { event.event(EventType.REGISTER); if (!realm.isRegistrationAllowed()) { event.error(Errors.REGISTRATION_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Registration not allowed"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REGISTRATION_NOT_ALLOWED); } Checks checks = new Checks(); @@ -256,7 +249,7 @@ public class LoginActionsService { authManager.expireIdentityCookie(realm, uriInfo, clientConnection); - return Flows.forms(session, realm, clientSession.getClient(), uriInfo) + return Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers) .setClientSessionCode(clientSessionCode.getCode()) .createRegistration(); } @@ -276,17 +269,17 @@ public class LoginActionsService { event.event(EventType.LOGIN); if (!checkSsl()) { 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.error(Errors.REALM_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED); } ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm); if (clientCode == null) { event.error(Errors.INVALID_CODE); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE); } ClientSessionModel clientSession = clientCode.getClientSession(); @@ -295,7 +288,7 @@ public class LoginActionsService { if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE) || clientSession.getUserSession() != null) { clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE); event.client(clientSession.getClient()).error(Errors.EXPIRED_CODE); - return Flows.forms(this.session, realm, clientSession.getClient(), uriInfo).setError(Messages.EXPIRED_CODE) + return Flows.forms(this.session, realm, clientSession.getClient(), uriInfo, headers).setError(Messages.EXPIRED_CODE) .setClientSessionCode(clientCode.getCode()) .createLogin(); } @@ -319,17 +312,18 @@ public class LoginActionsService { ClientModel client = clientSession.getClient(); if (client == null) { 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.error(Errors.CLIENT_NOT_FOUND); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.LOGIN_REQUESTER_NOT_ENABLED); } if (formData.containsKey("cancel")) { event.error(Errors.REJECTED_BY_USER); LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod()); protocol.setRealm(realm) + .setHttpHeaders(headers) .setUriInfo(uriInfo); return protocol.cancelLogin(clientSession); } @@ -356,14 +350,14 @@ public class LoginActionsService { return authManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event); case ACCOUNT_TEMPORARILY_DISABLED: event.error(Errors.USER_TEMPORARILY_DISABLED); - return Flows.forms(this.session, realm, client, uriInfo) + return Flows.forms(this.session, realm, client, uriInfo, headers) .setError(Messages.ACCOUNT_TEMPORARILY_DISABLED) .setFormData(formData) .setClientSessionCode(clientCode.getCode()) .createLogin(); case ACCOUNT_DISABLED: event.error(Errors.USER_DISABLED); - return Flows.forms(this.session, realm, client, uriInfo) + return Flows.forms(this.session, realm, client, uriInfo, headers) .setError(Messages.ACCOUNT_DISABLED) .setClientSessionCode(clientCode.getCode()) .setFormData(formData).createLogin(); @@ -373,19 +367,19 @@ public class LoginActionsService { String passwordToken = new JWSBuilder().jsonContent(new PasswordToken(realm.getName(), user.getId())).rsa256(realm.getPrivateKey()); formData.add(CredentialRepresentation.PASSWORD_TOKEN, passwordToken); - return Flows.forms(this.session, realm, client, uriInfo) + return Flows.forms(this.session, realm, client, uriInfo, headers) .setFormData(formData) .setClientSessionCode(clientCode.getCode()) .createLoginTotp(); case INVALID_USER: event.error(Errors.USER_NOT_FOUND); - return Flows.forms(this.session, realm, client, uriInfo).setError(Messages.INVALID_USER) + return Flows.forms(this.session, realm, client, uriInfo, headers).setError(Messages.INVALID_USER) .setFormData(formData) .setClientSessionCode(clientCode.getCode()) .createLogin(); default: event.error(Errors.INVALID_USER_CREDENTIALS); - return Flows.forms(this.session, realm, client, uriInfo).setError(Messages.INVALID_USER) + return Flows.forms(this.session, realm, client, uriInfo, headers).setError(Messages.INVALID_USER) .setFormData(formData) .setClientSessionCode(clientCode.getCode()) .createLogin(); @@ -407,25 +401,25 @@ public class LoginActionsService { event.event(EventType.REGISTER); if (!checkSsl()) { 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.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 (!realm.isRegistrationAllowed()) { event.error(Errors.REGISTRATION_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Registration not allowed"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REGISTRATION_NOT_ALLOWED); } ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm); if (clientCode == null) { event.error(Errors.INVALID_CODE); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE); } if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE)) { event.error(Errors.INVALID_CODE); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid code, please login again through your application."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_CODE); } String username = formData.getFirst("username"); @@ -444,17 +438,17 @@ public class LoginActionsService { if (!realm.isEnabled()) { 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); } ClientModel client = clientSession.getClient(); if (client == null) { 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.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); } @@ -464,15 +458,20 @@ public class LoginActionsService { } // Validate here, so user is not created if password doesn't validate to passwordPolicy of current realm - String error = Validation.validateRegistrationForm(realm, formData, requiredCredentialTypes); - if (error == null) { - error = Validation.validatePassword(formData, realm.getPasswordPolicy()); + String errorMessage = Validation.validateRegistrationForm(realm, formData, requiredCredentialTypes); + Object[] parameters = new Object[0]; + if (errorMessage == null) { + PasswordPolicy.Error error = Validation.validatePassword(formData, realm.getPasswordPolicy()); + if(error != null){ + errorMessage = error.getMessage(); + parameters = error.getParameters(); + } } - if (error != null) { + if (errorMessage != null) { event.error(Errors.INVALID_REGISTRATION); - return Flows.forms(session, realm, client, uriInfo) - .setError(error) + return Flows.forms(session, realm, client, uriInfo, headers) + .setError(errorMessage, parameters) .setFormData(formData) .setClientSessionCode(clientCode.getCode()) .createRegistration(); @@ -481,7 +480,7 @@ public class LoginActionsService { // Validate that user with this username doesn't exist in realm or any federation provider if (session.users().getUserByUsername(username, realm) != null) { event.error(Errors.USERNAME_IN_USE); - return Flows.forms(session, realm, client, uriInfo) + return Flows.forms(session, realm, client, uriInfo, headers) .setError(Messages.USERNAME_EXISTS) .setFormData(formData) .setClientSessionCode(clientCode.getCode()) @@ -491,7 +490,7 @@ public class LoginActionsService { // Validate that user with this email doesn't exist in realm or any federation provider if (session.users().getUserByEmail(email, realm) != null) { event.error(Errors.EMAIL_IN_USE); - return Flows.forms(session, realm, client, uriInfo) + return Flows.forms(session, realm, client, uriInfo, headers) .setError(Messages.EMAIL_EXISTS) .setFormData(formData) .setClientSessionCode(clientCode.getCode()) @@ -512,9 +511,14 @@ public class LoginActionsService { boolean passwordUpdateSuccessful; String passwordUpdateError = null; + Object[] passwordUpdateErrorParameters = null; try { session.users().updateCredential(realm, user, UserCredentialModel.password(formData.getFirst("password"))); passwordUpdateSuccessful = true; + } catch (ModelException me) { + passwordUpdateSuccessful = false; + passwordUpdateError = me.getMessage(); + passwordUpdateErrorParameters = me.getParameters(); } catch (Exception ape) { passwordUpdateSuccessful = false; passwordUpdateError = ape.getMessage(); @@ -523,8 +527,8 @@ public class LoginActionsService { // User already registered, but force him to update password if (!passwordUpdateSuccessful) { user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD); - return Flows.forms(session, realm, client, uriInfo) - .setError(passwordUpdateError) + return Flows.forms(session, realm, client, uriInfo, headers) + .setError(passwordUpdateError, passwordUpdateErrorParameters) .setClientSessionCode(clientCode.getCode()) .createResponse(UserModel.RequiredAction.UPDATE_PASSWORD); } @@ -552,7 +556,7 @@ public class LoginActionsService { if (!checkSsl()) { - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED); } String code = formData.getFirst("code"); @@ -560,7 +564,7 @@ public class LoginActionsService { ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm); if (accessCode == null || !accessCode.isValid(ClientSessionModel.Action.OAUTH_GRANT)) { event.error(Errors.INVALID_CODE); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid access code."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_ACCESS_CODE); } ClientSessionModel clientSession = accessCode.getClientSession(); event.detail(Details.CODE_ID, clientSession.getId()); @@ -582,14 +586,15 @@ public class LoginActionsService { } if (!AuthenticationManager.isSessionValid(realm, userSession)) { - AuthenticationManager.logout(session, realm, userSession, uriInfo, clientConnection); + AuthenticationManager.logout(session, realm, userSession, uriInfo, clientConnection, headers); event.error(Errors.INVALID_CODE); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Session not active"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.SESSION_NOT_ACTIVE); } event.session(userSession); LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod()); protocol.setRealm(realm) + .setHttpHeaders(headers) .setUriInfo(uriInfo); if (formData.containsKey("cancel")) { event.error(Errors.REJECTED_BY_USER); @@ -620,7 +625,7 @@ public class LoginActionsService { String error = Validation.validateUpdateProfileForm(formData); if (error != null) { - return Flows.forms(session, realm, null, uriInfo).setUser(user).setError(error) + return Flows.forms(session, realm, null, uriInfo, headers).setUser(user).setError(error) .setClientSessionCode(accessCode.getCode()) .createResponse(RequiredAction.UPDATE_PROFILE); } @@ -638,7 +643,7 @@ public class LoginActionsService { // check for duplicated email if (userByEmail != null && !userByEmail.getId().equals(user.getId())) { - return Flows.forms(session, realm, null, uriInfo).setUser(user).setError(Messages.EMAIL_EXISTS) + return Flows.forms(session, realm, null, uriInfo, headers).setUser(user).setError(Messages.EMAIL_EXISTS) .setClientSessionCode(accessCode.getCode()) .createResponse(RequiredAction.UPDATE_PROFILE); } @@ -677,7 +682,7 @@ public class LoginActionsService { String totp = formData.getFirst("totp"); String totpSecret = formData.getFirst("totpSecret"); - LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo).setUser(user); + LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo, headers).setUser(user); if (Validation.isEmpty(totp)) { return loginForms.setError(Messages.MISSING_TOTP) .setClientSessionCode(accessCode.getCode()) @@ -722,7 +727,7 @@ public class LoginActionsService { String passwordNew = formData.getFirst("password-new"); String passwordConfirm = formData.getFirst("password-confirm"); - LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo).setUser(user); + LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo, headers).setUser(user); if (Validation.isEmpty(passwordNew)) { return loginForms.setError(Messages.MISSING_PASSWORD) .setClientSessionCode(accessCode.getCode()) @@ -735,6 +740,10 @@ public class LoginActionsService { try { session.users().updateCredential(realm, user, UserCredentialModel.password(passwordNew)); + } catch (ModelException me) { + return loginForms.setError(me.getMessage(), me.getParameters()) + .setClientSessionCode(accessCode.getCode()) + .createResponse(RequiredAction.UPDATE_PASSWORD); } catch (Exception ape) { return loginForms.setError(ape.getMessage()) .setClientSessionCode(accessCode.getCode()) @@ -748,7 +757,7 @@ public class LoginActionsService { if (clientSession.getAction().equals(ClientSessionModel.Action.RECOVER_PASSWORD)) { String actionCookieValue = getActionCookie(); if (actionCookieValue == null || !actionCookieValue.equals(userSession.getId())) { - return Flows.forms(session, realm, clientSession.getClient(), uriInfo).setSuccess("passwordUpdated").createInfoPage(); + return Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers).setSuccess(Messages.ACCOUNT_PASSWORD_UPDATED).createInfoPage(); } } @@ -780,7 +789,7 @@ public class LoginActionsService { String actionCookieValue = getActionCookie(); if (actionCookieValue == null || !actionCookieValue.equals(userSession.getId())) { - return Flows.forms(session, realm, clientSession.getClient(), uriInfo).setSuccess("emailVerified").createInfoPage(); + return Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers).setSuccess(Messages.EMAIL_VERIFIED).createInfoPage(); } event = event.clone().removeDetail(Details.EMAIL).event(EventType.LOGIN); @@ -798,7 +807,7 @@ public class LoginActionsService { createActionCookie(realm, uriInfo, clientConnection, userSession.getId()); - return Flows.forms(session, realm, null, uriInfo) + return Flows.forms(session, realm, null, uriInfo, headers) .setClientSessionCode(accessCode.getCode()) .setUser(userSession.getUser()) .createResponse(RequiredAction.VERIFY_EMAIL); @@ -815,11 +824,11 @@ public class LoginActionsService { return checks.response; } ClientSessionCode accessCode = checks.clientCode; - return Flows.forms(session, realm, null, uriInfo) + return Flows.forms(session, realm, null, uriInfo, headers) .setClientSessionCode(accessCode.getCode()) .createResponse(RequiredAction.UPDATE_PASSWORD); } else { - return Flows.forms(session, realm, null, uriInfo) + return Flows.forms(session, realm, null, uriInfo, headers) .setClientSessionCode(code) .createPasswordReset(); } @@ -832,16 +841,16 @@ public class LoginActionsService { final MultivaluedMap formData) { event.event(EventType.SEND_RESET_PASSWORD); if (!checkSsl()) { - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED); } if (!realm.isEnabled()) { 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); } ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm); if (accessCode == null) { event.error(Errors.INVALID_CODE); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE); } ClientSessionModel clientSession = accessCode.getClientSession(); @@ -849,12 +858,10 @@ public class LoginActionsService { ClientModel client = clientSession.getClient(); if (client == null) { - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, - "Unknown login requester."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_LOGIN_REQUESTER); } if (!client.isEnabled()) { - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, - "Login requester not enabled."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.LOGIN_REQUESTER_NOT_ENABLED); } event.client(client.getClientId()) @@ -897,7 +904,7 @@ public class LoginActionsService { } catch (EmailException e) { event.error(Errors.EMAIL_SEND_FAILED); logger.error("Failed to send password reset email", e); - return Flows.forms(this.session, realm, client, uriInfo).setError("emailSendError") + return Flows.forms(this.session, realm, client, uriInfo, headers).setError(Messages.EMAIL_SENT_ERROR) .setClientSessionCode(accessCode.getCode()) .createErrorPage(); } @@ -905,7 +912,7 @@ public class LoginActionsService { createActionCookie(realm, uriInfo, clientConnection, userSession.getId()); } - return Flows.forms(session, realm, client, uriInfo).setSuccess("emailSent").setClientSessionCode(accessCode.getCode()).createPasswordReset(); + return Flows.forms(session, realm, client, uriInfo, headers).setSuccess(Messages.EMAIL_SENT).setClientSessionCode(accessCode.getCode()).createPasswordReset(); } private String getActionCookie() { diff --git a/services/src/main/java/org/keycloak/services/resources/ThemeResource.java b/services/src/main/java/org/keycloak/services/resources/ThemeResource.java index 488774d275..5fe27a4117 100755 --- a/services/src/main/java/org/keycloak/services/resources/ThemeResource.java +++ b/services/src/main/java/org/keycloak/services/resources/ThemeResource.java @@ -2,6 +2,7 @@ package org.keycloak.services.resources; import org.jboss.logging.Logger; import org.keycloak.Config; +import org.keycloak.Version; import org.keycloak.freemarker.Theme; import org.keycloak.freemarker.ThemeProvider; import org.keycloak.models.KeycloakSession; @@ -38,7 +39,11 @@ public class ThemeResource { */ @GET @Path("/{version}/{themeType}/{themeName}/{path:.*}") - public Response getResource(@PathParam("themeType") String themType, @PathParam("themeName") String themeName, @PathParam("path") String path) { + public Response getResource(@PathParam("version") String version, @PathParam("themeType") String themType, @PathParam("themeName") String themeName, @PathParam("path") String path) { + if (!version.equals(Version.RESOURCES_VERSION)) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + try { ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending"); Theme theme = themeProvider.getTheme(themeName, Theme.Type.valueOf(themType.toUpperCase())); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java index ff6e0fcbfa..362f72a7f4 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java @@ -8,6 +8,7 @@ import org.jboss.resteasy.spi.HttpResponse; import org.jboss.resteasy.spi.NotFoundException; import org.keycloak.ClientConnection; import org.keycloak.Config; +import org.keycloak.Version; import org.keycloak.freemarker.BrowserSecurityHeaderSetup; import org.keycloak.freemarker.FreeMarkerException; import org.keycloak.freemarker.FreeMarkerUtil; @@ -290,6 +291,7 @@ public class AdminConsole { map.put("authUrl", authUrl); map.put("resourceUrl", Urls.themeRoot(baseUri) + "/admin/" + adminTheme); + map.put("resourceVersion", Version.RESOURCES_VERSION); ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending"); Theme theme = themeProvider.getTheme(realm.getAdminTheme(), Theme.Type.ADMIN); diff --git a/services/src/main/java/org/keycloak/services/resources/flows/Flows.java b/services/src/main/java/org/keycloak/services/resources/flows/Flows.java index 8c9e4d4b65..3d0697db8a 100755 --- a/services/src/main/java/org/keycloak/services/resources/flows/Flows.java +++ b/services/src/main/java/org/keycloak/services/resources/flows/Flows.java @@ -26,6 +26,7 @@ import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; @@ -37,16 +38,16 @@ public class Flows { private Flows() { } - public static LoginFormsProvider forms(KeycloakSession session, RealmModel realm, ClientModel client, UriInfo uriInfo) { - return session.getProvider(LoginFormsProvider.class).setRealm(realm).setUriInfo(uriInfo).setClient(client); + public static LoginFormsProvider forms(KeycloakSession session, RealmModel realm, ClientModel client, UriInfo uriInfo, HttpHeaders headers) { + return session.getProvider(LoginFormsProvider.class).setRealm(realm).setUriInfo(uriInfo).setClient(client).setHttpHeaders(headers); } public static ErrorFlows errors() { return new ErrorFlows(); } - public static Response forwardToSecurityFailurePage(KeycloakSession session, RealmModel realm, UriInfo uriInfo, String message) { - return Flows.forms(session, realm, null, uriInfo).setError(message).createErrorPage(); + public static Response forwardToSecurityFailurePage(KeycloakSession session, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, String message, Object ... parameters) { + return Flows.forms(session, realm, null, uriInfo, headers).setError(message,parameters).createErrorPage(); } diff --git a/services/src/main/java/org/keycloak/services/resources/flows/OAuthRedirect.java b/services/src/main/java/org/keycloak/services/resources/flows/OAuthRedirect.java index 7d3eef017d..2ccb4a004a 100755 --- a/services/src/main/java/org/keycloak/services/resources/flows/OAuthRedirect.java +++ b/services/src/main/java/org/keycloak/services/resources/flows/OAuthRedirect.java @@ -34,7 +34,8 @@ public class OAuthRedirect extends AbstractOAuthClient { UriBuilder uriBuilder = UriBuilder.fromUri(authUrl) .queryParam(OAuth2Constants.CLIENT_ID, clientId) .queryParam(OAuth2Constants.REDIRECT_URI, redirectUri) - .queryParam(OAuth2Constants.STATE, state); + .queryParam(OAuth2Constants.STATE, state) + .queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE); if (scope != null) { uriBuilder.queryParam(OAuth2Constants.SCOPE, scope); } diff --git a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java index 7889388fc9..ab56f4d7db 100755 --- a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java +++ b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java @@ -190,6 +190,10 @@ public class Urls { return requiredActionsBase(baseUri).path(LoginActionsService.class, "processConsent").build(realmId); } + public static String localeCookiePath(URI baseUri, String realmName){ + return realmBase(baseUri).path(realmName).build().getRawPath(); + } + public static URI themeRoot(URI baseUri) { return themeBase(baseUri).path(Version.RESOURCES_VERSION).build(); } diff --git a/services/src/main/java/org/keycloak/services/validation/Validation.java b/services/src/main/java/org/keycloak/services/validation/Validation.java index 254cee1e8c..f72a63023a 100755 --- a/services/src/main/java/org/keycloak/services/validation/Validation.java +++ b/services/src/main/java/org/keycloak/services/validation/Validation.java @@ -48,8 +48,8 @@ public class Validation { return null; } - public static String validatePassword(MultivaluedMap formData, PasswordPolicy policy) { - return policy.validate(formData.getFirst("password")); + public static PasswordPolicy.Error validatePassword(MultivaluedMap formData, PasswordPolicy policy) { + return policy.validate(formData.getFirst("username"), formData.getFirst("password")); } public static String validateUpdateProfileForm(MultivaluedMap formData) { diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java b/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java index d5e1c375a6..73c78e0589 100755 --- a/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java +++ b/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java @@ -46,12 +46,27 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; /** * @author Stian Thorgersen */ public class KeycloakServer { + static { + try { + File f = new File(System.getProperty("user.home"), ".keycloak-test.properties"); + if (f.isFile()) { + Properties p = new Properties(); + p.load(new FileInputStream(f)); + System.getProperties().putAll(p); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + private static final Logger log = Logger.getLogger(KeycloakServer.class); private boolean sysout = false; diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/EmbeddedServersFactory.java b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/EmbeddedServersFactory.java index d739b6c3a8..4c3fab61d2 100644 --- a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/EmbeddedServersFactory.java +++ b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/EmbeddedServersFactory.java @@ -1,5 +1,8 @@ package org.keycloak.testutils.ldap; +import java.net.InetAddress; +import java.net.UnknownHostException; + /** * Factory for ApacheDS based LDAP and Kerberos servers * @@ -21,6 +24,7 @@ public class EmbeddedServersFactory { private String baseDN; private String bindHost; private int bindPort; + private String ldapSaslPrincipal; private String ldifFile; private String kerberosRealm; private int kdcPort; @@ -39,6 +43,7 @@ public class EmbeddedServersFactory { this.bindHost = System.getProperty("ldap.host"); String bindPort = System.getProperty("ldap.port"); this.ldifFile = System.getProperty("ldap.ldif"); + this.ldapSaslPrincipal = System.getProperty("ldap.saslPrincipal"); this.kerberosRealm = System.getProperty("kerberos.realm"); String kdcPort = System.getProperty("kerberos.port"); @@ -62,6 +67,16 @@ public class EmbeddedServersFactory { if (kdcEncryptionTypes == null || kdcEncryptionTypes.isEmpty()) { kdcEncryptionTypes = DEFAULT_KDC_ENCRYPTION_TYPES; } + + if (ldapSaslPrincipal == null || ldapSaslPrincipal.isEmpty()) { + try { + // Same algorithm like sun.security.krb5.PrincipalName constructor + String canonicalHost = (InetAddress.getByName(bindHost)).getCanonicalHostName(); + this.ldapSaslPrincipal = "ldap/" + canonicalHost + "@" + kerberosRealm; + } catch (UnknownHostException uhe) { + throw new RuntimeException(uhe); + } + } } @@ -72,7 +87,7 @@ public class EmbeddedServersFactory { ldifFile = DEFAULT_LDIF_FILE; } - return new LDAPEmbeddedServer(baseDN, bindHost, bindPort, ldifFile); + return new LDAPEmbeddedServer(baseDN, bindHost, bindPort, ldifFile, ldapSaslPrincipal); } @@ -83,6 +98,6 @@ public class EmbeddedServersFactory { ldifFile = DEFAULT_KERBEROS_LDIF_FILE; } - return new KerberosEmbeddedServer(baseDN, bindHost, bindPort, ldifFile, kerberosRealm, kdcPort, kdcEncryptionTypes); + return new KerberosEmbeddedServer(baseDN, bindHost, bindPort, ldifFile, ldapSaslPrincipal, kerberosRealm, kdcPort, kdcEncryptionTypes); } } diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosEmbeddedServer.java b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosEmbeddedServer.java index f5683425c0..63cc027f7f 100644 --- a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosEmbeddedServer.java +++ b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosEmbeddedServer.java @@ -2,6 +2,7 @@ package org.keycloak.testutils.ldap; import java.io.IOException; import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.HashSet; import java.util.Set; @@ -48,8 +49,8 @@ public class KerberosEmbeddedServer extends LDAPEmbeddedServer { } - protected KerberosEmbeddedServer(String baseDN, String bindHost, int bindPort, String ldifFile, String kerberosRealm, int kdcPort, String kdcEncryptionTypes) { - super(baseDN, bindHost, bindPort, ldifFile); + protected KerberosEmbeddedServer(String baseDN, String bindHost, int bindPort, String ldifFile, String ldapSaslPrincipal, String kerberosRealm, int kdcPort, String kdcEncryptionTypes) { + super(baseDN, bindHost, bindPort, ldifFile, ldapSaslPrincipal); this.kdcEncryptionTypes = kdcEncryptionTypes; this.kerberosRealm = kerberosRealm; this.kdcPort = kdcPort; @@ -79,7 +80,8 @@ public class KerberosEmbeddedServer extends LDAPEmbeddedServer { LdapServer ldapServer = super.createLdapServer(); ldapServer.setSaslHost( this.bindHost ); - ldapServer.setSaslPrincipal( "ldap/" + this.bindHost + "@" + this.kerberosRealm); + ldapServer.setSaslPrincipal( this.ldapSaslPrincipal); + ldapServer.setSaslRealms(new ArrayList()); ldapServer.addSaslMechanismHandler(SupportedSaslMechanisms.PLAIN, new PlainMechanismHandler()); ldapServer.addSaslMechanismHandler(SupportedSaslMechanisms.CRAM_MD5, new CramMd5MechanismHandler()); diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPEmbeddedServer.java b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPEmbeddedServer.java index 4fde15bd3e..e6e7be1b1d 100644 --- a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPEmbeddedServer.java +++ b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPEmbeddedServer.java @@ -2,7 +2,6 @@ package org.keycloak.testutils.ldap; import java.io.File; import java.io.InputStream; -import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -35,6 +34,7 @@ public class LDAPEmbeddedServer { protected final String bindHost; protected final int bindPort; protected final String ldifFile; + protected final String ldapSaslPrincipal; protected DirectoryService directoryService; protected LdapServer ldapServer; @@ -47,16 +47,19 @@ public class LDAPEmbeddedServer { ldapEmbeddedServer.start(); } - public LDAPEmbeddedServer(String baseDN, String bindHost, int bindPort, String ldifFile) { + public LDAPEmbeddedServer(String baseDN, String bindHost, int bindPort, String ldifFile, String ldapSaslPrincipal) { this.baseDN = baseDN; this.bindHost = bindHost; this.bindPort = bindPort; this.ldifFile = ldifFile; + this.ldapSaslPrincipal = ldapSaslPrincipal; } public void init() throws Exception { - log.info("Creating LDAP Directory Service. Config: baseDN=" + baseDN + ", bindHost=" + bindHost + ", bindPort=" + bindPort); + log.info("Creating LDAP Directory Service. Config: baseDN=" + baseDN + ", bindHost=" + bindHost + ", bindPort=" + bindPort + + ", ldapSaslPrincipal=" + ldapSaslPrincipal); + this.directoryService = createDirectoryService(); log.info("Importing LDIF: " + ldifFile); @@ -133,9 +136,6 @@ public class LDAPEmbeddedServer { // Propagate the anonymous flag to the DS directoryService.setAllowAnonymousAccess(false); - ldapServer.setSaslHost( this.bindHost ); - ldapServer.setSaslPrincipal( "ldap/" + this.bindHost + "@KEYCLOAK.ORG"); - ldapServer.setSaslRealms(new ArrayList()); return ldapServer; } @@ -143,6 +143,7 @@ public class LDAPEmbeddedServer { private void importLdif() throws Exception { Map map = new HashMap(); map.put("hostname", this.bindHost); + map.put("ldapSaslPrincipal", this.ldapSaslPrincipal); // For now, assume that LDIF file is on classpath InputStream is = getClass().getClassLoader().getResourceAsStream(ldifFile); diff --git a/testsuite/integration/src/main/resources/kerberos/users-kerberos.ldif b/testsuite/integration/src/main/resources/kerberos/users-kerberos.ldif index acdd570a68..fd9936cfa9 100644 --- a/testsuite/integration/src/main/resources/kerberos/users-kerberos.ldif +++ b/testsuite/integration/src/main/resources/kerberos/users-kerberos.ldif @@ -32,7 +32,7 @@ cn: LDAP sn: Service uid: ldap userPassword: randall -krb5PrincipalName: ldap/${hostname}@KEYCLOAK.ORG +krb5PrincipalName: ${ldapSaslPrincipal} krb5KeyVersionNumber: 0 dn: uid=HTTP,ou=People,dc=keycloak,dc=org diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java index a32a70dc1d..9319661732 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java @@ -67,10 +67,6 @@ public class OAuthClient { private String realm = "test"; - private String responseType = OAuth2Constants.CODE; - - private String grantType = "authorization_code"; - private String clientId = "test-app"; private String redirectUri = "http://localhost:8081/app/auth"; @@ -113,9 +109,8 @@ public class OAuthClient { HttpPost post = new HttpPost(getAccessTokenUrl()); List parameters = new LinkedList(); - if (grantType != null) { - parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, grantType)); - } + parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.AUTHORIZATION_CODE)); + if (code != null) { parameters.add(new BasicNameValuePair(OAuth2Constants.CODE, code)); } @@ -153,6 +148,7 @@ public class OAuthClient { post.setHeader("Authorization", authorization); List parameters = new LinkedList(); + parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD)); parameters.add(new BasicNameValuePair("username", username)); parameters.add(new BasicNameValuePair("password", password)); @@ -199,9 +195,8 @@ public class OAuthClient { HttpPost post = new HttpPost(getRefreshTokenUrl()); List parameters = new LinkedList(); - if (grantType != null) { - parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, grantType)); - } + parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.REFRESH_TOKEN)); + if (refreshToken != null) { parameters.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken)); } @@ -290,10 +285,8 @@ public class OAuthClient { } public String getLoginFormUrl() { - UriBuilder b = OIDCLoginProtocolService.loginPageUrl(UriBuilder.fromUri(baseUrl)); - if (responseType != null) { - b.queryParam(OAuth2Constants.RESPONSE_TYPE, responseType); - } + UriBuilder b = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri(baseUrl)); + b.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE); if (clientId != null) { b.queryParam(OAuth2Constants.CLIENT_ID, clientId); } @@ -307,7 +300,7 @@ public class OAuthClient { } public String getAccessTokenUrl() { - UriBuilder b = OIDCLoginProtocolService.accessCodeToTokenUrl(UriBuilder.fromUri(baseUrl)); + UriBuilder b = OIDCLoginProtocolService.tokenUrl(UriBuilder.fromUri(baseUrl)); return b.build(realm).toString(); } @@ -323,12 +316,12 @@ public class OAuthClient { } public String getResourceOwnerPasswordCredentialGrantUrl() { - UriBuilder b = OIDCLoginProtocolService.grantAccessTokenUrl(UriBuilder.fromUri(baseUrl)); + UriBuilder b = OIDCLoginProtocolService.tokenUrl(UriBuilder.fromUri(baseUrl)); return b.build(realm).toString(); } public String getRefreshTokenUrl() { - UriBuilder b = OIDCLoginProtocolService.refreshUrl(UriBuilder.fromUri(baseUrl)); + UriBuilder b = OIDCLoginProtocolService.tokenUrl(UriBuilder.fromUri(baseUrl)); return b.build(realm).toString(); } @@ -351,11 +344,6 @@ public class OAuthClient { return this; } - public OAuthClient responseType(String responseType) { - this.responseType = responseType; - return this; - } - public OAuthClient state(String state) { this.state = state; return this; diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java index 6497cb50f1..4743826502 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java @@ -283,7 +283,7 @@ public class AccountTest { // All fields are required, so there should be an error when something is missing. profilePage.updateProfile("", "New last", "new@email.com"); - Assert.assertEquals("Please specify first name", profilePage.getError()); + Assert.assertEquals("Please specify first name.", profilePage.getError()); Assert.assertEquals("", profilePage.getFirstName()); Assert.assertEquals("New last", profilePage.getLastName()); Assert.assertEquals("new@email.com", profilePage.getEmail()); @@ -292,7 +292,7 @@ public class AccountTest { profilePage.updateProfile("New first", "", "new@email.com"); - Assert.assertEquals("Please specify last name", profilePage.getError()); + Assert.assertEquals("Please specify last name.", profilePage.getError()); Assert.assertEquals("New first", profilePage.getFirstName()); Assert.assertEquals("", profilePage.getLastName()); Assert.assertEquals("new@email.com", profilePage.getEmail()); @@ -301,7 +301,7 @@ public class AccountTest { profilePage.updateProfile("New first", "New last", ""); - Assert.assertEquals("Please specify email", profilePage.getError()); + Assert.assertEquals("Please specify email.", profilePage.getError()); Assert.assertEquals("New first", profilePage.getFirstName()); Assert.assertEquals("New last", profilePage.getLastName()); Assert.assertEquals("", profilePage.getEmail()); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java index 1d6665171b..0d6b4cfefc 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java @@ -236,7 +236,7 @@ public class RequiredActionEmailVerificationTest { events.expectRequiredAction(EventType.VERIFY_EMAIL).session(sessionId).detail("email", "test-user@localhost").detail(Details.CODE_ID, mailCodeId).assertEvent(); assertTrue(infoPage.isCurrent()); - assertEquals("Email verified", infoPage.getInfo()); + assertEquals("Your email address has been verified.", infoPage.getInfo()); loginPage.open(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java index d832f967ee..e5e4c3c3f3 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java @@ -108,7 +108,7 @@ public class RequiredActionUpdateProfileTest { updateProfilePage.assertCurrent(); - Assert.assertEquals("Please specify first name", updateProfilePage.getError()); + Assert.assertEquals("Please specify first name.", updateProfilePage.getError()); events.assertEmpty(); } @@ -125,7 +125,7 @@ public class RequiredActionUpdateProfileTest { updateProfilePage.assertCurrent(); - Assert.assertEquals("Please specify last name", updateProfilePage.getError()); + Assert.assertEquals("Please specify last name.", updateProfilePage.getError()); events.assertEmpty(); } @@ -142,7 +142,7 @@ public class RequiredActionUpdateProfileTest { updateProfilePage.assertCurrent(); - Assert.assertEquals("Please specify email", updateProfilePage.getError()); + Assert.assertEquals("Please specify email.", updateProfilePage.getError()); events.assertEmpty(); } @@ -159,7 +159,7 @@ public class RequiredActionUpdateProfileTest { updateProfilePage.assertCurrent(); - Assert.assertEquals("Email already exists", updateProfilePage.getError()); + Assert.assertEquals("Email already exists.", updateProfilePage.getError()); events.assertEmpty(); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java index e1d83b32a3..cfc9534e7b 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java @@ -92,7 +92,7 @@ public class AdapterTestStrategy extends ExternalResource { @WebResource protected InputPage inputPage; - protected String LOGIN_URL = OIDCLoginProtocolService.loginPageUrl(UriBuilder.fromUri(AUTH_SERVER_URL)).build("demo").toString(); + protected String LOGIN_URL = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri(AUTH_SERVER_URL)).build("demo").toString(); public AdapterTestStrategy(String AUTH_SERVER_URL, String APP_SERVER_BASE_URL, AbstractKeycloakRule keycloakRule) { this.AUTH_SERVER_URL = AUTH_SERVER_URL; @@ -420,11 +420,12 @@ public class AdapterTestStrategy extends ExternalResource { public void testBadUser() throws Exception { Client client = ClientBuilder.newClient(); UriBuilder builder = UriBuilder.fromUri(AUTH_SERVER_URL); - URI uri = OIDCLoginProtocolService.grantAccessTokenUrl(builder).build("demo"); + URI uri = OIDCLoginProtocolService.tokenUrl(builder).build("demo"); WebTarget target = client.target(uri); String header = BasicAuthHelper.createHeader("customer-portal", "password"); Form form = new Form(); - form.param("username", "monkey@redhat.com") + form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD) + .param("username", "monkey@redhat.com") .param("password", "password"); Response response = target.request() .header(HttpHeaders.AUTHORIZATION, header) diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CookieTokenStoreAdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CookieTokenStoreAdapterTest.java index 54980c6960..632202d542 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CookieTokenStoreAdapterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CookieTokenStoreAdapterTest.java @@ -31,7 +31,7 @@ import org.openqa.selenium.WebDriver; */ public class CookieTokenStoreAdapterTest { - public static final String LOGIN_URL = OIDCLoginProtocolService.loginPageUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build("demo").toString(); + public static final String LOGIN_URL = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build("demo").toString(); @ClassRule public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule() { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/MultiTenancyTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/MultiTenancyTest.java index 8d4b835d23..235d5f54c4 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/MultiTenancyTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/MultiTenancyTest.java @@ -126,7 +126,7 @@ public class MultiTenancyTest { } private void doTenantRequests(String tenant, boolean logout) { - String tenantLoginUrl = OIDCLoginProtocolService.loginPageUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build(tenant).toString(); + String tenantLoginUrl = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build(tenant).toString(); driver.navigate().to("http://localhost:8081/multi-tenant?realm="+tenant); System.out.println("Current url: " + driver.getCurrentUrl()); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java index cce65bd49e..01a5861350 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java @@ -66,7 +66,7 @@ import java.util.Map; */ public class RelativeUriAdapterTest { - public static final String LOGIN_URL = OIDCLoginProtocolService.loginPageUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build("demo").toString(); + public static final String LOGIN_URL = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build("demo").toString(); public static PublicKey realmPublicKey; @ClassRule public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule(){ diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java index b1ae4cbe70..5b66f7f1df 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java @@ -175,7 +175,7 @@ public abstract class AbstractIdentityProviderTest { driver.navigate().to("http://localhost:8081/test-app/logout"); driver.navigate().to("http://localhost:8081/test-app"); - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login")); + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); } finally { getRealm().setRegistrationEmailAsUsername(false); @@ -215,7 +215,7 @@ public abstract class AbstractIdentityProviderTest { this.driver.navigate().to("http://localhost:8081/test-app/"); - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login")); + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); try { this.driver.findElement(By.className(getProviderId())); @@ -245,7 +245,7 @@ public abstract class AbstractIdentityProviderTest { this.driver.navigate().to("http://localhost:8081/test-app/"); - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login")); + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); try { this.driver.findElement(By.className(getProviderId())); @@ -267,7 +267,7 @@ public abstract class AbstractIdentityProviderTest { public void testUserAlreadyExistsWhenUpdatingProfile() { this.driver.navigate().to("http://localhost:8081/test-app/"); - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login")); + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); // choose the identity provider this.loginPage.clickSocial(getProviderId()); @@ -286,7 +286,7 @@ public abstract class AbstractIdentityProviderTest { assertNotNull(element); - assertEquals("Email already exists", element.getText()); + assertEquals("Email already exists.", element.getText()); this.updateProfilePage.assertCurrent(); this.updateProfilePage.update("Test", "User", "test-user@redhat.com"); @@ -306,7 +306,7 @@ public abstract class AbstractIdentityProviderTest { this.driver.navigate().to("http://localhost:8081/test-app/"); - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login")); + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); // choose the identity provider this.loginPage.clickSocial(getProviderId()); @@ -374,7 +374,7 @@ public abstract class AbstractIdentityProviderTest { public void testIdentityProviderNotAllowed() { this.driver.navigate().to("http://localhost:8081/test-app/"); - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login")); + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); driver.findElement(By.className("model-oidc-idp")); } @@ -438,7 +438,7 @@ public abstract class AbstractIdentityProviderTest { driver.navigate().to("http://localhost:8081/test-app/logout"); driver.navigate().to("http://localhost:8081/test-app"); - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login")); + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); } @Test @@ -533,13 +533,13 @@ public abstract class AbstractIdentityProviderTest { driver.navigate().to("http://localhost:8081/test-app/logout"); driver.navigate().to("http://localhost:8081/test-app"); - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login")); + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); } private void authenticateWithIdentityProvider(IdentityProviderModel identityProviderModel, String username) { driver.navigate().to("http://localhost:8081/test-app"); - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login")); + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); // choose the identity provider this.loginPage.clickSocial(getProviderId()); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java index b46617eac4..bac23e8c2b 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java @@ -74,7 +74,7 @@ public class IdentityProviderHintTest { public void testInvalidIdentityProviderHint() { this.driver.navigate().to("http://localhost:8081/test-app?k_idp_hint=invalid-idp-id"); - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login")); + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); assertEquals("Could not find an identity provider with the identifier [invalid-idp-id].", this.driver.findElement(By.className("instruction")).getText()); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java index 611c6bd10e..5f484dc8b2 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java @@ -236,12 +236,12 @@ public class FederationProvidersIntegrationTest { // check existing username registerPage.register("firstName", "lastName", "email@mail.cz", "existing", "Password1", "Password1"); registerPage.assertCurrent(); - Assert.assertEquals("Username already exists", registerPage.getError()); + Assert.assertEquals("Username already exists.", registerPage.getError()); // Check existing email registerPage.register("firstName", "lastName", "existing@email.org", "nonExisting", "Password1", "Password1"); registerPage.assertCurrent(); - Assert.assertEquals("Email already exists", registerPage.getError()); + Assert.assertEquals("Email already exists.", registerPage.getError()); } @Test diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java index 75a83f4f8a..8c5a2f8b22 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java @@ -162,7 +162,7 @@ public class LoginTest { loginPage.assertCurrent(); - Assert.assertEquals("Account is disabled, contact admin", loginPage.getError()); + Assert.assertEquals("Account is disabled, contact admin.", loginPage.getError()); events.expectLogin().user(userId).session((String) null).error("user_disabled").detail(Details.USERNAME, "login-test").assertEvent(); } finally { @@ -316,7 +316,7 @@ public class LoginTest { loginPage.login("login@test.com", "password"); loginPage.assertCurrent(); - Assert.assertEquals("Login timeout. Please login again", loginPage.getError()); + Assert.assertEquals("Login timeout. Please login again.", loginPage.getError()); events.expectLogin().user((String) null).session((String) null).error("expired_code").clearDetails().detail(Details.CODE_ID, AssertEvents.isCodeId()).assertEvent(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java index 2d0657a438..b2ac90e04e 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java @@ -78,7 +78,7 @@ public class RegisterTest { registerPage.register("firstName", "lastName", "registerExistingUser@email", "test-user@localhost", "password", "password"); registerPage.assertCurrent(); - Assert.assertEquals("Username already exists", registerPage.getError()); + Assert.assertEquals("Username already exists.", registerPage.getError()); events.expectRegister("test-user@localhost", "registerExistingUser@email").user((String) null).error("username_in_use").assertEvent(); } @@ -92,7 +92,7 @@ public class RegisterTest { registerPage.register("firstName", "lastName", "registerUserInvalidPasswordConfirm@email", "registerUserInvalidPasswordConfirm", "password", "invalid"); registerPage.assertCurrent(); - Assert.assertEquals("Password confirmation doesn't match", registerPage.getError()); + Assert.assertEquals("Password confirmation doesn't match.", registerPage.getError()); events.expectRegister("registerUserInvalidPasswordConfirm", "registerUserInvalidPasswordConfirm@email").user((String) null).error("invalid_registration").assertEvent(); } @@ -157,7 +157,7 @@ public class RegisterTest { registerPage.register("firstName", "lastName", "registerUserMissingUsername@email", null, "password", "password"); registerPage.assertCurrent(); - Assert.assertEquals("Please specify username", registerPage.getError()); + Assert.assertEquals("Please specify username.", registerPage.getError()); events.expectRegister(null, "registerUserMissingUsername@email").removeDetail("username").error("invalid_registration").assertEvent(); } @@ -170,12 +170,12 @@ public class RegisterTest { registerPage.register("firstName", "lastName", null, "registerUserMissingEmail", "password", "password"); registerPage.assertCurrent(); - Assert.assertEquals("Please specify email", registerPage.getError()); + Assert.assertEquals("Please specify email.", registerPage.getError()); events.expectRegister("registerUserMissingEmail", null).removeDetail("email").error("invalid_registration").assertEvent(); registerPage.register("firstName", "lastName", "registerUserInvalidEmailemail", "registerUserInvalidEmail", "password", "password"); registerPage.assertCurrent(); - Assert.assertEquals("Invalid email address", registerPage.getError()); + Assert.assertEquals("Invalid email address.", registerPage.getError()); events.expectRegister("registerUserInvalidEmail", "registerUserInvalidEmailemail").error("invalid_registration").assertEvent(); } @@ -205,7 +205,7 @@ public class RegisterTest { registerPage.registerWithEmailAsUsername("firstName", "lastName", "test-user@localhost", "password", "password"); registerPage.assertCurrent(); - Assert.assertEquals("Username already exists", registerPage.getError()); + Assert.assertEquals("Username already exists.", registerPage.getError()); events.expectRegister("test-user@localhost", "test-user@localhost").user((String) null).error("username_in_use").assertEvent(); } finally { @@ -224,12 +224,12 @@ public class RegisterTest { registerPage.registerWithEmailAsUsername("firstName", "lastName", null, "password", "password"); registerPage.assertCurrent(); - Assert.assertEquals("Please specify email", registerPage.getError()); + Assert.assertEquals("Please specify email.", registerPage.getError()); events.expectRegister(null, null).removeDetail("username").removeDetail("email").error("invalid_registration").assertEvent(); registerPage.registerWithEmailAsUsername("firstName", "lastName", "registerUserInvalidEmailemail", "password", "password"); registerPage.assertCurrent(); - Assert.assertEquals("Invalid email address", registerPage.getError()); + Assert.assertEquals("Invalid email address.", registerPage.getError()); events.expectRegister("registerUserInvalidEmailemail", "registerUserInvalidEmailemail").error("invalid_registration").assertEvent(); } finally { configureRelamRegistrationEmailAsUsername(false); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java index 7582e2094f..a96b660a54 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java @@ -504,7 +504,7 @@ public class ResetPasswordTest { events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).session(sessionId).detail(Details.USERNAME, username).assertEvent(); assertTrue(infoPage.isCurrent()); - assertEquals("Password updated", infoPage.getInfo()); + assertEquals("Your password has been updated", infoPage.getInfo()); loginPage.open(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java index 506013b70b..dd32e7cef0 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java @@ -4,11 +4,14 @@ import org.junit.Assert; import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runners.MethodSorters; +import org.keycloak.constants.KerberosConstants; import org.keycloak.models.ApplicationModel; import org.keycloak.models.ClientModel; import org.keycloak.models.Constants; import org.keycloak.models.FederatedIdentityModel; +import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.RoleModel; @@ -16,6 +19,9 @@ import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProviderFactory; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; +import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.services.managers.RealmManager; @@ -197,12 +203,16 @@ public class ImportTest extends AbstractModelTest { Assert.assertEquals("localhost", smtpConfig.get("host")); Assert.assertEquals("3025", smtpConfig.get("port")); - // Test social config - //FIXME: KEYCLOAK-883 -// Map socialConfig = realm.getSocialConfig(); -// Assert.assertTrue(socialConfig.size() == 2); -// Assert.assertEquals("abc", socialConfig.get("google.key")); -// Assert.assertEquals("def", socialConfig.get("google.secret")); + // Test identity providers + List identityProviders = realm.getIdentityProviders(); + Assert.assertEquals(1, identityProviders.size()); + IdentityProviderModel google = identityProviders.get(0); + Assert.assertEquals("google1", google.getId()); + Assert.assertEquals("google", google.getProviderId()); + Assert.assertEquals("Google", google.getName()); + Assert.assertTrue(google.isEnabled()); + Assert.assertEquals("googleId", google.getConfig().get("clientId")); + Assert.assertEquals("googleSecret", google.getConfig().get("clientSecret")); // Test federation providers List fedProviders = realm.getUserFederationProviders(); @@ -216,6 +226,23 @@ public class ImportTest extends AbstractModelTest { // Assert that federation link wasn't created during import UserFederationProviderFactory factory = (UserFederationProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, "dummy"); Assert.assertNull(factory.getInstance(session, null).getUserByUsername(realm, "wburke")); + + // Test protocol mappers. Default application has all the builtin protocol mappers. OtherApp just gss credential + Assert.assertNotNull(application.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, "username")); + Assert.assertNotNull(application.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, "email")); + Assert.assertNotNull(application.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, "given name")); + Assert.assertNull(application.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME)); + + Assert.assertEquals(1, otherApp.getProtocolMappers().size()); + Assert.assertNull(otherApp.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, "username")); + ProtocolMapperModel gssCredentialMapper = otherApp.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME); + Assert.assertEquals(KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME, gssCredentialMapper.getName()); + Assert.assertEquals( OIDCLoginProtocol.LOGIN_PROTOCOL, gssCredentialMapper.getProtocol()); + Assert.assertEquals(UserSessionNoteMapper.PROVIDER_ID, gssCredentialMapper.getProtocolMapper()); + String includeInAccessToken = gssCredentialMapper.getConfig().get(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN); + String includeInIdToken = gssCredentialMapper.getConfig().get(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN); + Assert.assertTrue(includeInAccessToken.equalsIgnoreCase("true")); + Assert.assertTrue(includeInIdToken == null || Boolean.parseBoolean(includeInIdToken) == false); } @Test diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java index 56538a738c..34342e04b9 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java @@ -149,7 +149,7 @@ public class AccessTokenTest { AccessTokenResponse response = oauth.doAccessTokenRequest(code, "invalid"); Assert.assertEquals(400, response.getStatusCode()); - AssertEvents.ExpectedEvent expectedEvent = events.expectCodeToToken(codeId, loginEvent.getSessionId()).error("invalid_client_credentials").removeDetail(Details.TOKEN_ID).removeDetail(Details.REFRESH_TOKEN_ID); + AssertEvents.ExpectedEvent expectedEvent = events.expectCodeToToken(codeId, loginEvent.getSessionId()).error("invalid_client_credentials").clearDetails().user((String) null).session((String) null); expectedEvent.assertEvent(); } @@ -190,7 +190,7 @@ public class AccessTokenTest { assertNull(tokenResponse.getAccessToken()); assertNull(tokenResponse.getRefreshToken()); - events.expectCodeToToken(codeId, sessionId).removeDetail(Details.TOKEN_ID).client((String) null).user((String) null).session((String) null).removeDetail(Details.REFRESH_TOKEN_ID).error(Errors.INVALID_CODE).assertEvent(); + events.expectCodeToToken(codeId, sessionId).removeDetail(Details.TOKEN_ID).user((String) null).session((String) null).removeDetail(Details.REFRESH_TOKEN_ID).error(Errors.INVALID_CODE).assertEvent(); events.clear(); } @@ -222,7 +222,7 @@ public class AccessTokenTest { Assert.assertEquals(400, response.getStatusCode()); AssertEvents.ExpectedEvent expectedEvent = events.expectCodeToToken(codeId, null); - expectedEvent.error("invalid_code").removeDetail(Details.TOKEN_ID).removeDetail(Details.REFRESH_TOKEN_ID).client((String) null).user((String) null); + expectedEvent.error("invalid_code").removeDetail(Details.TOKEN_ID).removeDetail(Details.REFRESH_TOKEN_ID).user((String) null); expectedEvent.assertEvent(); events.clear(); @@ -254,7 +254,7 @@ public class AccessTokenTest { Assert.assertEquals(400, response.getStatusCode()); AssertEvents.ExpectedEvent expectedEvent = events.expectCodeToToken(codeId, null); - expectedEvent.error("invalid_code").removeDetail(Details.TOKEN_ID).removeDetail(Details.REFRESH_TOKEN_ID).client((String) null).user((String) null); + expectedEvent.error("invalid_code").removeDetail(Details.TOKEN_ID).removeDetail(Details.REFRESH_TOKEN_ID).user((String) null); expectedEvent.assertEvent(); events.clear(); @@ -335,7 +335,7 @@ public class AccessTokenTest { public void testValidateAccessToken() throws Exception { Client client = ClientBuilder.newClient(); UriBuilder builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT); - URI grantUri = OIDCLoginProtocolService.grantAccessTokenUrl(builder).build("test"); + URI grantUri = OIDCLoginProtocolService.tokenUrl(builder).build("test"); WebTarget grantTarget = client.target(grantUri); builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT); URI validateUri = OIDCLoginProtocolService.validateAccessTokenUrl(builder).build("test"); @@ -392,7 +392,7 @@ public class AccessTokenTest { public void testGrantAccessToken() throws Exception { Client client = ClientBuilder.newClient(); UriBuilder builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT); - URI grantUri = OIDCLoginProtocolService.grantAccessTokenUrl(builder).build("test"); + URI grantUri = OIDCLoginProtocolService.tokenUrl(builder).build("test"); WebTarget grantTarget = client.target(grantUri); { // test checkSsl @@ -421,6 +421,7 @@ public class AccessTokenTest { { // test null username String header = BasicAuthHelper.createHeader("test-app", "password"); Form form = new Form(); + form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD); form.param("password", "password"); Response response = grantTarget.request() .header(HttpHeaders.AUTHORIZATION, header) @@ -432,6 +433,7 @@ public class AccessTokenTest { { // test no password String header = BasicAuthHelper.createHeader("test-app", "password"); Form form = new Form(); + form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD); form.param("username", "test-user@localhost"); Response response = grantTarget.request() .header(HttpHeaders.AUTHORIZATION, header) @@ -443,6 +445,7 @@ public class AccessTokenTest { { // test invalid password String header = BasicAuthHelper.createHeader("test-app", "password"); Form form = new Form(); + form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD); form.param("username", "test-user@localhost"); form.param("password", "invalid"); Response response = grantTarget.request() @@ -598,7 +601,7 @@ public class AccessTokenTest { public void testTokenMapping() throws Exception { Client client = ClientBuilder.newClient(); UriBuilder builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT); - URI grantUri = OIDCLoginProtocolService.grantAccessTokenUrl(builder).build("test"); + URI grantUri = OIDCLoginProtocolService.tokenUrl(builder).build("test"); WebTarget grantTarget = client.target(grantUri); { KeycloakSession session = keycloakRule.startSession(); @@ -721,7 +724,8 @@ public class AccessTokenTest { protected Response executeGrantAccessTokenRequest(WebTarget grantTarget) { String header = BasicAuthHelper.createHeader("test-app", "password"); Form form = new Form(); - form.param("username", "test-user@localhost") + form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD) + .param("username", "test-user@localhost") .param("password", "password"); return grantTarget.request() .header(HttpHeaders.AUTHORIZATION, header) diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java index c75a030821..9aa884ffe8 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java @@ -108,7 +108,7 @@ public class OAuthRedirectUriTest { oauth.openLoginForm(); Assert.assertTrue(errorPage.isCurrent()); - Assert.assertEquals("Invalid redirect_uri", errorPage.getError()); + Assert.assertEquals("Invalid parameter: redirect_uri", errorPage.getError()); } finally { keycloakRule.update(new KeycloakRule.KeycloakSetup() { @Override @@ -133,7 +133,7 @@ public class OAuthRedirectUriTest { oauth.openLoginForm(); Assert.assertTrue(errorPage.isCurrent()); - Assert.assertEquals("Invalid redirect_uri", errorPage.getError()); + Assert.assertEquals("Invalid parameter: redirect_uri", errorPage.getError()); } finally { keycloakRule.update(new KeycloakRule.KeycloakSetup() { @Override @@ -158,7 +158,7 @@ public class OAuthRedirectUriTest { oauth.openLoginForm(); Assert.assertTrue(errorPage.isCurrent()); - Assert.assertEquals("Invalid redirect_uri", errorPage.getError()); + Assert.assertEquals("Invalid parameter: redirect_uri", errorPage.getError()); } finally { keycloakRule.update(new KeycloakRule.KeycloakSetup() { @Override @@ -184,7 +184,7 @@ public class OAuthRedirectUriTest { oauth.openLoginForm(); Assert.assertTrue(errorPage.isCurrent()); - Assert.assertEquals("Invalid redirect_uri", errorPage.getError()); + Assert.assertEquals("Invalid parameter: redirect_uri", errorPage.getError()); } @Test @@ -244,7 +244,7 @@ public class OAuthRedirectUriTest { Assert.assertTrue(loginPage.isCurrent()); } else { Assert.assertTrue(errorPage.isCurrent()); - Assert.assertEquals("Invalid redirect_uri", errorPage.getError()); + Assert.assertEquals("Invalid parameter: redirect_uri", errorPage.getError()); } if (expectValid) { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java index 4f254307bd..3776c03d99 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java @@ -97,7 +97,7 @@ public class RefreshTokenTest { public void nullRefreshToken() throws Exception { Client client = ClientBuilder.newClient(); UriBuilder builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT); - URI uri = OIDCLoginProtocolService.refreshUrl(builder).build("test"); + URI uri = OIDCLoginProtocolService.tokenUrl(builder).build("test"); WebTarget target = client.target(uri); org.keycloak.representations.AccessTokenResponse tokenResponse = null; @@ -389,10 +389,10 @@ public class RefreshTokenTest { public void testCheckSsl() throws Exception { Client client = ClientBuilder.newClient(); UriBuilder builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT); - URI grantUri = OIDCLoginProtocolService.grantAccessTokenUrl(builder).build("test"); + URI grantUri = OIDCLoginProtocolService.tokenUrl(builder).build("test"); WebTarget grantTarget = client.target(grantUri); builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT); - URI uri = OIDCLoginProtocolService.refreshUrl(builder).build("test"); + URI uri = OIDCLoginProtocolService.tokenUrl(builder).build("test"); WebTarget refreshTarget = client.target(uri); String refreshToken = null; @@ -452,6 +452,7 @@ public class RefreshTokenTest { protected Response executeRefreshToken(WebTarget refreshTarget, String refreshToken) { String header = BasicAuthHelper.createHeader("test-app", "password"); Form form = new Form(); + form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.REFRESH_TOKEN); form.param("refresh_token", refreshToken); return refreshTarget.request() .header(HttpHeaders.AUTHORIZATION, header) @@ -461,7 +462,8 @@ public class RefreshTokenTest { protected Response executeGrantAccessTokenRequest(WebTarget grantTarget) { String header = BasicAuthHelper.createHeader("test-app", "password"); Form form = new Form(); - form.param("username", "test-user@localhost") + form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD) + .param("username", "test-user@localhost") .param("password", "password"); return grantTarget.request() .header(HttpHeaders.AUTHORIZATION, header) diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java index ea269c339d..7ab25689cb 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java @@ -181,11 +181,9 @@ public class ResourceOwnerPasswordCredentialsGrantTest { events.expectLogin() .client("resource-owner") .session((String) null) - .detail(Details.AUTH_METHOD, "oauth_credentials") - .detail(Details.RESPONSE_TYPE, "token") - .removeDetail(Details.CODE_ID) - .removeDetail(Details.REDIRECT_URI) + .clearDetails() .error(Errors.INVALID_CLIENT_CREDENTIALS) + .user((String) null) .assertEvent(); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java index eb52fbb80a..ce9a574435 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java @@ -24,6 +24,7 @@ package org.keycloak.testsuite.oidc; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; +import org.keycloak.OAuth2Constants; import org.keycloak.models.RealmModel; import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.representations.AccessTokenResponse; @@ -68,7 +69,7 @@ public class UserInfoTest { public void testSuccessfulUserInfoRequest() throws Exception { Client client = ClientBuilder.newClient(); UriBuilder builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT); - URI grantUri = OIDCLoginProtocolService.grantAccessTokenUrl(builder).build("test"); + URI grantUri = OIDCLoginProtocolService.tokenUrl(builder).build("test"); WebTarget grantTarget = client.target(grantUri); AccessTokenResponse accessTokenResponse = executeGrantAccessTokenRequest(grantTarget); Response response = executeUserInfoRequest(accessTokenResponse.getToken()); @@ -99,7 +100,8 @@ public class UserInfoTest { private AccessTokenResponse executeGrantAccessTokenRequest(WebTarget grantTarget) { String header = BasicAuthHelper.createHeader("test-app", "password"); Form form = new Form(); - form.param("username", "test-user@localhost") + form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD) + .param("username", "test-user@localhost") .param("password", "password"); Response response = grantTarget.request() diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java index 1766a8766c..fc83347637 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java @@ -56,7 +56,7 @@ public class LoginPage extends AbstractPage { @FindBy(linkText = "Register") private WebElement registerLink; - @FindBy(linkText = "Password") + @FindBy(linkText = "Forgot Password?") private WebElement resetPasswordLink; @FindBy(linkText = "Username") diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java index be6d79d4e6..cdf61d0331 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java @@ -136,7 +136,7 @@ public class AccessTokenPerfTest { } public String getLoginFormUrl(String state) { - UriBuilder b = OIDCLoginProtocolService.loginPageUrl(UriBuilder.fromUri(baseUrl)); + UriBuilder b = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri(baseUrl)); if (responseType != null) { b.queryParam(OAuth2Constants.RESPONSE_TYPE, responseType); } @@ -204,7 +204,7 @@ public class AccessTokenPerfTest { String authorization = BasicAuthHelper.createHeader(clientId, "password"); - String res = client.target(OIDCLoginProtocolService.accessCodeToTokenUrl(UriBuilder.fromUri(baseUrl)).build(realm)).request() + String res = client.target(OIDCLoginProtocolService.tokenUrl(UriBuilder.fromUri(baseUrl)).build(realm)).request() .header(HttpHeaders.AUTHORIZATION, authorization) .post(Entity.form(form), String.class); count.incrementAndGet(); diff --git a/testsuite/integration/src/test/resources/model/testrealm.json b/testsuite/integration/src/test/resources/model/testrealm.json index 8d36a576fc..f43c7fc610 100755 --- a/testsuite/integration/src/test/resources/model/testrealm.json +++ b/testsuite/integration/src/test/resources/model/testrealm.json @@ -15,12 +15,12 @@ "identityProviders" : [ { "providerId" : "google", - "id" : "google", + "id" : "google1", "name" : "Google", "enabled": true, "config": { - "clientId": "clientId", - "clientSecret": "clientSecret" + "clientId": "googleId", + "clientSecret": "googleSecret" } } ], @@ -111,9 +111,23 @@ }, { "name": "OtherApp", - "enabled": true + "enabled": true, + "protocolMappers" : [ + { + "name" : "gss delegation credential", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : true, + "consentText" : "gss delegation credential", + "config" : { + "user.session.note" : "gss_delegation_credential", + "access.token.claim" : "true", + "claim.name" : "gss_delegation_credential", + "Claim JSON Type" : "String" + } + } + ] } - ], "oauthClients" : [ { diff --git a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/OAuthClient.java b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/OAuthClient.java index 168456e339..d5d2379e57 100755 --- a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/OAuthClient.java +++ b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/OAuthClient.java @@ -199,7 +199,7 @@ public class OAuthClient { } public String getLoginFormUrl() { - UriBuilder b = OIDCLoginProtocolService.loginPageUrl(UriBuilder.fromUri(baseUrl)); + UriBuilder b = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri(baseUrl)); if (responseType != null) { b.queryParam(OAuth2Constants.RESPONSE_TYPE, responseType); } @@ -216,7 +216,7 @@ public class OAuthClient { } public String getAccessTokenUrl() { - UriBuilder b = OIDCLoginProtocolService.accessCodeToTokenUrl(UriBuilder.fromUri(baseUrl)); + UriBuilder b = OIDCLoginProtocolService.tokenUrl(UriBuilder.fromUri(baseUrl)); return b.build(realm).toString(); } @@ -232,12 +232,12 @@ public class OAuthClient { } public String getResourceOwnerPasswordCredentialGrantUrl() { - UriBuilder b = OIDCLoginProtocolService.grantAccessTokenUrl(UriBuilder.fromUri(baseUrl)); + UriBuilder b = OIDCLoginProtocolService.tokenUrl(UriBuilder.fromUri(baseUrl)); return b.build(realm).toString(); } public String getRefreshTokenUrl() { - UriBuilder b = OIDCLoginProtocolService.refreshUrl(UriBuilder.fromUri(baseUrl)); + UriBuilder b = OIDCLoginProtocolService.tokenUrl(UriBuilder.fromUri(baseUrl)); return b.build(realm).toString(); } diff --git a/testsuite/proxy/src/test/java/org/keycloak/testsuite/ProxyTest.java b/testsuite/proxy/src/test/java/org/keycloak/testsuite/ProxyTest.java index 7443287087..a93d416793 100755 --- a/testsuite/proxy/src/test/java/org/keycloak/testsuite/ProxyTest.java +++ b/testsuite/proxy/src/test/java/org/keycloak/testsuite/ProxyTest.java @@ -188,7 +188,7 @@ public class ProxyTest { @WebResource protected LoginPage loginPage; - public static final String LOGIN_URL = OIDCLoginProtocolService.loginPageUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build("demo").toString(); + public static final String LOGIN_URL = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build("demo").toString(); @Test public void testHttp() throws Exception {