/*
* Copyright 2018 A-SIT Plus GmbH
* AT-specific eIDAS Connector has been developed in a cooperation between EGIZ,
* A-SIT Plus GmbH, A-SIT, and Graz University of Technology.
*
* Licensed under the EUPL, Version 1.2 or - as soon they will be approved by
* the European Commission - subsequent versions of the EUPL (the "License");
* You may not use this work except in compliance with the License.
* You may obtain a copy of the License at:
* https://joinup.ec.europa.eu/news/understanding-eupl-v12
*
* 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.
*
* This product combines work with different licenses. See the "NOTICE" text
* file for details on the various modules and licenses.
* The "NOTICE" text file is part of the distribution. Any derivative works
* that you distribute must include a readable copy of the "NOTICE" text file.
*/
package at.asitplus.eidas.specific.connector.controller;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.transform.TransformerFactoryConfigurationError;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import at.asitplus.eidas.specific.connector.MsEidasNodeConstants;
import at.gv.egiz.eaaf.core.api.data.EaafConstants;
import at.gv.egiz.eaaf.core.api.idp.IConfigurationWithSP;
import at.gv.egiz.eaaf.core.api.storage.ITransactionStorage;
import at.gv.egiz.eaaf.core.exceptions.EaafException;
import at.gv.egiz.eaaf.core.impl.utils.DomUtils;
import at.gv.egiz.eaaf.core.impl.utils.Random;
import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvpMetadataBuilderConfiguration;
import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvpMetadataConfigurationFactory;
import at.gv.egiz.eaaf.modules.pvp2.impl.builder.PvpMetadataBuilder;
import at.gv.egiz.eaaf.modules.pvp2.impl.utils.AbstractCredentialProvider;
@Controller
public class MonitoringController {
private static final Logger log = LoggerFactory.getLogger(MonitoringController.class);
private static final String MESSAGE_OK = "OK";
private static final String MESSAGE_ERROR = "ERROR";
private static final String MESSAGE_SKIPPED = "SKIPPED";
private static final String TEST_STORAGE = "Storage: ";
private static final String TEST_CONFIG = "Config: ";
private static final String TEST_PVPMETADATA = "PVP_metadata: ";
private static final String TEST_EIDASNODEMETADATA = "eIDASNode_metadata: ";
@Autowired
private ITransactionStorage storage;
@Autowired
private IConfigurationWithSP config;
@Autowired
private PvpMetadataBuilder metadatabuilder;
@Autowired
private IPvpMetadataConfigurationFactory configFactory;
private AbstractCredentialProvider pvpIdpCredentials;
/**
* Sets a specific credential provider for PVP S-Profile IDP component.
*
* @param pvpIdpCredentials credential provider
*/
public void setPvpIdpCredentials(AbstractCredentialProvider pvpIdpCredentials) {
this.pvpIdpCredentials = pvpIdpCredentials;
}
/**
* Generic exception handling that wrote an error-message to html response.
*
* @param resp Http response object
* @param exception Error
* @throws IOException In case of a html response error.
*/
@ExceptionHandler({ Throwable.class })
public void genericExceptionHandler(HttpServletResponse resp, Exception exception) throws IOException {
log.error("Monitoring Servlet receives an error.", exception);
resp.setContentType(EaafConstants.CONTENTTYPE_HTML_UTF8);
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
resp.getWriter().write("Reason: "
+ StringEscapeUtils.escapeHtml4(StringEscapeUtils.escapeEcmaScript(exception.getMessage())));
}
/**
* MS-Connector status-monitoring end-point.
*
* @param req http request
* @param resp http response
* @throws IOException In case of a general processing error
*/
@RequestMapping(value = { MsEidasNodeConstants.ENDPOINT_MONITORING_MONITOR },
method = { RequestMethod.GET })
public void startFullTest(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType(EaafConstants.CONTENTTYPE_HTML_UTF8);
try {
testConfig();
testStorage();
testPvpMetadata();
testEidasNodeMetadata();
resp.setStatus(HttpServletResponse.SC_OK);
resp.getWriter().write(MESSAGE_OK);
} catch (final Exception e) {
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
resp.getWriter().write(MESSAGE_ERROR);
}
}
/**
* MS-Connector internal verify monitoring end-point.
*
* @param req http request object
* @param resp http response object
* @throws IOException In case of an internal processing error
*/
@RequestMapping(value = { MsEidasNodeConstants.ENDPOINT_MONITORING_VERIFY },
method = { RequestMethod.GET })
public void startSingleTests(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String result = StringUtils.EMPTY;
try {
result += testConfig() + "
";
} catch (final Exception e) {
result += e.getMessage() + "
";
}
try {
result += testStorage() + "
";
} catch (final Exception e) {
result += e.getMessage() + "
";
}
try {
result += testPvpMetadata() + "
";
} catch (final Exception e) {
result += e.getMessage() + "
";
}
try {
result += testEidasNodeMetadata() + "
";
} catch (final Exception e) {
result += e.getMessage() + "
";
}
resp.setContentType(EaafConstants.CONTENTTYPE_HTML_UTF8);
resp.setStatus(HttpServletResponse.SC_OK);
resp.getWriter().write(result);
}
private String testStorage() throws Exception {
try {
final String key = Random.nextHexRandom16();
final String value = Random.nextHexRandom16();
storage.put(key, value, -1);
final String result = storage.get(key, String.class);
storage.remove(key);
if (result != null && result.equals(value)) {
return TEST_STORAGE + MESSAGE_OK;
} else {
log.warn("Montioring: TestValue: " + value + " does NOT match in Storage test");
}
} catch (final EaafException e) {
log.warn("Montioring: Can not read/write to storage.", e);
}
throw new Exception(TEST_STORAGE + MESSAGE_ERROR);
}
private String testConfig() throws Exception {
try {
if (config.getBasicConfigurationWithPrefix(MsEidasNodeConstants.PROP_CONFIG_SP_LIST_PREFIX) != null
&& config.getBasicConfigurationWithPrefix(MsEidasNodeConstants.PROP_CONFIG_SP_LIST_PREFIX)
.size() > 0) {
return TEST_CONFIG + MESSAGE_OK;
} else {
log.warn("Montioring: Can not read from configuration file.");
}
} catch (final Exception e) {
log.warn("Montioring: Can not read from configuration file.", e);
}
throw new Exception(TEST_CONFIG + MESSAGE_ERROR);
}
private String testPvpMetadata() throws Exception {
try {
// build metadata
final IPvpMetadataBuilderConfiguration metadataConfig =
configFactory.generateMetadataBuilderConfiguration(
"http://localhost/monitoring",
pvpIdpCredentials);
metadatabuilder.buildPvpMetadata(metadataConfig);
return TEST_PVPMETADATA + MESSAGE_OK;
} catch (Exception | TransformerFactoryConfigurationError e) {
log.warn("Monitoring: Has an error in '" + TEST_PVPMETADATA + "': " + e.getMessage(), e);
throw new Exception(TEST_PVPMETADATA + MESSAGE_ERROR, e);
}
}
private String testEidasNodeMetadata() throws Exception {
try {
final String urlString = config.getBasicConfiguration(
MsEidasNodeConstants.PROP_CONFIG_MONITORING_EIDASNODE_METADATAURL);
if (StringUtils.isEmpty(urlString)) {
log.debug("No eIDASNode metadata URL. Skipping test ... ");
return TEST_EIDASNODEMETADATA + MESSAGE_SKIPPED;
}
// create HTTP client
// TODO: update if we switch to openSAML3
final HttpClient httpClient = new HttpClient();
// set parameters
final HttpClientParams params = new HttpClientParams();
params.setSoTimeout(5 * 1000);
httpClient.setParams(params);
// request URL
final HttpMethod method = new GetMethod(urlString);
final int respCode = httpClient.executeMethod(method);
if (respCode != 200) {
log.warn("Monitoring: Has an error in '" + TEST_EIDASNODEMETADATA + "': " + " HTTP responsecode: "
+ respCode);
throw new Exception(TEST_EIDASNODEMETADATA + MESSAGE_ERROR);
}
// parse metadata
DomUtils.parseXmlNonValidating(method.getResponseBodyAsStream());
return TEST_EIDASNODEMETADATA + MESSAGE_OK;
} catch (Exception | TransformerFactoryConfigurationError e) {
log.warn("Monitoring: Has an error in '" + TEST_EIDASNODEMETADATA + "': " + e.getMessage(), e);
throw new Exception(TEST_EIDASNODEMETADATA + MESSAGE_ERROR, e);
}
}
}