/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package fi.csc.emrex.smp;
import fi.csc.emrex.smp.model.Person;
import fi.csc.emrex.smp.model.VerificationReply;
import fi.csc.emrex.smp.model.VerifiedReport;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.xml.bind.DatatypeConverter;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
*
* @author salum
*/
@Controller
@Slf4j
public class ThymeController {
@Autowired
private HttpServletRequest context;
@Autowired
private SignatureVerifier signatureVerifier;
@Value("${emreg.url}")
private String emregUrl;
@Value("${smp.return.url}")
private String returnUrl;
@RequestMapping(value = "/", method = RequestMethod.GET)
public String smp(HttpServletRequest request, Model model) throws Exception {
return smpsmp(request, model);
}
@RequestMapping(value = "/smp/", method = RequestMethod.GET)
public String smpsmp(HttpServletRequest request, Model model) throws Exception {
String firstName = request.getHeader("shib-givenName");
model.addAttribute("name", firstName);
return "smp";
}
private void printAttributes(HttpServletRequest request) {
final String requestURI = request.getRequestURI();
System.out.println("requestURI: " + requestURI);
final String requestURL = request.getRequestURL().toString();
System.out.println("requestURL: " + requestURL);
final Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
final String headerName = headerNames.nextElement();
System.out.println(headerName + ": " + request.getHeader(headerName));
}
}
@RequestMapping(value = "/abort", method= RequestMethod.GET)
public String abort() throws Exception {
return "onReturnAbort";
}
@RequestMapping(value = "/smp/abort", method = RequestMethod.GET)
public String smpAbort() throws Exception {
return abort();
}
@RequestMapping(value = "/smp/onReturn", method = RequestMethod.POST)
public String smponReturnelmo(@ModelAttribute ElmoData request, Model model, @CookieValue(value = "elmoSessionId") String sessionIdCookie, @CookieValue(value = "chosenNCP") String chosenNCP, HttpServletRequest httpRequest) throws Exception {
return this.onReturnelmo(request, model, sessionIdCookie, chosenNCP, httpRequest);
}
@RequestMapping(value = "/onReturn", method = RequestMethod.POST)
public String onReturnelmo(@ModelAttribute ElmoData request, Model model, @CookieValue(value = "elmoSessionId") String sessionIdCookie, @CookieValue(value = "chosenNCP") String chosenNCP, HttpServletRequest httpRequest) throws Exception {
String sessionId = request.getSessionId();
String elmo = request.getElmo();
if (elmo == null) {
return "onReturnAbort";
}
Person person = new Person();
person.setFirstName(httpRequest.getHeader("shib-cn"));
person.setLastName(httpRequest.getHeader("shib-sn"));
person.setGender(httpRequest.getHeader("shib-schacGender"));
person.setBirthDate(httpRequest.getHeader("shib-schacDateOfBirth"), "YYYYMMDD");
if(context.getSession().getAttribute("shibPerson")==null){
context.getSession().setAttribute("shibPerson", person);
}
final byte[] bytes = DatatypeConverter.parseBase64Binary(elmo);
final String decodedXml = GzipUtil.gzipDecompress(bytes);
// TODO charset problems UTF-8 vs UTF-16
final boolean verifySignatureResult = signatureVerifier.verifySignatureWithDecodedData(getCertificate(), decodedXml, StandardCharsets.UTF_8);
log.info("Verify signature result: {}", verifySignatureResult);
System.out.println("providedSessionId: " + sessionId);
String ncpPubKey = chosenNCP;
try {
FiSmpApplication.verifySessionId(sessionId, sessionIdCookie);
} catch (Exception e) {
System.out.println(e.getMessage());
model.addAttribute("error", "<p>Session verification failed</p>");
return "error";
}
try {
if (!FiSmpApplication.verifyElmoSignature(decodedXml, ncpPubKey)) {
model.addAttribute("error", "<p>NCP signature check failed</p>");
return "error";
}
} catch (Exception e) {
System.out.println(e.getMessage());
model.addAttribute("error", "<p>NCP verification failed</p>");
return "error";
}
context.getSession().setAttribute("elmoxmlstring", decodedXml);
model.addAttribute("elmoXml", decodedXml);
Document document;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
//Get the DOM Builder
DocumentBuilder builder;
if (person != null) {
List<VerifiedReport> results = new ArrayList<>();
try {
builder = factory.newDocumentBuilder();
StringReader sr = new StringReader(decodedXml);
final InputSource inputSource = new InputSource();
inputSource.setEncoding(StandardCharsets.UTF_8.name());
inputSource.setCharacterStream(sr);
//Load and Parse the XML document
//document contains the complete XML as a Tree.
document = builder.parse(inputSource);
NodeList reports = document.getElementsByTagName("report");
for (int i = 0; i < reports.getLength(); i++) {
VerifiedReport vr = new VerifiedReport();
Element report = (Element) reports.item(i);
vr.setReport(nodeToString(report));
Person elmoPerson = getUserFromElmoReport(report);
//Person shibPerson = (Person) context.getSession().getAttribute("shibPerson");
if (elmoPerson != null) {
VerificationReply verification = person.verifiy(elmoPerson);
System.out.println("VerScore: " + verification.getScore());
vr.setVerification(verification);
} else {
vr.addMessage("Elmo learner missing");
//todo fix this
}
results.add(vr);
}
context.getSession().setAttribute("reports", results);
model.addAttribute("reports", results);
} catch (ParserConfigurationException | IOException | SAXException ex) {
System.out.println(ex.getMessage());
Logger.getLogger(ThymeController.class.getName()).log(Level.SEVERE, null, ex);
model.addAttribute("error", ex.getMessage());
return "error";
}
} else {
model.addAttribute("error", "<p>HAKA login missing</p>");
return "error";
}
return "review";
}
// FIXME serti jostain muualta
private String getCertificate() {
return "-----BEGIN CERTIFICATE-----\n" +
"MIIB+TCCAWICCQDiZILVgSkjojANBgkqhkiG9w0BAQUFADBBMQswCQYDVQQGEwJG\n" +
"STERMA8GA1UECAwISGVsc2lua2kxETAPBgNVBAcMCEhlbHNpbmtpMQwwCgYDVQQK\n" +
"DANDU0MwHhcNMTUwMjA1MTEwNTI5WhcNMTgwNTIwMTEwNTI5WjBBMQswCQYDVQQG\n" +
"EwJGSTERMA8GA1UECAwISGVsc2lua2kxETAPBgNVBAcMCEhlbHNpbmtpMQwwCgYD\n" +
"VQQKDANDU0MwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMyVVTyGT1Cp8z1f\n" +
"jYEO93HEtIpFKnb/tvPb6Ee5b8m8lnuv6YWsF8DBWPVfsOq0KCWD8zE1yD+w+xxM\n" +
"mp6+zATp089PUrEUYawG/tGu9OG+EX+nhOAj0SBvGHEkXh6lGJgeGxbdFVwZePAN\n" +
"135ra5L3gYcwYBVOuEyYFZJp7diHAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAP2E9\n" +
"YD7djCum5UYn1Od9Z1w55j+SuKRWMnTR3yzy1PXJjb2dGqNcV9tEhdbqWbwTnNfl\n" +
"6sidCnd1U0p4XdLjg28me8ZmfftH+QU4LkwSFSyF4ajoTFC3QHD0xTtGpQIT/rAD\n" +
"x/59fhfX5icydMzzNulwXJWImtXq2/AX43/yR+M=\n" +
"-----END CERTIFICATE-----";
}
/**
* @Deprecated @RequestMapping(value = "/smp/review", method =
* RequestMethod.POST) public String smpRewiew(@ModelAttribute User user,
* Model model) { return this.rewiew(user, model); }
*
* @Deprecated
* @RequestMapping(value = "/review", method = RequestMethod.POST) public
* String rewiew(@ModelAttribute User user, Model model) {
*
* String elmoString = (String)
* context.getSession().getAttribute("elmoxmlstring");
* model.addAttribute("elmoXml", elmoString);
* System.out.println(elmoString); Person elmoPerson =
* getUserFromElmo(elmoString); Person shibPerson = (Person)
* context.getSession().getAttribute("shibPerson"); VerificationReply
* verification = shibPerson.verifiy(elmoPerson);
* System.out.println("VerScore: " + verification.getScore());
* model.addAttribute("verification", verification); return "review"; }
*/
private Person getUserFromElmoReport(Element report) {
Element learner = getOneNode(report, "learner");
if (learner != null) {
System.out.println("learner found");
Person elmoPerson = new Person();
elmoPerson.setFirstName(getOneNode(learner, "givenNames").getTextContent());
elmoPerson.setLastName(getOneNode(learner, "familyName").getTextContent());
Element bday = getOneNode(learner, "bday");
if (bday != null) {
elmoPerson.setBirthDate(bday.getTextContent(), bday.getAttribute("dtf"));
}
Element gender = getOneNode(learner, "gender");
if (gender != null) {
elmoPerson.setGender(gender.getTextContent());
}
return elmoPerson;
} else {
System.out.println("no learner found");
return null;
}
}
/*
private Person getPersonFromElmo(String xml) {
xml = xml.replaceAll("[\\n\\r]", "");
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
docFactory.setNamespaceAware(false);
DocumentBuilder docBuilder = null;
Document doc = null;
try {
docBuilder = docFactory.newDocumentBuilder();
doc = docBuilder.parse(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)));
} catch (Exception e) {
System.out.println("Failed to parse XML"+ e.getMessage());
throw new IllegalArgumentException("Failed to parse XML", e);
}
NodeList list = doc.getElementsByTagName("report");
if (list.getLength() == 0) {
throw new IllegalArgumentException("Failed to get report from XML.");
}
Node report = list.item(0);
Person p = new Person();
p.setBirthDate(getValueForTag(report, "learner/bday"));
p.setFamilyName(getValueForTag(report, "learner/familyName"));
p.setGivenNames(getValueForTag(report, "learner/givenNames"));
p.setGender("-"); // TODO: We need to expand ELMO to include Gender
return p;
}
*/
private Element getOneNode(Element node, String name) {
NodeList list = node.getElementsByTagName(name);
if (list.getLength() == 1) {
System.out.println("found " + name);
return (Element) list.item(0);
} else {
System.out.println("no " + name + "found");
return null;
}
}
private String getValueForTag(Node node, String exp) {
XPath xpath = XPathFactory.newInstance().newXPath();
try {
return xpath.evaluate(exp, node);
} catch (Exception e) {
System.out.println("XPATH error" + e);
return null;
}
}
private String nodeToString(Node node) {
StringWriter sw = new StringWriter();
try {
Transformer t = TransformerFactory.newInstance().newTransformer();
t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
t.setOutputProperty(OutputKeys.INDENT, "no");
t.transform(new DOMSource(node), new StreamResult(sw));
} catch (TransformerException te) {
System.out.println("nodeToString Transformer Exception");
}
return sw.toString();
}
}