keycloak-aplcache
Changes
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml 1(+1 -0)
pom.xml 6(+6 -0)
services/pom.xml 4(+4 -0)
Details
diff --git a/common/src/main/java/org/keycloak/common/Profile.java b/common/src/main/java/org/keycloak/common/Profile.java
index 7f97e55..5a64c88 100755
--- a/common/src/main/java/org/keycloak/common/Profile.java
+++ b/common/src/main/java/org/keycloak/common/Profile.java
@@ -35,13 +35,13 @@ import java.util.Set;
public class Profile {
public enum Feature {
- AUTHORIZATION, IMPERSONATION, SCRIPTS, DOCKER
+ AUTHORIZATION, IMPERSONATION, SCRIPTS, DOCKER, ACCOUNT2
}
private enum ProfileValue {
- PRODUCT(Feature.AUTHORIZATION, Feature.SCRIPTS, Feature.DOCKER),
- PREVIEW,
- COMMUNITY(Feature.DOCKER);
+ PRODUCT(Feature.AUTHORIZATION, Feature.SCRIPTS, Feature.DOCKER, Feature.ACCOUNT2),
+ PREVIEW(Feature.ACCOUNT2),
+ COMMUNITY(Feature.DOCKER, Feature.ACCOUNT2);
private List<Feature> disabled;
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml
index 18e162a..e85db0e 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml
@@ -62,6 +62,7 @@
<module name="org.bouncycastle" />
<module name="javax.api"/>
<module name="javax.activation.api"/>
+ <module name="javax.json.api"/>
<module name="org.apache.httpcomponents"/>
<module name="org.twitter4j"/>
<module name="javax.transaction.api"/>
pom.xml 6(+6 -0)
diff --git a/pom.xml b/pom.xml
index 375d767..337c568 100755
--- a/pom.xml
+++ b/pom.xml
@@ -81,6 +81,7 @@
<elytron.undertow-server.version>1.0.0.Final</elytron.undertow-server.version>
<woodstox.version>5.0.3</woodstox.version>
<xmlsec.version>2.0.5</xmlsec.version>
+ <glassfish.json.version>1.0.4</glassfish.json.version>
<!-- Authorization Drools Policy Provider -->
<version.org.drools>6.4.0.Final</version.org.drools>
@@ -414,6 +415,11 @@
<version>${wildfly.version}</version>
<type>zip</type>
</dependency>
+ <dependency>
+ <groupId>org.glassfish</groupId>
+ <artifactId>javax.json</artifactId>
+ <version>${glassfish.json.version}</version>
+ </dependency>
<!-- Twitter -->
<dependency>
services/pom.xml 4(+4 -0)
diff --git a/services/pom.xml b/services/pom.xml
index 703d485..9cd490e 100755
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -59,6 +59,10 @@
<artifactId>javax.mail-api</artifactId>
</dependency>
<dependency>
+ <groupId>org.glassfish</groupId>
+ <artifactId>javax.json</artifactId>
+ </dependency>
+ <dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi</artifactId>
<scope>provided</scope>
diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountConsole.java b/services/src/main/java/org/keycloak/services/resources/account/AccountConsole.java
new file mode 100644
index 0000000..20f3e79
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/account/AccountConsole.java
@@ -0,0 +1,215 @@
+package org.keycloak.services.resources.account;
+
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.keycloak.common.Version;
+import org.keycloak.models.*;
+import org.keycloak.models.Constants;
+import org.keycloak.services.Urls;
+import org.keycloak.services.managers.ClientManager;
+import org.keycloak.theme.BrowserSecurityHeaderSetup;
+import org.keycloak.theme.FreeMarkerException;
+import org.keycloak.theme.FreeMarkerUtil;
+import org.keycloak.theme.Theme;
+import org.keycloak.utils.MediaType;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.json.Json;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonWriter;
+import org.jboss.logging.Logger;
+import org.keycloak.models.ClientModel;
+import org.keycloak.protocol.oidc.utils.RedirectUtils;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.services.managers.AppAuthManager;
+import org.keycloak.services.managers.Auth;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.util.ResolveRelative;
+import org.keycloak.services.validation.Validation;
+
+/**
+ * Created by st on 29/03/17.
+ */
+public class AccountConsole {
+ private static final Logger logger = Logger.getLogger(AccountConsole.class);
+
+ private final Pattern bundleParamPattern = Pattern.compile("(\\{\\s*(\\d+)\\s*\\})");
+
+ @Context
+ protected KeycloakSession session;
+ @Context
+ protected UriInfo uriInfo;
+
+ private final AppAuthManager authManager;
+ private final RealmModel realm;
+ private final ClientModel client;
+ private final Theme theme;
+
+ private Auth auth;
+
+ public AccountConsole(RealmModel realm, ClientModel client, Theme theme) {
+ this.realm = realm;
+ this.client = client;
+ this.theme = theme;
+ this.authManager = new AppAuthManager();
+ }
+
+ public void init() {
+ AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm);
+ if (authResult != null) {
+ auth = new Auth(realm, authResult.getToken(), authResult.getUser(), client, authResult.getSession(), true);
+ }
+ }
+
+ @GET
+ @NoCache
+ public Response getMainPage() throws URISyntaxException, IOException, FreeMarkerException {
+ if (!uriInfo.getRequestUri().getPath().endsWith("/")) {
+ return Response.status(302).location(uriInfo.getRequestUriBuilder().path("/").build()).build();
+ } else {
+ Map<String, Object> map = new HashMap<>();
+
+ URI baseUri = uriInfo.getBaseUri();
+
+ String authUrl = baseUri.toString();
+ authUrl = authUrl.substring(0, authUrl.length() - 1);
+
+ map.put("authUrl", authUrl);
+ map.put("baseUrl", authUrl + "/realms/" + realm.getName() + "/account");
+ map.put("realm", realm.getName());
+ map.put("resourceUrl", Urls.themeRoot(baseUri) + "/account/" + theme.getName());
+ map.put("resourceVersion", Version.RESOURCES_VERSION);
+
+ String[] referrer = getReferrer();
+ if (referrer != null) {
+ map.put("referrer", referrer[0]);
+ map.put("referrer_uri", referrer[1]);
+ }
+
+ try {
+ if (auth != null) {
+ Locale locale = session.getContext().resolveLocale(auth.getUser());
+ map.put("locale", locale.toLanguageTag());
+ map.put("msg", messagesToJsonString(theme.getMessages(locale)));
+ }
+ } catch (Exception e) {
+ logger.warn("Failed to load messages", e);
+ }
+
+ map.put("properties", theme.getProperties());
+
+ FreeMarkerUtil freeMarkerUtil = new FreeMarkerUtil();
+ String result = freeMarkerUtil.processTemplate(map, "index.ftl", theme);
+ Response.ResponseBuilder builder = Response.status(Response.Status.OK).type(MediaType.TEXT_HTML_UTF_8).language(Locale.ENGLISH).entity(result);
+ BrowserSecurityHeaderSetup.headers(builder, realm);
+ return builder.build();
+ }
+ }
+
+ private String messagesToJsonString(Properties props) {
+ if (props == null) return "";
+
+ JsonObjectBuilder json = Json.createObjectBuilder();
+ for (String prop : props.stringPropertyNames()) {
+ json.add(prop, convertPropValue(props.getProperty(prop)));
+ }
+
+ return json.build().toString();
+ }
+
+ private String convertPropValue(String propertyValue) {
+ propertyValue = propertyValue.replace("''", "%27");
+ propertyValue = propertyValue.replace("'", "%27");
+ propertyValue = propertyValue.replace("\"", "%22");
+
+ propertyValue = putJavaParamsInNgTranslateFormat(propertyValue);
+
+ return propertyValue;
+ }
+
+ // Put java resource bundle params in ngx-translate format
+ // Do you like {0} and {1} ?
+ // becomes
+ // Do you like {{param_0}} and {{param_1}} ?
+ private String putJavaParamsInNgTranslateFormat(String propertyValue) {
+ Matcher matcher = bundleParamPattern.matcher(propertyValue);
+ while (matcher.find()) {
+ propertyValue = propertyValue.replace(matcher.group(1), "{{param_" + matcher.group(2) + "}}");
+ }
+
+ return propertyValue;
+ }
+
+ @GET
+ @Path("index.html")
+ public Response getIndexHtmlRedirect() {
+ return Response.status(302).location(session.getContext().getUri().getRequestUriBuilder().path("../").build()).build();
+ }
+
+ @GET
+ @Path("keycloak.json")
+ @Produces(MediaType.APPLICATION_JSON)
+ @NoCache
+ public ClientManager.InstallationAdapterConfig getConfig() {
+ ClientModel accountClient = realm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
+ if (accountClient == null) {
+ throw new javax.ws.rs.NotFoundException("Account console client not found");
+ }
+ RealmManager realmMgr = new RealmManager(session);
+ URI baseUri = session.getContext().getUri().getBaseUri();
+ return new ClientManager(realmMgr).toInstallationRepresentation(realm, accountClient, baseUri);
+ }
+
+ // TODO: took this code from elsewhere - refactor
+ private String[] getReferrer() {
+ String referrer = uriInfo.getQueryParameters().getFirst("referrer");
+ if (referrer == null) {
+ return null;
+ }
+
+ String referrerUri = uriInfo.getQueryParameters().getFirst("referrer_uri");
+
+ ClientModel referrerClient = realm.getClientByClientId(referrer);
+ if (referrerClient != null) {
+ if (referrerUri != null) {
+ referrerUri = RedirectUtils.verifyRedirectUri(uriInfo, referrerUri, realm, referrerClient);
+ } else {
+ referrerUri = ResolveRelative.resolveRelativeUri(uriInfo.getRequestUri(), client.getRootUrl(), referrerClient.getBaseUrl());
+ }
+
+ if (referrerUri != null) {
+ String referrerName = referrerClient.getName();
+ if (Validation.isBlank(referrerName)) {
+ referrerName = referrer;
+ }
+ return new String[]{referrerName, referrerUri};
+ }
+ } else if (referrerUri != null) {
+ referrerClient = realm.getClientByClientId(referrer);
+ if (client != null) {
+ referrerUri = RedirectUtils.verifyRedirectUri(uriInfo, referrerUri, realm, referrerClient);
+
+ if (referrerUri != null) {
+ return new String[]{referrer, referrerUri};
+ }
+ }
+ }
+
+ return null;
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountLoader.java b/services/src/main/java/org/keycloak/services/resources/account/AccountLoader.java
index 11c3161..9aef0d0 100644
--- a/services/src/main/java/org/keycloak/services/resources/account/AccountLoader.java
+++ b/services/src/main/java/org/keycloak/services/resources/account/AccountLoader.java
@@ -27,12 +27,16 @@ import org.keycloak.models.RealmModel;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.Auth;
import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.theme.Theme;
+import org.keycloak.theme.ThemeProvider;
import javax.ws.rs.HttpMethod;
+import javax.ws.rs.InternalServerErrorException;
import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
+import java.io.IOException;
import java.util.List;
/**
@@ -42,10 +46,7 @@ public class AccountLoader {
private static final Logger logger = Logger.getLogger(AccountLoader.class);
- private AccountLoader() {
- }
-
- public static Object getAccountService(KeycloakSession session, EventBuilder event) {
+ public Object getAccountService(KeycloakSession session, EventBuilder event) {
RealmModel realm = session.getContext().getRealm();
ClientModel client = realm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
@@ -59,6 +60,9 @@ public class AccountLoader {
MediaType content = headers.getMediaType();
List<MediaType> accepts = headers.getAcceptableMediaTypes();
+ Theme theme = getTheme(session);
+ boolean deprecatedAccount = isDeprecatedFormsAccountConsole(theme);
+
if (request.getHttpMethod().equals(HttpMethod.OPTIONS)) {
return new CorsPreflightService(request);
} else if ((accepts.contains(MediaType.APPLICATION_JSON_TYPE) || MediaType.APPLICATION_JSON_TYPE.equals(content)) && !request.getUri().getPath().endsWith("keycloak.json")) {
@@ -73,10 +77,34 @@ public class AccountLoader {
accountRestService.init();
return accountRestService;
} else {
- AccountFormService accountFormService = new AccountFormService(realm, client, event);
- ResteasyProviderFactory.getInstance().injectProperties(accountFormService);
- accountFormService.init();
- return accountFormService;
+ if (deprecatedAccount) {
+ AccountFormService accountFormService = new AccountFormService(realm, client, event);
+ ResteasyProviderFactory.getInstance().injectProperties(accountFormService);
+ accountFormService.init();
+ return accountFormService;
+ } else {
+ AccountConsole console = new AccountConsole(realm, client, theme);
+ ResteasyProviderFactory.getInstance().injectProperties(console);
+ console.init();
+ return console;
+ }
+ }
+ }
+
+ private Theme getTheme(KeycloakSession session) {
+ try {
+ ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
+ return themeProvider.getTheme(session.getContext().getRealm().getAccountTheme(), Theme.Type.ACCOUNT);
+ } catch (IOException e) {
+ throw new InternalServerErrorException(e);
+ }
+ }
+
+ private boolean isDeprecatedFormsAccountConsole(Theme theme) {
+ try {
+ return Boolean.parseBoolean(theme.getProperties().getProperty("deprecatedMode", "true"));
+ } catch (IOException e) {
+ throw new InternalServerErrorException(e);
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
index a391c1d..377697a 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
@@ -20,6 +20,7 @@ package org.keycloak.services.resources.admin.info;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.IdentityProviderFactory;
import org.keycloak.broker.social.SocialIdentityProvider;
+import org.keycloak.common.Profile;
import org.keycloak.component.ComponentFactory;
import org.keycloak.events.EventType;
import org.keycloak.events.admin.OperationType;
@@ -169,6 +170,10 @@ public class ServerInfoAdminResource {
List<String> themeNames = new LinkedList<>(themeProvider.nameSet(type));
Collections.sort(themeNames);
+ if (!Profile.isFeatureEnabled(Profile.Feature.ACCOUNT2)) {
+ themeNames.remove("keycloak-preview");
+ }
+
List<ThemeInfoRepresentation> themes = new LinkedList<>();
info.getThemes().put(type.toString().toLowerCase(), themes);
diff --git a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
index 18a6fd9..d7f4967 100755
--- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
@@ -209,7 +209,7 @@ public class RealmsResource {
public Object getAccountService(final @PathParam("realm") String name) {
RealmModel realm = init(name);
EventBuilder event = new EventBuilder(realm, session, clientConnection);
- return AccountLoader.getAccountService(session, event);
+ return new AccountLoader().getAccountService(session, event);
}
@Path("{realm}")
diff --git a/themes/src/main/resources-community/META-INF/keycloak-themes.json b/themes/src/main/resources-community/META-INF/keycloak-themes.json
index 12711ea..71270a3 100755
--- a/themes/src/main/resources-community/META-INF/keycloak-themes.json
+++ b/themes/src/main/resources-community/META-INF/keycloak-themes.json
@@ -5,5 +5,8 @@
}, {
"name" : "keycloak",
"types": [ "admin", "account", "login", "common", "email", "welcome" ]
+ }, {
+ "name" : "keycloak-preview",
+ "types": [ "account" ]
}]
-}
\ No newline at end of file
+}