Java tutorial
/** * The MIT License * Copyright (c) 2015 Population Register Centre * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package fi.vm.kapa.identification.adapter.service; import fi.vm.kapa.identification.adapter.utils.AuthenticationHandlerUtils; import fi.vm.kapa.identification.service.TupasStampIdService; import org.apache.commons.codec.digest.DigestUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import javax.annotation.PostConstruct; import javax.ws.rs.core.MultivaluedMap; import java.io.UnsupportedEncodingException; import java.time.Clock; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; import java.util.stream.Collectors; @Service public class SessionParserService { private static final Logger logger = LoggerFactory.getLogger(SessionParserService.class); public static final int STAMP_VALIDITY_SECONDS = 300; public static final String CREATED_TS = "created"; private static final long ATTRIBUTE_EXPIRY_TIME_MS = 5000; private final Clock clock = Clock.systemUTC(); private Map<String, String> tupasProperties = new HashMap<>(); private String tupasFormTemplate; private Map<String, String> declRefs = new HashMap<>(); private TupasStampIdService tupasStampIdService; private String redirectUrlBase; private AdapterPropertyMapper propertyMapper; private AuthenticationHandlerUtils authenticationHandlerUtils; private static final String HETU_REGEXP = "^[0-9]{6}[+-Aa][0-9]{3}[A-Za-z0-9]$"; private static final Pattern HETU_PATTERN = Pattern.compile(HETU_REGEXP); @Autowired public SessionParserService(@Value("${redirect.url.base}") String redirectUrlBase, AdapterPropertyMapper propertyMapper, AuthenticationHandlerUtils authenticationHandlerUtils) { this.redirectUrlBase = redirectUrlBase; this.propertyMapper = propertyMapper; this.authenticationHandlerUtils = authenticationHandlerUtils; this.tupasStampIdService = TupasStampIdService.getInstance(); } @PostConstruct public void initSessionParser() { tupasProperties = propertyMapper.getTupasProperties(); tupasFormTemplate = propertyMapper.getTupasFormTemplate(); Set<Map.Entry<String, String>> tupasEntries = tupasProperties.entrySet().stream() .filter(entry -> entry.getKey().startsWith("BANK_URL_TFI_")).collect(Collectors.toSet()); tupasEntries .forEach(entry -> declRefs.put(entry.getValue(), entry.getKey().replaceFirst("BANK_URL_TFI_", ""))); } public String createInitResult(MultivaluedMap<String, String> requestParams) { String template; String declRef = requestParams.getFirst("declRef"); String selectionId = declRefs.get(declRef); String token = authenticationHandlerUtils.createToken(); String timeStampStr = String.valueOf(this.clock.millis()); String tupasCounterStr = tupasStampIdService.getTupasStampCounter(timeStampStr.length()); // replace any existing requestParams.putSingle("ADPT_TSTAMP", String.valueOf(timeStampStr) + tupasCounterStr); authenticationHandlerUtils.insertIntoCache(token, requestParams); logger.debug("Creating result string for IdP selection '{}'", selectionId); String actionUrl = tupasProperties.get("BANK_URL_" + selectionId); String version = tupasProperties.get("BANK_VERSION_" + selectionId); String rcvId = tupasProperties.get("BANK_RCVID_" + selectionId); String lang = requestParams.getFirst("lang").toUpperCase(); String timestamp = requestParams.getFirst("ADPT_TSTAMP"); String idType = tupasProperties.get("TUPAS_IDTYPE"); String returnLink = propertyMapper.getTupasReturnLinkBase() + token; String cancelLink = propertyMapper.getTupasCancelLink() + token; String rejectLink = propertyMapper.getTupasRejectLink(); String keyVersion = tupasProperties.get("BANK_KEYVERS_" + selectionId); String tupasMac = createTupasRequestMac(version, rcvId, lang, timestamp, idType, returnLink, cancelLink, rejectLink, keyVersion, tupasProperties.get("BANK_SECRET_" + selectionId)); logger.info("Start Tupas identification for token={} ADPT_TSTAMP={}", token, requestParams.getFirst("ADPT_TSTAMP")); template = tupasFormTemplate.replace("TUPAS_ACTION_URL", actionUrl).replace("TUPAS_VERSION", version) .replace("TUPAS_RCVID", rcvId).replace("ADPT_LANG", lang).replace("ADPT_TSTAMP", timestamp) .replace("TUPAS_IDTYPE", idType).replace("ADPT_RETURN", returnLink) .replace("ADPT_CANCEL", cancelLink).replace("ADPT_REJECT", rejectLink) .replace("TUPAS_KEYVERS", keyVersion).replace("TUPAS_MAC", tupasMac); return template; } public String parseSessionData(final MultivaluedMap<String, String> sessionData) { String redirectUrl = null; String tokenId = sessionData.getFirst("token"); MultivaluedMap<String, String> requestParams = authenticationHandlerUtils.popFromCache(tokenId); if (!hasHetu(sessionData)) { String custId = sessionData.getFirst("B02K_CUSTID"); logger.warn("Tupas identification did not return HETU, aborting. token={}, B02K_CUSTID={}", tokenId, custId); return null; } if (!CollectionUtils.isEmpty(requestParams)) { if (!isIdentifierPreserved(sessionData, requestParams)) { logger.warn("Tampered response received " + sessionData); return null; } if (!isIdentifierValid(sessionData, STAMP_VALIDITY_SECONDS)) { logger.warn("Expired response received " + sessionData); return null; } String declRef = requestParams.getFirst("declRef"); String selectionId = declRefs.get(declRef); String calculatedMac = createTupasVerificationMac(sessionData.getFirst("B02K_VERS"), sessionData.getFirst("B02K_TIMESTMP"), sessionData.getFirst("B02K_IDNBR"), sessionData.getFirst("B02K_STAMP"), sessionData.getFirst("B02K_CUSTNAME"), sessionData.getFirst("B02K_KEYVERS"), sessionData.getFirst("B02K_ALG"), sessionData.getFirst("B02K_CUSTID"), sessionData.getFirst("B02K_CUSTTYPE"), tupasProperties.get("BANK_SECRET_" + selectionId)); if (calculatedMac.equals(sessionData.getFirst("B02K_MAC"))) { Map<String, String> attributes = new HashMap<>(); attributes.put(propertyMapper.getSessionAttributeMap().get("B02K_CUSTID"), sessionData.getFirst("B02K_CUSTID")); attributes.put(propertyMapper.getSessionAttributeMap().get("B02K_CUSTNAME"), sessionData.getFirst("B02K_CUSTNAME")); attributes.put(CREATED_TS, Long.toString(clock.millis())); authenticationHandlerUtils.addToAttributesCache(tokenId, attributes); logger.info("Accepting Tupas identification for token={} B02K_STAMP={}", tokenId, sessionData.getFirst("B02K_STAMP")); redirectUrl = redirectUrlBase + "?token=" + tokenId + "&ckey=" + requestParams.getFirst("ckey"); } else { logger.warn("Tupas MAC calculation failed, values don't match"); } } logger.debug("Generated redirect URL: {}", redirectUrl); return redirectUrl; } private boolean isIdentifierValid(MultivaluedMap<String, String> sessionData, long validitySeconds) { String stamp = sessionData.getFirst("B02K_STAMP"); if (null != stamp && stamp.length() == 20) { try { long creationTimeSec = Long.parseLong(stamp.substring(0, 13)) / 1000; long nowSec = this.clock.millis() / 1000; if (nowSec < (creationTimeSec + validitySeconds)) { return true; } } catch (NumberFormatException e) { logger.warn("Unexpected B02K_STAMP in session data " + sessionData); return false; } } return false; } private boolean isIdentifierPreserved(MultivaluedMap<String, String> sessionData, MultivaluedMap<String, String> requestParams) { return null != sessionData.getFirst("B02K_STAMP") && sessionData.getFirst("B02K_STAMP").equals(requestParams.getFirst("ADPT_TSTAMP")); } boolean hasHetu(MultivaluedMap<String, String> sessionData) { if (null != sessionData) { if ("01".equals(sessionData.getFirst("B02K_CUSTTYPE")) && looksLikeHetu(sessionData.getFirst("B02K_CUSTID"))) { return true; } else if ("08".equals(sessionData.getFirst("B02K_CUSTTYPE")) && looksLikeHetu(sessionData.getFirst("B02K_CUSTID"))) { logger.warn("B02K_CUSTTYPE=08: test user in SPankki and landsbanken"); return true; } else if ("08".equals(sessionData.getFirst("B02K_CUSTTYPE"))) { logger.warn("B02K_CUSTTYPE=08: business user not supported"); return false; } } return false; } boolean looksLikeHetu(String value) { if (null == value) { return false; } else { return HETU_PATTERN.matcher(value).matches(); } } public Map<String, String> getSessionAttributes(final MultivaluedMap<String, String> attrsParams) { String token = attrsParams.getFirst("token"); Map<String, String> attributes = authenticationHandlerUtils.popFromAttributesCache(token); if (null == attributes) { logger.info("No attributes for token={}", token); return null; } else { if (areAttributesValid(attributes)) { Map<String, String> values = new HashMap<>(); for (Map.Entry<String, String> entry : attributes.entrySet()) { if (!entry.getKey().equals(CREATED_TS)) { values.put(entry.getKey(), entry.getValue()); } } logger.info("Attributes retrieved, token={}, attributes={}", token, values); return values; } else { logger.info("Discarding expired attributes for token={}, attributes={}request={}", token, attributes, attrsParams); return null; } } } private boolean areAttributesValid(Map<String, String> attributes) { if (null != attributes && null != attributes.get(CREATED_TS)) { Long created = Long.parseLong(attributes.get(CREATED_TS)); long now = clock.millis(); if (now - created <= ATTRIBUTE_EXPIRY_TIME_MS) { return true; } else { logger.info("Expired attributes found, time={} created={}, timeout={}", now, created, ATTRIBUTE_EXPIRY_TIME_MS); return false; } } return false; } public String purgeSession(final MultivaluedMap<String, String> attrsParams) { MultivaluedMap<String, String> retval = authenticationHandlerUtils .popFromCache(attrsParams.getFirst("token")); if (null != retval) { String redirurl = propertyMapper.getIdp_authn_cancelLink() + "&ckey=" + retval.getFirst("ckey") + "&token=" + attrsParams.getFirst("token"); logger.debug("purge session redir url: " + redirurl); return redirurl; } return null; } String createTupasRequestMac(String version, String rcvId, String lang, String timestamp, String idType, String returnLink, String cancelLink, String rejectLink, String keyVersion, String sharedSecret) { String tupasString = "701&" + version + "&" + rcvId + "&" + lang + "&" + timestamp + "&" + idType + "&" + returnLink + "&" + cancelLink + "&" + rejectLink + "&" + keyVersion + "&03&" + sharedSecret + "&"; logger.debug("Generated Tupas request MAC string:\n{}", tupasString); try { return DigestUtils.sha256Hex(tupasString.getBytes("ISO-8859-1")).toUpperCase(); } catch (UnsupportedEncodingException e) { logger.error("Unable to calculate tupas request MAC", e); return null; } } String createTupasVerificationMac(String version, String timestamp, String idNbr, String origStamp, String custName, String keyVers, String algorithm, String custId, String custType, String sharedSecret) { String tupasString = version + "&" + timestamp + "&" + idNbr + "&" + origStamp + "&" + custName + "&" + keyVers + "&" + algorithm + "&" + custId + "&" + custType + "&" + sharedSecret + "&"; logger.debug("Generated Tupas response MAC string:\n{}", tupasString); try { return DigestUtils.sha256Hex(tupasString.getBytes("ISO-8859-1")).toUpperCase(); } catch (UnsupportedEncodingException e) { logger.error("Unable to calculate tupas response verification MAC", e); return null; } } }