/* * Copyright 2017 Graz University of Technology EAAF-Core Components has been developed in a * cooperation between EGIZ, A-SIT Plus, 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 "Licence"); You may not use this work except in * compliance with the Licence. You may obtain a copy of the Licence at: * https://joinup.ec.europa.eu/news/understanding-eupl-v12 * * Unless required by applicable law or agreed to in writing, software distributed under the Licence * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the Licence for the specific language governing permissions and limitations under * the Licence. * * 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.gv.egiz.eaaf.core.impl.idp.controller.protocols; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.UUID; import javax.annotation.Nonnull; import javax.servlet.http.HttpServletRequest; import at.gv.egiz.eaaf.core.api.IRequest; import at.gv.egiz.eaaf.core.api.data.EaafConstants; import at.gv.egiz.eaaf.core.api.idp.IConfiguration; import at.gv.egiz.eaaf.core.api.idp.IConfigurationWithSP; import at.gv.egiz.eaaf.core.api.idp.ISpConfiguration; import at.gv.egiz.eaaf.core.exceptions.EaafAuthenticationException; import at.gv.egiz.eaaf.core.exceptions.EaafException; import at.gv.egiz.eaaf.core.exceptions.EaafStorageException; import at.gv.egiz.eaaf.core.impl.http.HttpUtils; import at.gv.egiz.eaaf.core.impl.idp.auth.data.AuthProcessDataWrapper; import at.gv.egiz.eaaf.core.impl.utils.TransactionIdUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.util.Assert; public abstract class RequestImpl implements IRequest, Serializable { private static final Logger log = LoggerFactory.getLogger(RequestImpl.class); public static final String DATAID_REQUESTER_IP_ADDRESS = "reqestImpl_requesterIPAddr"; private static final String ERROR_CODE_INTERNAL_00 = "eaaf.core.00"; private static final long serialVersionUID = 1L; private String module = null; private String action = null; private String pendingRequestId = null; private String processInstanceId; private String internalSsoSessionId; private String uniqueTransactionIdentifer; private String uniqueSessionIdentifer; private String uniquePiiTransactionIdentifier; private String requestedServiceProviderIdentifer; private String idpAuthUrl = null; private ISpConfiguration spConfiguration = null; private boolean passiv = false; private boolean force = false; private boolean isAbortedByUser = false; // every request needs authentication by default private boolean needAuthentication = true; // every request is not authenticated by default private boolean isAuthenticated = false; // every request needs no SSO by default private boolean needSso = false; private boolean needUserConsent = false; private final Map genericDataStorage = new HashMap<>(); /** * Initialize this pendingRequest object. * * @param req {@link HttpServletRequest} * @param authConfig {@link IConfiguration} * @throws EaafException * */ public final void initialize(final HttpServletRequest req, final IConfigurationWithSP authConfig) throws EaafException { initialize(req, authConfig, null, null); } /** * Initialize this pendingRequest object. * * @param req {@link HttpServletRequest} * @param authConfig {@link IConfiguration} * @param transactionId Unique ID for technical log correlation that should be used in this pendingRequest * @throws EaafException * */ public final void initialize(final HttpServletRequest req, final IConfigurationWithSP authConfig, @Nullable final String transactionId) throws EaafException { initialize(req, authConfig, transactionId, null); } /** * Initialize this pendingRequest object. * * @param req {@link HttpServletRequest} * @param authConfig {@link IConfiguration} * @param transactionId Unique ID for technical log correlation that should be used in this pendingRequest * @param piiTransactionId Unique ID for PII data correlation that should be used in this pendingRequest * for logging. If 'null' a new one will be generated * * @throws EaafException * */ public final void initialize(@NonNull final HttpServletRequest req, @NonNull final IConfigurationWithSP authConfig, @Nullable final String transactionId, @Nullable final String piiTransactionId) throws EaafException { // use external transactionId or create new one if empty or null if (StringUtils.isNotEmpty(transactionId)) { uniqueTransactionIdentifer = transactionId; } else { uniqueTransactionIdentifer = UUID.randomUUID().toString(); } // set unique transaction identifier for logging TransactionIdUtils.setTransactionId(uniqueTransactionIdentifer); // use external piiTransactionId or create new one if empty or null if (StringUtils.isNotEmpty(piiTransactionId)) { uniquePiiTransactionIdentifier = piiTransactionId; } else { uniquePiiTransactionIdentifier = UUID.randomUUID().toString(); } // initialize session object genericDataStorage.put(EaafConstants.AUTH_DATA_CREATED, new Date()); // genericDataStorage.put(EAAFConstants.VALUE_SESSIONID, // Random.nextLongRandom()); // check if End-Point is valid final String authUrlString = HttpUtils.extractAuthUrlFromRequest(req); URL authReqUrl; try { authReqUrl = new URL(authUrlString); } catch (final MalformedURLException e) { log.error("IDP AuthenticationServiceURL Prefix is not a valid URL." + authUrlString, e); throw new EaafAuthenticationException(ERROR_CODE_INTERNAL_00, new Object[] { authUrlString }, e); } this.idpAuthUrl = authConfig.validateIdpUrl(authReqUrl); if (this.idpAuthUrl == null) { log.warn( "Extract AuthenticationServiceURL: " + authReqUrl + " is NOT found in configuration."); throw new EaafAuthenticationException(ERROR_CODE_INTERNAL_00, new Object[] { authUrlString }); } // set unique session identifier final String uniqueID = (String) req.getAttribute(EaafConstants.UNIQUESESSIONIDENTIFIER); if (StringUtils.isNotEmpty(uniqueID)) { this.uniqueSessionIdentifer = uniqueID; } else { log.debug("Create new sessionIdentifier for this pendingRequest ... "); this.uniqueSessionIdentifer = UUID.randomUUID().toString(); } // set requester's IP address try { setRawDataToTransaction(DATAID_REQUESTER_IP_ADDRESS, req.getRemoteAddr()); } catch (final EaafStorageException e) { log.info("Can NOT store remote IP address into 'pendingRequest'.", e); } } public final void setSpEntityId(final String spIdentifier) { this.requestedServiceProviderIdentifer = spIdentifier; } @Override public final String getSpEntityId() { return this.requestedServiceProviderIdentifer; } @Override public final boolean isPassiv() { return passiv; } @Override public final boolean forceAuth() { return force; } public final void setPassiv(final boolean passiv) { this.passiv = passiv; } public final void setForce(final boolean force) { this.force = force; } @Override public final String requestedAction() { return action; } public final void setAction(final String action) { this.action = action; } @Override public final String requestedModule() { return module; } public final void setModule(final String module) { this.module = module; } public final void setPendingRequestId(final String pendingReqId) { this.pendingRequestId = pendingReqId; } @Override @NonNull public final String getPendingRequestId() { if (pendingRequestId == null) { throw new IllegalStateException("No PendingRequestId set!!!"); } return pendingRequestId; } @Override public final String getInternalSsoSessionIdentifier() { return this.internalSsoSessionId; } @Override public final void setInternalSsoSessionIdentifier(final String internalSsoSessionId) { this.internalSsoSessionId = internalSsoSessionId; } @Override @Nonnull public final T getSessionData(@Nonnull final Class wrapper) { Assert.notNull(wrapper, "Wrapper must NOT null"); if (AuthProcessDataWrapper.class.isAssignableFrom(wrapper)) { try { return wrapper.getConstructor(Map.class).newInstance(this.genericDataStorage); } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { log.error("Can NOT instance wrapper: " + wrapper.getName(), e); } } log.error("Can NOT wrap generic data into session data. " + "Reason: Wrapper " + wrapper.getName() + " is NOT a valid wrapper"); throw new RuntimeException("Can NOT wrap generic data into session data. " + "Reason: Wrapper " + wrapper.getName() + " is NOT a valid wrapper"); } @Override public final ISpConfiguration getServiceProviderConfiguration() { return this.spConfiguration; } @Override public T getServiceProviderConfiguration(final Class decorator) { if (this.spConfiguration != null) { if (decorator.isAssignableFrom(this.spConfiguration.getClass())) { return (T) this.spConfiguration; } else { log.error("Can not decorate SP configuration by '" + decorator.getName() + "'."); } throw new RuntimeException( "Can not decorate SP configuration by '" + decorator.getName() + "'."); } return null; } public void setOnlineApplicationConfiguration(final ISpConfiguration spConfig) { this.spConfiguration = spConfig; } @Override public final String getUniqueTransactionIdentifier() { return this.uniqueTransactionIdentifer; } @Override public final String getUniqueSessionIdentifier() { return this.uniqueSessionIdentifer; } @Override public final String getUniquePiiTransactionIdentifier() { return uniquePiiTransactionIdentifier; } @Override public final String getProcessInstanceId() { return this.processInstanceId; } /** * Set an unique transaction identifier to correlate technical logging * in one single transaction. * * @param id Unique identifier */ public final void setUniqueTransactionIdentifier(final String id) { this.uniqueTransactionIdentifer = id; } /** * Set an unique session identifier to correlate technical logging over a set of transactions, * like SSO as one example. * * @param id Unique identifier */ public final void setUniqueSessionIdentifier(final String id) { this.uniqueSessionIdentifer = id; } /** * Set an unique transaction identifier to correlate PII related data. * *

This identifier will be not used for technical logging.

* * @param id Unique identifier */ public void setUniquePiiTransactionIdentifier(String id) { this.uniquePiiTransactionIdentifier = id; } public void setProcessInstanceId(final String id) { this.processInstanceId = id; } @Override public final String getAuthUrl() { return this.idpAuthUrl; } @Override public final String getAuthUrlWithOutSlash() { if (this.idpAuthUrl.endsWith("/")) { return this.idpAuthUrl.substring(0, this.idpAuthUrl.length() - 1); } else { return this.idpAuthUrl; } } @Override public final boolean isNeedAuthentication() { return needAuthentication; } public final void setNeedAuthentication(final boolean needAuthentication) { this.needAuthentication = needAuthentication; } @Override public final boolean isAuthenticated() { return isAuthenticated; } @Override public final void setAuthenticated(final boolean isAuthenticated) { this.isAuthenticated = isAuthenticated; } @Override public final boolean needSingleSignOnFunctionality() { return needSso; } @Override public final void setNeedSingleSignOnFunctionality(final boolean needSso) { this.needSso = needSso; } @Override public final boolean isNeedUserConsent() { return this.needUserConsent; } @Override public final void setNeedUserConsent(final boolean needConsent) { this.needUserConsent = needConsent; } @Override public final boolean isAbortedByUser() { return this.isAbortedByUser; } @Override public final void setAbortedByUser(final boolean isAborted) { this.isAbortedByUser = isAborted; } @Override public final Object getRawData(final String key) { if (StringUtils.isNotEmpty(key)) { return genericDataStorage.get(key); } log.info("Can not load generic request-data with key='null'"); return null; } @Override public final T getRawData(final String key, final Class clazz) { if (StringUtils.isNotEmpty(key)) { final Object data = genericDataStorage.get(key); if (data == null) { return null; } try { @SuppressWarnings("unchecked") final T test = (T) data; return test; } catch (final Exception e) { log.warn("Generic request-data object can not be casted to requested type", e); return null; } } log.info("Can not load generic request-data with key='null'"); return null; } @Override public final void setRawDataToTransaction(final String key, final Object object) throws EaafStorageException { if (StringUtils.isEmpty(key)) { log.info("Generic request-data can not be stored with a 'null' key"); throw new EaafStorageException("Generic request-data can not be stored with a 'null' key", null); } if (object != null && !Serializable.class.isInstance(object)) { log.warn( "Generic request-data can only store objects which implements the 'Seralizable' interface"); throw new EaafStorageException( "Generic request-data can only store objects which implements the 'Seralizable' interface", null); } if (genericDataStorage.containsKey(key)) { log.trace("Overwrite generic request-data with key:" + key); } else { log.trace("Add generic request-data with key:" + key + " to session."); } genericDataStorage.put(key, object); } @Override public final void setRawDataToTransaction(final Map map) throws EaafStorageException { if (map == null) { log.info("Generic request-data can not be stored with a 'null' map"); throw new EaafStorageException("Generic request-data can not be stored with a 'null' map", null); } // validate and store values for (final Entry el : map.entrySet()) { setRawDataToTransaction(el.getKey(), el.getValue()); } } }