package org.apereo.cas.services.web; import org.apereo.cas.authentication.principal.WebApplicationService; import org.apereo.cas.services.RegisteredService; import org.apereo.cas.services.RegisteredServiceAccessStrategyUtils; import org.apereo.cas.services.ServicesManager; import org.apereo.cas.web.support.ArgumentExtractor; import org.apereo.cas.web.support.WebUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.template.TemplateLocation; import org.springframework.http.HttpStatus; import org.springframework.util.StringUtils; import org.springframework.web.servlet.View; import org.springframework.webflow.execution.RequestContext; import org.springframework.webflow.execution.RequestContextHolder; import org.thymeleaf.spring4.view.AbstractThymeleafView; import org.thymeleaf.spring4.view.ThymeleafViewResolver; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Collections; import java.util.List; import java.util.Locale; /** * {@link RegisteredServiceThemeBasedViewResolver} is an alternate Spring View Resolver that utilizes a service's * associated theme to selectively choose which set of UI views will be used to generate * the standard views. * * @author John Gasper * @author Misagh Moayyed * @since 4.1.0 */ public class RegisteredServiceThemeBasedViewResolver extends ThymeleafViewResolver { private static final Logger LOGGER = LoggerFactory.getLogger(RegisteredServiceThemeBasedViewResolver.class); private final ServicesManager servicesManager; private final ArgumentExtractor argumentExtractor; private final String prefix; private final String suffix; public RegisteredServiceThemeBasedViewResolver(final ServicesManager servicesManager, final ArgumentExtractor argumentExtractor, final String prefix, final String suffix) { this.servicesManager = servicesManager; this.argumentExtractor = argumentExtractor; this.prefix = prefix; this.suffix = suffix; } @Override protected View loadView(final String viewName, final Locale locale) throws Exception { final View view = super.loadView(viewName, locale); final RequestContext requestContext = RequestContextHolder.getRequestContext(); final WebApplicationService service; final HttpServletResponse response; final List<ArgumentExtractor> argumentExtractorList = Collections.singletonList(this.argumentExtractor); if (requestContext != null) { response = WebUtils.getHttpServletResponse(requestContext); service = WebUtils.getService(argumentExtractorList, requestContext); } else { final HttpServletRequest request = WebUtils.getHttpServletRequestFromRequestAttributes(); service = WebUtils.getService(argumentExtractorList, request); response = WebUtils.getHttpServletResponseFromRequestAttributes(); } if (service == null) { return view; } final RegisteredService registeredService = this.servicesManager.findServiceBy(service); if (registeredService != null) { try { RegisteredServiceAccessStrategyUtils.ensureServiceAccessIsAllowed(service, registeredService); } catch (final Exception e) { response.setStatus(HttpStatus.UNAUTHORIZED.value()); } } if (registeredService != null && StringUtils.hasText(registeredService.getTheme()) && view instanceof AbstractThymeleafView) { LOGGER.debug("Attempting to locate views for service [{}] with theme [{}]", registeredService.getServiceId(), registeredService.getTheme()); final AbstractThymeleafView thymeleafView = (AbstractThymeleafView) view; final String viewUrl = registeredService.getTheme() + '/' + thymeleafView.getTemplateName(); final String viewLocationUrl = prefix.concat(viewUrl).concat(suffix); LOGGER.debug("Attempting to locate view at [{}]", viewLocationUrl); final TemplateLocation location = new TemplateLocation(viewLocationUrl); if (location.exists(getApplicationContext())) { LOGGER.debug("Found view [{}]", viewUrl); thymeleafView.setTemplateName(viewUrl); } else { LOGGER.debug("View [{}] does not exist. Falling back to default view at [{}]", viewLocationUrl, thymeleafView.getTemplateName()); } } return view; } }