keycloak-aplcache

Merge pull request #4476 from stianst/ACCOUNT32 KEYCLOAK-1250

9/14/2017 4:55:27 PM

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
+}