Add FreeMarkerProvider to prevent multiple instances of FreeMarker templates (#14062)

* Add FreeMarkerProvider to prevent multiple instances of FreeMarker templates

Closes #19185
This commit is contained in:
Stian Thorgersen 2022-08-29 13:42:53 +02:00 committed by GitHub
parent 090f7f89d5
commit aeba5e9f4b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 139 additions and 73 deletions

View file

@ -30,8 +30,8 @@ import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.ApplianceBootstrap; import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.util.CacheControlUtil; import org.keycloak.services.util.CacheControlUtil;
import org.keycloak.services.util.CookieHelper; import org.keycloak.services.util.CookieHelper;
import org.keycloak.theme.FreeMarkerUtil;
import org.keycloak.theme.Theme; import org.keycloak.theme.Theme;
import org.keycloak.theme.freemarker.FreeMarkerProvider;
import org.keycloak.urls.UrlType; import org.keycloak.urls.UrlType;
import org.keycloak.utils.MediaType; import org.keycloak.utils.MediaType;
@ -204,7 +204,7 @@ public class QuarkusWelcomeResource {
if (errorMessage != null) { if (errorMessage != null) {
map.put("errorMessage", errorMessage); map.put("errorMessage", errorMessage);
} }
FreeMarkerUtil freeMarkerUtil = new FreeMarkerUtil(); FreeMarkerProvider freeMarkerUtil = session.getProvider(FreeMarkerProvider.class);
String result = freeMarkerUtil.processTemplate(map, "index.ftl", theme); String result = freeMarkerUtil.processTemplate(map, "index.ftl", theme);
ResponseBuilder rb = Response.status(errorMessage == null ? Status.OK : Status.BAD_REQUEST) ResponseBuilder rb = Response.status(errorMessage == null ? Status.OK : Status.BAD_REQUEST)

View file

@ -43,10 +43,10 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.sessions.AuthenticationSessionModel; import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.theme.FreeMarkerException; import org.keycloak.theme.FreeMarkerException;
import org.keycloak.theme.FreeMarkerUtil;
import org.keycloak.theme.Theme; import org.keycloak.theme.Theme;
import org.keycloak.theme.beans.LinkExpirationFormatterMethod; import org.keycloak.theme.beans.LinkExpirationFormatterMethod;
import org.keycloak.theme.beans.MessageFormatterMethod; import org.keycloak.theme.beans.MessageFormatterMethod;
import org.keycloak.theme.freemarker.FreeMarkerProvider;
import org.keycloak.utils.StringUtil; import org.keycloak.utils.StringUtil;
/** /**
@ -60,14 +60,14 @@ public class FreeMarkerEmailTemplateProvider implements EmailTemplateProvider {
* etc.)! * etc.)!
*/ */
protected AuthenticationSessionModel authenticationSession; protected AuthenticationSessionModel authenticationSession;
protected FreeMarkerUtil freeMarker; protected FreeMarkerProvider freeMarker;
protected RealmModel realm; protected RealmModel realm;
protected UserModel user; protected UserModel user;
protected final Map<String, Object> attributes = new HashMap<>(); protected final Map<String, Object> attributes = new HashMap<>();
public FreeMarkerEmailTemplateProvider(KeycloakSession session, FreeMarkerUtil freeMarker) { public FreeMarkerEmailTemplateProvider(KeycloakSession session) {
this.session = session; this.session = session;
this.freeMarker = freeMarker; this.freeMarker = session.getProvider(FreeMarkerProvider.class);
} }
@Override @Override

View file

@ -22,23 +22,19 @@ import org.keycloak.email.EmailTemplateProvider;
import org.keycloak.email.EmailTemplateProviderFactory; import org.keycloak.email.EmailTemplateProviderFactory;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.theme.FreeMarkerUtil;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
public class FreeMarkerEmailTemplateProviderFactory implements EmailTemplateProviderFactory { public class FreeMarkerEmailTemplateProviderFactory implements EmailTemplateProviderFactory {
private FreeMarkerUtil freeMarker;
@Override @Override
public EmailTemplateProvider create(KeycloakSession session) { public EmailTemplateProvider create(KeycloakSession session) {
return new FreeMarkerEmailTemplateProvider(session, freeMarker); return new FreeMarkerEmailTemplateProvider(session);
} }
@Override @Override
public void init(Config.Scope config) { public void init(Config.Scope config) {
freeMarker = new FreeMarkerUtil();
} }
@Override @Override
@ -47,7 +43,6 @@ public class FreeMarkerEmailTemplateProviderFactory implements EmailTemplateProv
@Override @Override
public void close() { public void close() {
freeMarker = null;
} }
@Override @Override

View file

@ -39,7 +39,6 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.FormMessage; import org.keycloak.models.utils.FormMessage;
import org.keycloak.services.util.CacheControlUtil; import org.keycloak.services.util.CacheControlUtil;
import org.keycloak.theme.FreeMarkerException; import org.keycloak.theme.FreeMarkerException;
import org.keycloak.theme.FreeMarkerUtil;
import org.keycloak.theme.Theme; import org.keycloak.theme.Theme;
import org.keycloak.theme.beans.AdvancedMessageFormatterMethod; import org.keycloak.theme.beans.AdvancedMessageFormatterMethod;
import org.keycloak.theme.beans.LocaleBean; import org.keycloak.theme.beans.LocaleBean;
@ -47,6 +46,7 @@ import org.keycloak.theme.beans.MessageBean;
import org.keycloak.theme.beans.MessageFormatterMethod; import org.keycloak.theme.beans.MessageFormatterMethod;
import org.keycloak.theme.beans.MessageType; import org.keycloak.theme.beans.MessageType;
import org.keycloak.theme.beans.MessagesPerFieldBean; import org.keycloak.theme.beans.MessagesPerFieldBean;
import org.keycloak.theme.freemarker.FreeMarkerProvider;
import org.keycloak.utils.MediaType; import org.keycloak.utils.MediaType;
import org.keycloak.utils.StringUtil; import org.keycloak.utils.StringUtil;
@ -87,7 +87,7 @@ public class FreeMarkerAccountProvider implements AccountProvider {
protected boolean passwordUpdateSupported; protected boolean passwordUpdateSupported;
protected boolean passwordSet; protected boolean passwordSet;
protected KeycloakSession session; protected KeycloakSession session;
protected FreeMarkerUtil freeMarker; protected FreeMarkerProvider freeMarker;
protected HttpHeaders headers; protected HttpHeaders headers;
protected Map<String, Object> attributes; protected Map<String, Object> attributes;
@ -97,9 +97,9 @@ public class FreeMarkerAccountProvider implements AccountProvider {
protected MessageType messageType = MessageType.ERROR; protected MessageType messageType = MessageType.ERROR;
private boolean authorizationSupported; private boolean authorizationSupported;
public FreeMarkerAccountProvider(KeycloakSession session, FreeMarkerUtil freeMarker) { public FreeMarkerAccountProvider(KeycloakSession session) {
this.session = session; this.session = session;
this.freeMarker = freeMarker; this.freeMarker = session.getProvider(FreeMarkerProvider.class);
} }
public AccountProvider setUriInfo(UriInfo uriInfo) { public AccountProvider setUriInfo(UriInfo uriInfo) {

View file

@ -22,23 +22,19 @@ import org.keycloak.forms.account.AccountProvider;
import org.keycloak.forms.account.AccountProviderFactory; import org.keycloak.forms.account.AccountProviderFactory;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.theme.FreeMarkerUtil;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
public class FreeMarkerAccountProviderFactory implements AccountProviderFactory { public class FreeMarkerAccountProviderFactory implements AccountProviderFactory {
private FreeMarkerUtil freeMarker;
@Override @Override
public AccountProvider create(KeycloakSession session) { public AccountProvider create(KeycloakSession session) {
return new FreeMarkerAccountProvider(session, freeMarker); return new FreeMarkerAccountProvider(session);
} }
@Override @Override
public void init(Config.Scope config) { public void init(Config.Scope config) {
freeMarker = new FreeMarkerUtil();
} }
@Override @Override
@ -47,7 +43,6 @@ public class FreeMarkerAccountProviderFactory implements AccountProviderFactory
} }
@Override @Override
public void close() { public void close() {
freeMarker = null;
} }
@Override @Override

View file

@ -81,7 +81,6 @@ import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.LoginActionsService; import org.keycloak.services.resources.LoginActionsService;
import org.keycloak.sessions.AuthenticationSessionModel; import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.theme.FreeMarkerException; import org.keycloak.theme.FreeMarkerException;
import org.keycloak.theme.FreeMarkerUtil;
import org.keycloak.theme.Theme; import org.keycloak.theme.Theme;
import org.keycloak.theme.beans.AdvancedMessageFormatterMethod; import org.keycloak.theme.beans.AdvancedMessageFormatterMethod;
import org.keycloak.theme.beans.LocaleBean; import org.keycloak.theme.beans.LocaleBean;
@ -89,6 +88,7 @@ import org.keycloak.theme.beans.MessageBean;
import org.keycloak.theme.beans.MessageFormatterMethod; import org.keycloak.theme.beans.MessageFormatterMethod;
import org.keycloak.theme.beans.MessageType; import org.keycloak.theme.beans.MessageType;
import org.keycloak.theme.beans.MessagesPerFieldBean; import org.keycloak.theme.beans.MessagesPerFieldBean;
import org.keycloak.theme.freemarker.FreeMarkerProvider;
import org.keycloak.userprofile.UserProfileContext; import org.keycloak.userprofile.UserProfileContext;
import org.keycloak.userprofile.UserProfileProvider; import org.keycloak.userprofile.UserProfileProvider;
import org.keycloak.utils.MediaType; import org.keycloak.utils.MediaType;
@ -121,15 +121,15 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
protected ClientModel client; protected ClientModel client;
protected UriInfo uriInfo; protected UriInfo uriInfo;
protected FreeMarkerUtil freeMarker; protected FreeMarkerProvider freeMarker;
protected UserModel user; protected UserModel user;
protected final Map<String, Object> attributes = new HashMap<>(); protected final Map<String, Object> attributes = new HashMap<>();
public FreeMarkerLoginFormsProvider(KeycloakSession session, FreeMarkerUtil freeMarker) { public FreeMarkerLoginFormsProvider(KeycloakSession session) {
this.session = session; this.session = session;
this.freeMarker = freeMarker; this.freeMarker = session.getProvider(FreeMarkerProvider.class);
this.attributes.put("scripts", new LinkedList<>()); this.attributes.put("scripts", new LinkedList<>());
this.realm = session.getContext().getRealm(); this.realm = session.getContext().getRealm();
this.client = session.getContext().getClient(); this.client = session.getContext().getClient();

View file

@ -22,23 +22,19 @@ import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.forms.login.LoginFormsProviderFactory; import org.keycloak.forms.login.LoginFormsProviderFactory;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.theme.FreeMarkerUtil;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
public class FreeMarkerLoginFormsProviderFactory implements LoginFormsProviderFactory { public class FreeMarkerLoginFormsProviderFactory implements LoginFormsProviderFactory {
private FreeMarkerUtil freeMarker;
@Override @Override
public LoginFormsProvider create(KeycloakSession session) { public LoginFormsProvider create(KeycloakSession session) {
return new FreeMarkerLoginFormsProvider(session, freeMarker); return new FreeMarkerLoginFormsProvider(session);
} }
@Override @Override
public void init(Config.Scope config) { public void init(Config.Scope config) {
freeMarker = new FreeMarkerUtil();
} }
@Override @Override
@ -47,7 +43,6 @@ public class FreeMarkerLoginFormsProviderFactory implements LoginFormsProviderFa
} }
@Override @Override
public void close() { public void close() {
freeMarker = null;
} }
@Override @Override

View file

@ -14,12 +14,12 @@ import org.keycloak.models.RealmModel;
import org.keycloak.representations.idm.OAuth2ErrorRepresentation; import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.messages.Messages; import org.keycloak.services.messages.Messages;
import org.keycloak.theme.FreeMarkerUtil;
import org.keycloak.theme.Theme; import org.keycloak.theme.Theme;
import org.keycloak.theme.beans.LocaleBean; import org.keycloak.theme.beans.LocaleBean;
import org.keycloak.theme.beans.MessageBean; import org.keycloak.theme.beans.MessageBean;
import org.keycloak.theme.beans.MessageFormatterMethod; import org.keycloak.theme.beans.MessageFormatterMethod;
import org.keycloak.theme.beans.MessageType; import org.keycloak.theme.beans.MessageType;
import org.keycloak.theme.freemarker.FreeMarkerProvider;
import org.keycloak.utils.MediaType; import org.keycloak.utils.MediaType;
import org.keycloak.utils.MediaTypeMatcher; import org.keycloak.utils.MediaTypeMatcher;
@ -87,7 +87,7 @@ public class KeycloakErrorHandler implements ExceptionMapper<Throwable> {
Locale locale = session.getContext().resolveLocale(null); Locale locale = session.getContext().resolveLocale(null);
FreeMarkerUtil freeMarker = new FreeMarkerUtil(); FreeMarkerProvider freeMarker = session.getProvider(FreeMarkerProvider.class);
Map<String, Object> attributes = initAttributes(session, realm, theme, locale, statusCode); Map<String, Object> attributes = initAttributes(session, realm, theme, locale, statusCode);
String templateName = "error.ftl"; String templateName = "error.ftl";

View file

@ -28,8 +28,8 @@ import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.ApplianceBootstrap; import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.util.CacheControlUtil; import org.keycloak.services.util.CacheControlUtil;
import org.keycloak.services.util.CookieHelper; import org.keycloak.services.util.CookieHelper;
import org.keycloak.theme.FreeMarkerUtil;
import org.keycloak.theme.Theme; import org.keycloak.theme.Theme;
import org.keycloak.theme.freemarker.FreeMarkerProvider;
import org.keycloak.urls.UrlType; import org.keycloak.urls.UrlType;
import org.keycloak.utils.MediaType; import org.keycloak.utils.MediaType;
@ -203,7 +203,7 @@ public class WelcomeResource {
if (errorMessage != null) { if (errorMessage != null) {
map.put("errorMessage", errorMessage); map.put("errorMessage", errorMessage);
} }
FreeMarkerUtil freeMarkerUtil = new FreeMarkerUtil(); FreeMarkerProvider freeMarkerUtil = session.getProvider(FreeMarkerProvider.class);
String result = freeMarkerUtil.processTemplate(map, "index.ftl", theme); String result = freeMarkerUtil.processTemplate(map, "index.ftl", theme);
ResponseBuilder rb = Response.status(errorMessage == null ? Status.OK : Status.BAD_REQUEST) ResponseBuilder rb = Response.status(errorMessage == null ? Status.OK : Status.BAD_REQUEST)

View file

@ -41,9 +41,9 @@ import org.keycloak.services.resources.RealmsResource;
import org.keycloak.services.util.ResolveRelative; import org.keycloak.services.util.ResolveRelative;
import org.keycloak.services.validation.Validation; import org.keycloak.services.validation.Validation;
import org.keycloak.theme.FreeMarkerException; import org.keycloak.theme.FreeMarkerException;
import org.keycloak.theme.FreeMarkerUtil;
import org.keycloak.theme.Theme; import org.keycloak.theme.Theme;
import org.keycloak.theme.beans.MessageFormatterMethod; import org.keycloak.theme.beans.MessageFormatterMethod;
import org.keycloak.theme.freemarker.FreeMarkerProvider;
import org.keycloak.urls.UrlType; import org.keycloak.urls.UrlType;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import org.keycloak.utils.MediaType; import org.keycloak.utils.MediaType;
@ -151,7 +151,7 @@ public class AccountConsole {
RequiredActionProviderModel updateEmailActionProvider = realm.getRequiredActionProviderByAlias(UserModel.RequiredAction.UPDATE_EMAIL.name()); RequiredActionProviderModel updateEmailActionProvider = realm.getRequiredActionProviderByAlias(UserModel.RequiredAction.UPDATE_EMAIL.name());
map.put("updateEmailActionEnabled", updateEmailActionProvider != null && updateEmailActionProvider.isEnabled()); map.put("updateEmailActionEnabled", updateEmailActionProvider != null && updateEmailActionProvider.isEnabled());
FreeMarkerUtil freeMarkerUtil = new FreeMarkerUtil(); FreeMarkerProvider freeMarkerUtil = session.getProvider(FreeMarkerProvider.class);
String result = freeMarkerUtil.processTemplate(map, "index.ftl", theme); 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); Response.ResponseBuilder builder = Response.status(Response.Status.OK).type(MediaType.TEXT_HTML_UTF_8).language(Locale.ENGLISH).entity(result);
return builder.build(); return builder.build();

View file

@ -42,8 +42,8 @@ import org.keycloak.services.managers.ClientManager;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.Cors; import org.keycloak.services.resources.Cors;
import org.keycloak.theme.FreeMarkerException; import org.keycloak.theme.FreeMarkerException;
import org.keycloak.theme.FreeMarkerUtil;
import org.keycloak.theme.Theme; import org.keycloak.theme.Theme;
import org.keycloak.theme.freemarker.FreeMarkerProvider;
import org.keycloak.urls.UrlType; import org.keycloak.urls.UrlType;
import org.keycloak.utils.MediaType; import org.keycloak.utils.MediaType;
@ -345,7 +345,7 @@ public class AdminConsole {
map.put("loginRealm", realm.getName()); map.put("loginRealm", realm.getName());
map.put("properties", theme.getProperties()); map.put("properties", theme.getProperties());
FreeMarkerUtil freeMarkerUtil = new FreeMarkerUtil(); FreeMarkerProvider freeMarkerUtil = session.getProvider(FreeMarkerProvider.class);
String result = freeMarkerUtil.processTemplate(map, "index.ftl", theme); 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); Response.ResponseBuilder builder = Response.status(Response.Status.OK).type(MediaType.TEXT_HTML_UTF_8).language(Locale.ENGLISH).entity(result);

View file

@ -1,27 +1,12 @@
/* package org.keycloak.theme.freemarker;
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.theme;
import freemarker.cache.URLTemplateLoader; import freemarker.cache.URLTemplateLoader;
import freemarker.core.HTMLOutputFormat; import freemarker.core.HTMLOutputFormat;
import freemarker.template.Configuration; import freemarker.template.Configuration;
import freemarker.template.Template; import freemarker.template.Template;
import org.keycloak.Config; import org.keycloak.theme.FreeMarkerException;
import org.keycloak.theme.KeycloakSanitizerMethod;
import org.keycloak.theme.Theme;
import java.io.IOException; import java.io.IOException;
import java.io.StringWriter; import java.io.StringWriter;
@ -30,29 +15,25 @@ import java.net.URL;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
/** public class DefaultFreeMarkerProvider implements FreeMarkerProvider {
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> private final ConcurrentHashMap<String, Template> cache;
*/ private final KeycloakSanitizerMethod kcSanitizeMethod;
public class FreeMarkerUtil {
private ConcurrentHashMap<String, Template> cache; public DefaultFreeMarkerProvider(ConcurrentHashMap<String, Template> cache, KeycloakSanitizerMethod kcSanitizeMethod) {
private final KeycloakSanitizerMethod kcSanitizeMethod = new KeycloakSanitizerMethod(); this.cache = cache;
this.kcSanitizeMethod = kcSanitizeMethod;
public FreeMarkerUtil() {
if (Config.scope("theme").getBoolean("cacheTemplates", true)) {
cache = new ConcurrentHashMap<>();
}
} }
@Override
public String processTemplate(Object data, String templateName, Theme theme) throws FreeMarkerException { public String processTemplate(Object data, String templateName, Theme theme) throws FreeMarkerException {
if (data instanceof Map) { if (data instanceof Map) {
((Map)data).put("kcSanitize", kcSanitizeMethod); ((Map)data).put("kcSanitize", kcSanitizeMethod);
} }
try { try {
Template template; Template template;
if (cache != null) { if (cache != null) {
String key = theme.getName() + "/" + templateName; String key = theme.getType().toString().toLowerCase() + "/" + theme.getName() + "/" + templateName;
template = cache.get(key); template = cache.get(key);
if (template == null) { if (template == null) {
template = getTemplate(templateName, theme); template = getTemplate(templateName, theme);
@ -80,7 +61,7 @@ public class FreeMarkerUtil {
if (templateName.toLowerCase().endsWith(".ftl")) { if (templateName.toLowerCase().endsWith(".ftl")) {
cfg.setOutputFormat(HTMLOutputFormat.INSTANCE); cfg.setOutputFormat(HTMLOutputFormat.INSTANCE);
} }
cfg.setTemplateLoader(new ThemeTemplateLoader(theme)); cfg.setTemplateLoader(new ThemeTemplateLoader(theme));
return cfg.getTemplate(templateName, "UTF-8"); return cfg.getTemplate(templateName, "UTF-8");
} }
@ -104,4 +85,8 @@ public class FreeMarkerUtil {
} }
@Override
public void close() {
}
} }

View file

@ -0,0 +1,50 @@
package org.keycloak.theme.freemarker;
import freemarker.template.Template;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.theme.KeycloakSanitizerMethod;
import java.util.concurrent.ConcurrentHashMap;
public class DefaultFreeMarkerProviderFactory implements FreeMarkerProviderFactory {
private DefaultFreeMarkerProvider provider;
private ConcurrentHashMap<String, Template> cache;
private KeycloakSanitizerMethod kcSanitizeMethod;
@Override
public DefaultFreeMarkerProvider create(KeycloakSession session) {
if (provider == null) {
synchronized (this) {
if (provider == null) {
if (Config.scope("theme").getBoolean("cacheTemplates", true)) {
cache = new ConcurrentHashMap<>();
}
kcSanitizeMethod = new KeycloakSanitizerMethod();
provider = new DefaultFreeMarkerProvider(cache, kcSanitizeMethod);
}
}
}
return provider;
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return "default";
}
}

View file

@ -0,0 +1,11 @@
package org.keycloak.theme.freemarker;
import org.keycloak.provider.Provider;
import org.keycloak.theme.FreeMarkerException;
import org.keycloak.theme.Theme;
public interface FreeMarkerProvider extends Provider {
public String processTemplate(Object data, String templateName, Theme theme) throws FreeMarkerException;
}

View file

@ -0,0 +1,6 @@
package org.keycloak.theme.freemarker;
import org.keycloak.provider.ProviderFactory;
public interface FreeMarkerProviderFactory extends ProviderFactory<FreeMarkerProvider> {
}

View file

@ -0,0 +1,27 @@
package org.keycloak.theme.freemarker;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
public class FreeMarkerSPI implements Spi {
@Override
public boolean isInternal() {
return true;
}
@Override
public String getName() {
return "freemarker";
}
@Override
public Class<? extends Provider> getProviderClass() {
return FreeMarkerProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return FreeMarkerProviderFactory.class;
}
}

View file

@ -29,3 +29,4 @@ org.keycloak.protocol.oidc.grants.ciba.resolvers.CIBALoginUserResolverSpi
org.keycloak.protocol.oidc.rar.AuthorizationRequestParserSpi org.keycloak.protocol.oidc.rar.AuthorizationRequestParserSpi
org.keycloak.services.resources.admin.ext.AdminRealmResourceSpi org.keycloak.services.resources.admin.ext.AdminRealmResourceSpi
org.keycloak.services.legacysessionsupport.LegacySessionSupportSpi org.keycloak.services.legacysessionsupport.LegacySessionSupportSpi
org.keycloak.theme.freemarker.FreeMarkerSPI

View file

@ -0,0 +1 @@
org.keycloak.theme.freemarker.DefaultFreeMarkerProviderFactory