package org.apereo.cas.web.report; import org.apache.commons.lang3.tuple.Pair; import org.apereo.cas.configuration.CasConfigurationProperties; import org.apereo.cas.configuration.CasConfigurationPropertiesEnvironmentManager; import org.apereo.cas.support.events.config.CasConfigurationModifiedEvent; import org.apereo.cas.util.RegexUtils; import org.apereo.cas.web.report.util.ControllerUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint; import org.springframework.cloud.endpoint.RefreshEndpoint; import org.springframework.context.ApplicationEventPublisher; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.TreeMap; import java.util.regex.Pattern; /** * Controller that exposes the CAS internal state and beans * as JSON. The report is available at {@code /status/config}. * * @author Misagh Moayyed * @since 4.1 */ public class ConfigurationStateController extends BaseCasMvcEndpoint { private static final String VIEW_CONFIG = "monitoring/viewConfig"; @Autowired private RefreshEndpoint refreshEndpoint; @Autowired private EnvironmentEndpoint environmentEndpoint; @Autowired private ApplicationEventPublisher eventPublisher; @Autowired private ConfigurableEnvironment environment; @Autowired(required = false) @Qualifier("configurationPropertiesEnvironmentManager") private CasConfigurationPropertiesEnvironmentManager configurationPropertiesEnvironmentManager; private CasConfigurationProperties casProperties; public ConfigurationStateController(final CasConfigurationProperties casProperties) { super("configstate", "/config", casProperties.getMonitor().getEndpoints().getConfigurationState(), casProperties); this.casProperties = casProperties; } /** * Handle request. * * @param request the request * @param response the response * @return the model and view * @throws Exception the exception */ @GetMapping public ModelAndView handleRequestInternal(final HttpServletRequest request, final HttpServletResponse response) throws Exception { ensureEndpointAccessIsAuthorized(request, response); final Map<String, Object> model = new HashMap<>(); final String path = request.getContextPath(); ControllerUtils.configureModelMapForConfigServerCloudBusEndpoints(path, model); model.put("enableRefresh", isRefreshEnabled()); model.put("enableUpdate", isUpdateEnabled()); return new ModelAndView(VIEW_CONFIG, model); } private Boolean isRefreshEnabled() { return !casProperties.getEvents().isTrackConfigurationModifications() && refreshEndpoint.isEnabled() && environment.getProperty("spring.cloud.config.enabled", Boolean.class); } private Boolean isUpdateEnabled() { return !environment.getProperty("spring.cloud.config.enabled", Boolean.class); } /** * Gets configuration. * * @param request the request * @param response the response * @return the configuration */ @GetMapping("/getConfiguration") @ResponseBody public Map getConfiguration(final HttpServletRequest request, final HttpServletResponse response) { ensureEndpointAccessIsAuthorized(request, response); final Pattern pattern = RegexUtils.createPattern("(configService:|applicationConfig:).+(application|cas).+"); if (environmentEndpoint.isEnabled()) { final Map results = new TreeMap(); final Map<String, Object> environmentSettings = environmentEndpoint.invoke(); environmentSettings.entrySet() .stream() .filter(entry -> pattern.matcher(entry.getKey()).matches()) .forEach(entry -> { final Map<String, Object> keys = (Map<String, Object>) entry.getValue(); keys.keySet().forEach(key -> { if (!results.containsKey(key)) { final String propHolder = String.format("${%s}", key); final String value = this.environment.resolvePlaceholders(propHolder); results.put(key, environmentEndpoint.sanitize(key, value)); } }); }); return results; } return new LinkedHashMap(); } /** * Update configuration map. * * @param jsonInput the json input * @param request the request * @param response the response */ @PostMapping("/updateConfiguration") @ResponseBody public void updateConfiguration(@RequestBody final Map<String, Map<String, String>> jsonInput, final HttpServletRequest request, final HttpServletResponse response) { ensureEndpointAccessIsAuthorized(request, response); if (isUpdateEnabled()) { final Map<String, String> newData = jsonInput.get("new"); configurationPropertiesEnvironmentManager.savePropertyForStandaloneProfile(Pair.of(newData.get("key"), newData.get("value"))); eventPublisher.publishEvent(new CasConfigurationModifiedEvent(this, !casProperties.getEvents().isTrackConfigurationModifications())); } } }