From a4eeb110b076672b3bb88f5e2f3420ae70c78f38 Mon Sep 17 00:00:00 2001 From: Yuli Shlosberg Date: Mon, 7 Jan 2019 16:23:36 +0200 Subject: Add restriction filter to onboarding Change-Id: Ief36760c8d89ac3443c8b12bfdef09c2f83abfc3 Issue-ID: SDC-2039 Signed-off-by: Yuli Shlosberg --- .../backend/openecomp-sdc-security-util/pom.xml | 93 ++++++++++ .../sdc/securityutil/AuthenticationCookie.java | 97 +++++++++++ .../securityutil/AuthenticationCookieUtils.java | 107 ++++++++++++ .../org/openecomp/sdc/securityutil/CipherUtil.java | 123 +++++++++++++ .../sdc/securityutil/CipherUtilException.java | 66 +++++++ .../ISessionValidationCookieConfiguration.java | 12 ++ .../ISessionValidationFilterConfiguration.java | 13 ++ .../org/openecomp/sdc/securityutil/Passwords.java | 187 ++++++++++++++++++++ .../sdc/securityutil/RepresentationUtils.java | 53 ++++++ .../openecomp/sdc/securityutil/SecurityUtil.java | 134 ++++++++++++++ .../filters/FilterServletOutputStream.java | 38 ++++ .../sdc/securityutil/filters/ResponceWrapper.java | 53 ++++++ .../sdc/securityutil/filters/SampleFilter.java | 132 ++++++++++++++ .../filters/SessionValidationFilter.java | 194 +++++++++++++++++++++ .../src/test/AuthenticationCookieUtilsTest.java | 54 ++++++ .../src/test/CipherUtilTest.java | 56 ++++++ .../src/test/PasswordsTest.java | 77 ++++++++ .../src/test/RepresentationUtilsTest.java | 34 ++++ .../src/test/SecurityUtilTest.java | 30 ++++ .../src/test/SessionValidationFilterTest.java | 157 +++++++++++++++++ 20 files changed, 1710 insertions(+) create mode 100644 openecomp-be/backend/openecomp-sdc-security-util/pom.xml create mode 100644 openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/AuthenticationCookie.java create mode 100644 openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/AuthenticationCookieUtils.java create mode 100644 openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/CipherUtil.java create mode 100644 openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/CipherUtilException.java create mode 100644 openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/ISessionValidationCookieConfiguration.java create mode 100644 openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/ISessionValidationFilterConfiguration.java create mode 100644 openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/Passwords.java create mode 100644 openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/RepresentationUtils.java create mode 100644 openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/SecurityUtil.java create mode 100644 openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/filters/FilterServletOutputStream.java create mode 100644 openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/filters/ResponceWrapper.java create mode 100644 openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/filters/SampleFilter.java create mode 100644 openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/filters/SessionValidationFilter.java create mode 100644 openecomp-be/backend/openecomp-sdc-security-util/src/test/AuthenticationCookieUtilsTest.java create mode 100644 openecomp-be/backend/openecomp-sdc-security-util/src/test/CipherUtilTest.java create mode 100644 openecomp-be/backend/openecomp-sdc-security-util/src/test/PasswordsTest.java create mode 100644 openecomp-be/backend/openecomp-sdc-security-util/src/test/RepresentationUtilsTest.java create mode 100644 openecomp-be/backend/openecomp-sdc-security-util/src/test/SecurityUtilTest.java create mode 100644 openecomp-be/backend/openecomp-sdc-security-util/src/test/SessionValidationFilterTest.java (limited to 'openecomp-be/backend/openecomp-sdc-security-util') diff --git a/openecomp-be/backend/openecomp-sdc-security-util/pom.xml b/openecomp-be/backend/openecomp-sdc-security-util/pom.xml new file mode 100644 index 0000000000..4bff04f9a5 --- /dev/null +++ b/openecomp-be/backend/openecomp-sdc-security-util/pom.xml @@ -0,0 +1,93 @@ + + + + backend + org.openecomp.sdc + 1.4.0-SNAPSHOT + + 4.0.0 + + openecomp-sdc-security-util + + + + + org.slf4j + slf4j-api + 1.7.25 + + + + junit + junit + 4.12 + test + + + + org.apache.commons + commons-crypto + 1.0.0 + + + + commons-lang + commons-lang + 2.6 + + + + org.functionaljava + functionaljava + 4.7 + + + + com.fasterxml.jackson.core + jackson-databind + 2.8.10 + + + + commons-codec + commons-codec + 1.11 + + + + javax.servlet + javax.servlet-api + 3.1.0 + + + + org.mockito + mockito-core + 2.8.9 + test + + + + org.powermock + powermock-module-junit4 + 1.7.4 + test + + + junit + junit + + + + + + org.powermock + powermock-api-mockito2 + 1.7.4 + test + + + + \ No newline at end of file diff --git a/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/AuthenticationCookie.java b/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/AuthenticationCookie.java new file mode 100644 index 0000000000..c064f3e08f --- /dev/null +++ b/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/AuthenticationCookie.java @@ -0,0 +1,97 @@ +package org.openecomp.sdc.securityutil; + +import java.util.Set; + + +public class AuthenticationCookie { + + private String userID; + private Set roles; + private long maxSessionTime; + private long currentSessionTime; + + public AuthenticationCookie(){ } + + public AuthenticationCookie(AuthenticationCookie authenticationCookie){ + this.userID = authenticationCookie.userID; + this.roles = authenticationCookie.roles; + this.maxSessionTime = authenticationCookie.maxSessionTime; + this.currentSessionTime = authenticationCookie.currentSessionTime; + } + + /** + * Create new cookie with max_session_time and current_session_time started with same value + * @param userId + */ + public AuthenticationCookie(String userId) { + this.userID =userId; + long currentTimeMilliSec = System.currentTimeMillis(); + this.maxSessionTime = currentTimeMilliSec; + this.currentSessionTime = currentTimeMilliSec; + } + + public String getUserID() { + return userID; + } + + public void setUserID(String userID) { + this.userID = userID; + } + + public Set getRoles() { + return roles; + } + + public void setRoles(Set roles) { + this.roles = roles; + } + + public long getMaxSessionTime() { + return maxSessionTime; + } + + public void setMaxSessionTime(long maxSessionTime) { + this.maxSessionTime = maxSessionTime; + } + + + public long getCurrentSessionTime() { + return currentSessionTime; + } + + public void setCurrentSessionTime(long currentSessionTime) { + this.currentSessionTime = currentSessionTime; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof AuthenticationCookie)) return false; + + AuthenticationCookie that = (AuthenticationCookie) o; + + if (getMaxSessionTime() != that.getMaxSessionTime()) return false; + if (getCurrentSessionTime() != that.getCurrentSessionTime()) return false; + if (getUserID() != null ? !getUserID().equals(that.getUserID()) : that.getUserID() != null) return false; + return getRoles() != null ? getRoles().containsAll(that.getRoles()) : that.getRoles() == null; + } + + @Override + public int hashCode() { + int result = getUserID() != null ? getUserID().hashCode() : 0; + result = 31 * result + (getRoles() != null ? getRoles().hashCode() : 0); + result = 31 * result + (int) (getMaxSessionTime() ^ (getMaxSessionTime() >>> 32)); + result = 31 * result + (int) (getCurrentSessionTime() ^ (getCurrentSessionTime() >>> 32)); + return result; + } + + @Override + public String toString() { + return "AuthenticationCookie{" + + "userID='" + userID + '\'' + + ", roles=" + roles + + ", maxSessionTime=" + maxSessionTime + + ", currentSessionTime=" + currentSessionTime + + '}'; + } +} diff --git a/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/AuthenticationCookieUtils.java b/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/AuthenticationCookieUtils.java new file mode 100644 index 0000000000..24225a86f9 --- /dev/null +++ b/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/AuthenticationCookieUtils.java @@ -0,0 +1,107 @@ +package org.openecomp.sdc.securityutil; + +import org.openecomp.sdc.securityutil.filters.SessionValidationFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.Cookie; +import java.io.IOException; + +public class AuthenticationCookieUtils { + + private static final Logger log = LoggerFactory.getLogger(SessionValidationFilter.class.getName()); + + /** + * Update given cookie session time value to current time + * + * @param cookie + * @param filterConfiguration + * @return + * @throws CipherUtilException + * @throws IOException + */ + public static Cookie updateSessionTime(Cookie cookie, ISessionValidationFilterConfiguration filterConfiguration) throws CipherUtilException, IOException { + AuthenticationCookie authenticationCookie = getAuthenticationCookie(cookie, filterConfiguration); + long newTime = System.currentTimeMillis(); + log.debug("SessionValidationFilter: Going to set new session time in cookie, old value: {}, new value: {}", authenticationCookie.getCurrentSessionTime(), newTime); + authenticationCookie.setCurrentSessionTime(newTime); + String encryptedCookie = getEncryptedCookie(authenticationCookie, filterConfiguration); + return createUpdatedCookie(cookie, encryptedCookie, filterConfiguration); + } + + /** + * Create new Cookie object with same attributes as original cookie + * @param cookie + * @param encryptedCookie + * @param cookieConfiguration + * @return + */ + public static Cookie createUpdatedCookie(Cookie cookie, String encryptedCookie, ISessionValidationCookieConfiguration cookieConfiguration) { + Cookie updatedCookie = new Cookie(cookie.getName(), encryptedCookie ); + updatedCookie.setPath(cookieConfiguration.getCookiePath()); + updatedCookie.setDomain(cookieConfiguration.getCookieDomain()); + updatedCookie.setHttpOnly(cookieConfiguration.isCookieHttpOnly()); + return updatedCookie; + } + + /** + * Convert AuthenticationCookie to JSON and encrypt with given key + * + * @param authenticationCookie + * @param filterConfiguration + * @return + * @throws IOException + * @throws CipherUtilException + */ + public static String getEncryptedCookie(AuthenticationCookie authenticationCookie, ISessionValidationFilterConfiguration filterConfiguration) throws IOException, CipherUtilException { + String changedCookieJson = RepresentationUtils.toRepresentation(authenticationCookie); + return CipherUtil.encryptPKC(changedCookieJson, filterConfiguration.getSecurityKey()); + } + + /** + * Decrypt given Cookie to JSON and convert to AuthenticationCookie object + * + * @param cookie + * @param filterConfiguration + * @return + * @throws CipherUtilException + */ + public static AuthenticationCookie getAuthenticationCookie(Cookie cookie, ISessionValidationFilterConfiguration filterConfiguration) throws CipherUtilException { + String originalCookieJson = CipherUtil.decryptPKC(cookie.getValue(), filterConfiguration.getSecurityKey()); + return RepresentationUtils.fromRepresentation(originalCookieJson, AuthenticationCookie.class); + } + + /** + * session expired if session was idle or max time reached + * + * @param cookie + * @param filterConfiguration + * @return + * @throws CipherUtilException + */ + public static boolean isSessionExpired(Cookie cookie, ISessionValidationFilterConfiguration filterConfiguration) throws CipherUtilException { + AuthenticationCookie authenticationCookie = getAuthenticationCookie(cookie, filterConfiguration); + long sessionExpirationDate = authenticationCookie.getMaxSessionTime() + filterConfiguration.getMaxSessionTimeOut(); + long sessionTime = authenticationCookie.getCurrentSessionTime(); + long currentTime = System.currentTimeMillis(); + log.debug("SessionValidationFilter: Checking if session expired: session time: {}, expiration time: {}, current time: {}", sessionTime, sessionExpirationDate, currentTime); + return currentTime > sessionExpirationDate || isSessionIdle(sessionTime, currentTime, filterConfiguration); + } + + /** + * Session is idle if wasn't updated ( wasn't in use ) for more then value from filter configuration + * + * @param sessionTimeValue + * @param currentTime + * @param filterConfiguration + * @return + */ + public static boolean isSessionIdle(long sessionTimeValue, long currentTime, ISessionValidationFilterConfiguration filterConfiguration) { + long currentIdleTime = currentTime - sessionTimeValue; + long maxIdleTime = filterConfiguration.getSessionIdleTimeOut(); + log.debug("SessionValidationFilter: Checking if session idle: session time: {}, current idle time: {}, max idle time: {}", currentTime, currentIdleTime, maxIdleTime); + return currentIdleTime >= maxIdleTime; + } + + +} diff --git a/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/CipherUtil.java b/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/CipherUtil.java new file mode 100644 index 0000000000..39553ca789 --- /dev/null +++ b/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/CipherUtil.java @@ -0,0 +1,123 @@ +package org.openecomp.sdc.securityutil; + +import java.security.SecureRandom; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import org.apache.commons.codec.binary.Base64; + +public class CipherUtil { + private static Logger log = LoggerFactory.getLogger( CipherUtil.class.getName()); + private static final String ALGORITHM = "AES"; + private static final String ALGORYTHM_DETAILS = ALGORITHM + "/CBC/PKCS5PADDING"; + private static final String CIPHER_PROVIDER = "SunJCE"; + private static final int BLOCK_SIZE = 128; + private static final int BYTE_SIZE = 8; + private static final int IV_SIZE = BLOCK_SIZE / BYTE_SIZE; + private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + private static final String ALGORITHM_NAME = "SHA1PRNG"; + + /** + * Encrypt the text using the secret key in key.properties file + * + * @param value string to encrypt + * @return The encrypted string + * @throws CipherUtilException + * In case of issue with the encryption + */ + public static String encryptPKC(String value, String base64key) throws CipherUtilException { + Cipher cipher; + byte[] iv = new byte[IV_SIZE]; + byte[] finalByte; + try { + cipher = Cipher.getInstance(ALGORYTHM_DETAILS, CIPHER_PROVIDER); + SecureRandom secureRandom = SecureRandom.getInstance(ALGORITHM_NAME); + secureRandom.nextBytes(iv); + IvParameterSpec ivspec = new IvParameterSpec(iv); + cipher.init(Cipher.ENCRYPT_MODE, getSecretKeySpec(base64key), ivspec); + finalByte = cipher.doFinal(value.getBytes()); + + } catch (Exception ex) { + log.error("encrypt failed", ex); + throw new CipherUtilException(ex); + } + return Base64.encodeBase64String(addAll(iv, finalByte)); + } + + /** + * Decrypts the text using the secret key in key.properties file. + * + * @param message + * The encrypted string that must be decrypted using the ONAP Portal + * Encryption Key + * @return The String decrypted + * @throws CipherUtilException + * if any decryption step fails + */ + + public static String decryptPKC(String message, String base64key) throws CipherUtilException { + byte[] encryptedMessage = Base64.decodeBase64(message); + Cipher cipher; + byte[] decrypted; + try { + cipher = Cipher.getInstance(ALGORYTHM_DETAILS, CIPHER_PROVIDER); + IvParameterSpec ivspec = new IvParameterSpec(subarray(encryptedMessage, 0, IV_SIZE)); + byte[] realData = subarray(encryptedMessage, IV_SIZE, encryptedMessage.length); + cipher.init(Cipher.DECRYPT_MODE, getSecretKeySpec(base64key), ivspec); + decrypted = cipher.doFinal(realData); + + } catch (Exception ex) { + log.error("decrypt failed", ex); + throw new CipherUtilException(ex); + } + return new String(decrypted); + } + + private static SecretKeySpec getSecretKeySpec(String keyString) { + byte[] key = Base64.decodeBase64(keyString); + return new SecretKeySpec(key, ALGORITHM); + } + + private static byte[] clone(byte[] array) { + return array == null ? null : array.clone(); + } + + private static byte[] addAll(byte[] array1, byte[] array2) { + if (array1 == null) { + return clone(array2); + } else if (array2 == null) { + return clone(array1); + } else { + byte[] joinedArray = new byte[array1.length + array2.length]; + System.arraycopy(array1, 0, joinedArray, 0, array1.length); + System.arraycopy(array2, 0, joinedArray, array1.length, array2.length); + return joinedArray; + } + } + + private static byte[] subarray(byte[] array, int startIndexInclusive, int endIndexExclusive) { + if (array == null) { + return null; + } else { + if (startIndexInclusive < 0) { + startIndexInclusive = 0; + } + + if (endIndexExclusive > array.length) { + endIndexExclusive = array.length; + } + + int newSize = endIndexExclusive - startIndexInclusive; + if (newSize <= 0) { + return EMPTY_BYTE_ARRAY; + } else { + byte[] subarray = new byte[newSize]; + System.arraycopy(array, startIndexInclusive, subarray, 0, newSize); + return subarray; + } + } + } +} diff --git a/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/CipherUtilException.java b/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/CipherUtilException.java new file mode 100644 index 0000000000..0e0d53a2f9 --- /dev/null +++ b/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/CipherUtilException.java @@ -0,0 +1,66 @@ +package org.openecomp.sdc.securityutil; + + +/* ============LICENSE_START========================================== + * ONAP Portal SDK + * =================================================================== + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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. + * + * ============LICENSE_END============================================ + * + * + */ + + +public class CipherUtilException extends Exception { + + private static final long serialVersionUID = -4163367786939629691L; + + public CipherUtilException() { + super(); + } + + public CipherUtilException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + public CipherUtilException(String message, Throwable cause) { + super(message, cause); + } + + public CipherUtilException(String message) { + super(message); + } + + public CipherUtilException(Throwable cause) { + super(cause); + } + +} \ No newline at end of file diff --git a/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/ISessionValidationCookieConfiguration.java b/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/ISessionValidationCookieConfiguration.java new file mode 100644 index 0000000000..cb72dca941 --- /dev/null +++ b/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/ISessionValidationCookieConfiguration.java @@ -0,0 +1,12 @@ +package org.openecomp.sdc.securityutil; + +/** + * Configuration for Cookie object , have to be same over all components of application + */ +public interface ISessionValidationCookieConfiguration { + + String getCookieName(); + String getCookieDomain(); + String getCookiePath(); + boolean isCookieHttpOnly(); +} diff --git a/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/ISessionValidationFilterConfiguration.java b/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/ISessionValidationFilterConfiguration.java new file mode 100644 index 0000000000..bf55ba947b --- /dev/null +++ b/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/ISessionValidationFilterConfiguration.java @@ -0,0 +1,13 @@ +package org.openecomp.sdc.securityutil; + +import java.util.List; + +public interface ISessionValidationFilterConfiguration extends ISessionValidationCookieConfiguration { + + String getSecurityKey(); + long getMaxSessionTimeOut(); + long getSessionIdleTimeOut(); // max idle time for session + String getRedirectURL(); + List getExcludedUrls(); // comma separated URLs, like this "/config,/configmgr,/rest,/kibanaProxy" +} + diff --git a/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/Passwords.java b/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/Passwords.java new file mode 100644 index 0000000000..e10bd81d0f --- /dev/null +++ b/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/Passwords.java @@ -0,0 +1,187 @@ +package org.openecomp.sdc.securityutil;/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + * ============LICENSE_END========================================================= + */ + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Random; + + +public class Passwords { + + private static Logger log = LoggerFactory.getLogger( Passwords.class.getName()); + private static final Random RANDOM = new SecureRandom(); + private static final int SALT = 0; + private static final int HASH = 1; + private static final String HASH_ALGORITHM = "SHA-256"; + + /** + * static utility class + */ + private Passwords() { + } + + /** + * the method calculates a hash with a generated salt for the given password + * + * @param password + * @return a "salt:hash" value + */ + public static String hashPassword(String password) { + if (password!=null){ + byte[] salt = getNextSalt(); + byte byteData[] = hash(salt, password.getBytes()); + if (byteData != null) { + return toHex(salt) + ":" + toHex(byteData); + } + } + return null; + } + + /** + * the method checks if the given password matches the calculated hash + * + * @param password + * @param expectedHash + * @return + */ + public static boolean isExpectedPassword(String password, String expectedHash) { + if (password==null && expectedHash==null) + return true; + if (password==null || expectedHash==null) //iff exactly 1 is null + return false; + if (!expectedHash.contains(":")){ + log.error("invalid password expecting hash at the prefix of the password (ex. e0277df331f4ff8f74752ac4a8fbe03b:6dfbad308cdf53c9ff2ee2dca811ee92f1b359586b33027580e2ff92578edbd0)\n" + + "\t\t\t"); + return false; + } + String[] params = expectedHash.split(":"); + return isExpectedPassword(password, params[SALT], params[HASH]); + } + + /** + * the method checks if the given password matches the calculated hash + * + * @param password + * @param salt + * @param hash + * the hash generated using the salt + * @return true if the password matched the hash + */ + public static boolean isExpectedPassword(String password, String salt, String hash) { + if ( password == null && hash == null ) + return true; + if ( salt == null ){ + log.error("salt must be initialized"); + return false; + } + //unintialized params + if ( password == null || hash == null ) + return false; + byte[] saltBytes = fromHex(salt); + byte[] hashBytes = fromHex(hash); + + byte byteData[] = hash(saltBytes, password.getBytes()); + if (byteData != null) { + return Arrays.equals(byteData, hashBytes); + } + return false; + } + + public static void main(String[] args) { + if (args.length > 1 || args.length > 0) { + System.out.println("[" + hashPassword(args[0]) + "]"); + } else { + System.out.println("no passward passed."); + } + + } + + /** + * Returns a random salt to be used to hash a password. + * + * @return a 16 bytes random salt + */ + private static byte[] getNextSalt() { + byte[] salt = new byte[16]; + RANDOM.nextBytes(salt); + return salt; + } + + /** + * hase's the salt and value using the chosen algorithm + * + * @param salt + * @param password + * @return an array of bytes resulting from the hash + */ + private static byte[] hash(byte[] salt, byte[] password) { + MessageDigest md; + byte[] byteData = null; + try { + md = MessageDigest.getInstance(HASH_ALGORITHM); + md.update(salt); + md.update(password); + byteData = md.digest(); + } catch (NoSuchAlgorithmException e) { + System.out.println("invalid algorithm name"); + } + return byteData; + } + + /** + * Converts a string of hexadecimal characters into a byte array. + * + * @param hex + * the hex string + * @return the hex string decoded into a byte array + */ + private static byte[] fromHex(String hex) { + if ( hex == null ) + return null; + byte[] binary = new byte[hex.length() / 2]; + for (int i = 0; i < binary.length; i++) { + binary[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16); + } + return binary; + } + + /** + * Converts a byte array into a hexadecimal string. + * + * @param array + * the byte array to convert + * @return a length*2 character string encoding the byte array + */ + private static String toHex(byte[] array) { + BigInteger bi = new BigInteger(1, array); + String hex = bi.toString(16); + int paddingLength = (array.length * 2) - hex.length(); + if (paddingLength > 0) + return String.format("%0" + paddingLength + "d", 0) + hex; + else + return hex; + } +} diff --git a/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/RepresentationUtils.java b/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/RepresentationUtils.java new file mode 100644 index 0000000000..b647fa3a65 --- /dev/null +++ b/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/RepresentationUtils.java @@ -0,0 +1,53 @@ +package org.openecomp.sdc.securityutil; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +public class RepresentationUtils { + + private static final Logger log = LoggerFactory.getLogger(RepresentationUtils.class.getName()); + + /** + * Build JSON Representation of given Object + * + * @param elementToRepresent + * @return + * @throws IOException + */ + public static String toRepresentation(T elementToRepresent) throws IOException { + + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + return mapper.writeValueAsString(elementToRepresent); + } + + /** + * Convert JSON representation to given class + * + * @param json + * @param clazz + * @param + * @return + */ + public static T fromRepresentation(String json, Class clazz) { + ObjectMapper mapper = new ObjectMapper(); + T object = null; + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + try { + object = mapper.readValue(json, clazz); + } catch (Exception e) { + log.error("Error when parsing JSON of object of type {}", clazz.getSimpleName(), e); + } // return null in case of exception + + return object; + } +} diff --git a/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/SecurityUtil.java b/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/SecurityUtil.java new file mode 100644 index 0000000000..8dd4e33a44 --- /dev/null +++ b/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/SecurityUtil.java @@ -0,0 +1,134 @@ +package org.openecomp.sdc.securityutil; + +import fj.data.Either; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.SecretKeySpec; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; + +public class SecurityUtil { + + private static final Logger LOG = LoggerFactory.getLogger( SecurityUtil.class ); + private static final byte[] KEY = new byte[]{-64,5,-32 ,-117 ,-44,8,-39, 1, -9, 36,-46,-81, 62,-15,-63,-75}; + public static final SecurityUtil INSTANCE = new SecurityUtil(); + public static final String ALGORITHM = "AES" ; + public static final String CHARSET = StandardCharsets.UTF_8.name(); + + public static Key secKey = null ; + + /** + * + * cmd commands >$PROGRAM_NAME decrypt "$ENCRYPTED_MSG" + * >$PROGRAM_NAME encrypt "message" + **/ + + private SecurityUtil(){ super(); } + + static { + try{ + secKey = generateKey( KEY, ALGORITHM ); + } + catch(Exception e){ + LOG.warn("cannot generate key for {}", ALGORITHM); + } + } + + + + public static Key generateKey(final byte[] KEY, String algorithm){ + return new SecretKeySpec(KEY, algorithm); + } + + //obfuscates key prefix -> ********** + public String obfuscateKey(String sensitiveData){ + + if (sensitiveData != null){ + int len = sensitiveData.length(); + StringBuilder builder = new StringBuilder(sensitiveData); + for (int i=0; i encrypt(String strDataToEncrypt){ + if (strDataToEncrypt != null ){ + try { + LOG.debug("Encrypt key -> {}", secKey); + Cipher aesCipherForEncryption = Cipher.getInstance("AES"); // Must specify the mode explicitly as most JCE providers default to ECB mode!! + aesCipherForEncryption.init(Cipher.ENCRYPT_MODE, secKey); + byte[] byteDataToEncrypt = strDataToEncrypt.getBytes(); + byte[] byteCipherText = aesCipherForEncryption.doFinal(byteDataToEncrypt); + String strCipherText = new String( Base64.getMimeEncoder().encode(byteCipherText), CHARSET ); + LOG.debug("Cipher Text generated using AES is {}", strCipherText); + return Either.left(strCipherText); + } catch( NoSuchAlgorithmException | UnsupportedEncodingException e){ + LOG.warn( "cannot encrypt data unknown algorithm or missing encoding for {}" ,secKey.getAlgorithm()); + } catch( InvalidKeyException e){ + LOG.warn( "invalid key recieved - > {} | {}" , Base64.getDecoder().decode( secKey.getEncoded() ), e.getMessage() ); + } catch( IllegalBlockSizeException | BadPaddingException | NoSuchPaddingException e){ + LOG.warn( "bad algorithm definition (Illegal Block Size or padding), please review you algorithm block&padding" , e.getMessage() ); + } + } + return Either.right("Cannot encrypt "+strDataToEncrypt); + } + + /** + * Decrypt the Data + * @param byteCipherText - should be valid bae64 input in the length of 16bytes + * @param isBase64Decoded - is data already base64 encoded&aligned to 16 bytes + * a. Initialize a new instance of Cipher for Decryption (normally don't reuse the same object) + * b. Decrypt the cipher bytes using doFinal method + */ + public Either decrypt(byte[] byteCipherText , boolean isBase64Decoded){ + if (byteCipherText != null){ + byte[] alignedCipherText = byteCipherText; + try{ + if (isBase64Decoded) + alignedCipherText = Base64.getDecoder().decode(byteCipherText); + LOG.debug("Decrypt key -> "+secKey.getEncoded()); + Cipher aesCipherForDecryption = Cipher.getInstance("AES"); // Must specify the mode explicitly as most JCE providers default to ECB mode!! + aesCipherForDecryption.init(Cipher.DECRYPT_MODE, secKey); + byte[] byteDecryptedText = aesCipherForDecryption.doFinal(alignedCipherText); + String strDecryptedText = new String(byteDecryptedText); + LOG.debug("Decrypted Text message is: {}" , obfuscateKey( strDecryptedText )); + return Either.left(strDecryptedText); + } catch( NoSuchAlgorithmException e){ + LOG.warn( "cannot encrypt data unknown algorithm or missing encoding for {}" ,secKey.getAlgorithm()); + } catch( InvalidKeyException e){ + LOG.warn( "invalid key recieved - > {} | {}" , Base64.getDecoder().decode( secKey.getEncoded() ), e.getMessage() ); + } catch( IllegalBlockSizeException | BadPaddingException | NoSuchPaddingException e){ + LOG.warn( "bad algorithm definition (Illegal Block Size or padding), please review you algorithm block&padding" , e.getMessage() ); + } + } + return Either.right("Decrypt FAILED"); + } + + public Either decrypt(String byteCipherText){ + try { + return decrypt(byteCipherText.getBytes(CHARSET),true); + } catch( UnsupportedEncodingException e ){ + LOG.warn( "Missing encoding for {} | {} " ,secKey.getAlgorithm() , e.getMessage()); + } + return Either.right("Decrypt FAILED"); + } +} diff --git a/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/filters/FilterServletOutputStream.java b/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/filters/FilterServletOutputStream.java new file mode 100644 index 0000000000..14fa598a50 --- /dev/null +++ b/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/filters/FilterServletOutputStream.java @@ -0,0 +1,38 @@ +package org.openecomp.sdc.securityutil.filters; + +import javax.servlet.ServletOutputStream; +import javax.servlet.WriteListener; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +public class FilterServletOutputStream extends ServletOutputStream { + + private DataOutputStream stream; + + public FilterServletOutputStream(OutputStream output) { + stream = new DataOutputStream(output); + } + + public void write(int b) throws IOException { + stream.write(b); + } + + public void write(byte[] b) throws IOException { + stream.write(b); + } + + public void write(byte[] b, int off, int len) throws IOException { + stream.write(b,off,len); + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setWriteListener(WriteListener writeListener) { + + } +} diff --git a/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/filters/ResponceWrapper.java b/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/filters/ResponceWrapper.java new file mode 100644 index 0000000000..df5351fe78 --- /dev/null +++ b/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/filters/ResponceWrapper.java @@ -0,0 +1,53 @@ +package org.openecomp.sdc.securityutil.filters; + +import javax.servlet.ServletOutputStream; +import javax.servlet.WriteListener; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; +import java.io.ByteArrayOutputStream; +import java.io.CharArrayWriter; +import java.io.IOException; +import java.io.PrintWriter; + +public class ResponceWrapper extends HttpServletResponseWrapper { + private ByteArrayOutputStream output; + private int contentLength; + private String contentType; + + public ResponceWrapper(HttpServletResponse response) { + super(response); + output = new ByteArrayOutputStream(); + } + + public byte[] getData() { + return output.toByteArray(); + } + + public ServletOutputStream getOutputStream() { + return new FilterServletOutputStream(output); + } + + public PrintWriter getWriter() { + return new PrintWriter(getOutputStream(), true); + } + + public void setContentLength(int length) { + this.contentLength = length; + super.setContentLength(length); + } + + public int getContentLength() { + return contentLength; + } + + public void setContentType(String type) { + this.contentType = type; + super.setContentType(type); + } + + + public String getContentType() { + return contentType; + + } +} diff --git a/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/filters/SampleFilter.java b/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/filters/SampleFilter.java new file mode 100644 index 0000000000..15cd4c537c --- /dev/null +++ b/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/filters/SampleFilter.java @@ -0,0 +1,132 @@ +package org.openecomp.sdc.securityutil.filters; + + +import org.openecomp.sdc.securityutil.ISessionValidationFilterConfiguration; + +import javax.servlet.http.Cookie; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class SampleFilter extends SessionValidationFilter { + + private static class Configuration implements ISessionValidationFilterConfiguration { + + private static Configuration instance; + + private String securityKey; + private long maxSessionTimeOut; + private long sessionIdleTimeOut; + private String redirectURL; + private List excludedUrls; + + private String cookieName; + private String cookieDomain; + private String cookiePath; + private boolean isCookieHttpOnly; + + private Configuration() { + //security key should be exactly 16 characters long clear text and then encoded to base64 + this.securityKey = "AGLDdG4D04BKm2IxIWEr8o=="; + this.maxSessionTimeOut = 24*60*60*1000; + this.sessionIdleTimeOut = 60*60*1000; + this.redirectURL = "https://www.e-access.att.com/ecomp_portal_ist/ecompportal/process_csp"; + this.excludedUrls = new ArrayList<>(Arrays.asList("/config","/configmgr","/rest","/kibanaProxy","/healthcheck","/upload.*")); + + this.cookieName = "kuku"; + this.cookieDomain = ""; + this.cookiePath = "/"; + this.isCookieHttpOnly = true; + } + + public void setSecurityKey(String securityKey) { + this.securityKey = securityKey; + } + + public void setMaxSessionTimeOut(long maxSessionTimeOut) { + this.maxSessionTimeOut = maxSessionTimeOut; + } + + public void setCookieName(String cookieName) { + this.cookieName = cookieName; + } + + public void setRedirectURL(String redirectURL) { + this.redirectURL = redirectURL; + } + + public void setExcludedUrls(List excludedUrls) { + this.excludedUrls = excludedUrls; + } + + public static Configuration getInstance(){ + if (instance == null ){ + instance = new Configuration(); + } + return instance; + } + + @Override + public String getSecurityKey() { + return securityKey; + } + + @Override + public long getMaxSessionTimeOut() { + return maxSessionTimeOut; + } + + @Override + public long getSessionIdleTimeOut() { + return sessionIdleTimeOut; + } + + @Override + public String getCookieName() { + return cookieName; + } + + @Override + public String getCookieDomain() { + return cookieDomain; + } + + @Override + public String getCookiePath() { + return cookiePath; + } + + @Override + public boolean isCookieHttpOnly() { + return isCookieHttpOnly; + } + + @Override + public String getRedirectURL() { + return redirectURL; + } + + @Override + public List getExcludedUrls() { + return excludedUrls; + } + } + + @Override + public ISessionValidationFilterConfiguration getFilterConfiguration() { + return Configuration.getInstance(); + } + + @Override + protected Cookie addRoleToCookie(Cookie updatedCookie) { + return updatedCookie; + } + + @Override + protected boolean isRoleValid(Cookie cookie) { + return true; + } + +} + + diff --git a/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/filters/SessionValidationFilter.java b/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/filters/SessionValidationFilter.java new file mode 100644 index 0000000000..9863d8f904 --- /dev/null +++ b/openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org/openecomp/sdc/securityutil/filters/SessionValidationFilter.java @@ -0,0 +1,194 @@ +package org.openecomp.sdc.securityutil.filters; + +import org.openecomp.sdc.securityutil.AuthenticationCookieUtils; +import org.openecomp.sdc.securityutil.CipherUtilException; +import org.openecomp.sdc.securityutil.ISessionValidationFilterConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +public abstract class SessionValidationFilter implements Filter { + private static final Logger log = LoggerFactory.getLogger(SessionValidationFilter.class.getName()); + private ISessionValidationFilterConfiguration filterConfiguration; + private List excludedUrls; + + public abstract ISessionValidationFilterConfiguration getFilterConfiguration(); + protected abstract Cookie addRoleToCookie(Cookie updatedCookie); + protected abstract boolean isRoleValid(Cookie cookie); + + @Override + public final void init(FilterConfig filterConfig) throws ServletException { + filterConfiguration = getFilterConfiguration(); + excludedUrls = filterConfiguration.getExcludedUrls(); + } + + @Override + public final void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + final HttpServletRequest httpRequest = (HttpServletRequest) servletRequest; + final HttpServletResponse httpResponse = (HttpServletResponse) servletResponse; + + long starTime = System.nanoTime(); + log.debug("SessionValidationFilter: Validation started, received request with URL {}", httpRequest.getRequestURL()); + + // request preprocessing + boolean isContinueProcessing = preProcessingRequest(servletRequest, servletResponse, filterChain, httpRequest, httpResponse); + List cookies = null; + + // request processing + if (isContinueProcessing) { + cookies = extractAuthenticationCookies(httpRequest.getCookies()); + isContinueProcessing = processRequest(httpRequest, httpResponse, cookies.get(0)); + } + + // response processing + if(isContinueProcessing){ + log.debug("SessionValidationFilter: Cookie from request {} is valid, passing request to session extension ...", httpRequest.getRequestURL()); + Cookie updatedCookie = processResponse(cookies.get(cookies.size()-1)); + + cleanResponceFromLeftoverCookies(httpResponse, cookies); + + // Use responce wrapper if servlet remove Cookie header from responce +// OutputStream out = httpResponse.getOutputStream(); +// ResponceWrapper responceWrapper = new ResponceWrapper(httpResponse); + + log.debug("SessionValidationFilter: request {} passed all validations, passing request to endpoint ...", httpRequest.getRequestURL()); + httpResponse.addCookie(updatedCookie); + filterChain.doFilter(servletRequest, httpResponse); + + // Use responce wrapper if servlet remove Cookie header from responce +// responceWrapper.addCookie(updatedCookie); +// httpResponse.setContentLength(responceWrapper.getData().length); +// out.write(responceWrapper.getData()); +// out.close(); + } + long durationSec = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - starTime); + long durationMil = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - starTime); + log.debug("SessionValidationFilter: Validation ended, running time for URL {} is: {} seconds {} miliseconds", httpRequest.getPathInfo(), durationSec, durationMil); + } + + + private boolean preProcessingRequest(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { + + boolean isPreProcessingSucceeded = true; + if (isUrlFromWhiteList(httpRequest)) { + log.debug("SessionValidationFilter: URL {} excluded from access validation , passing request to endpoint ... ", httpRequest.getRequestURL()); + filterChain.doFilter(servletRequest, servletResponse); + isPreProcessingSucceeded = false; + + } else if (!isCookiePresent(httpRequest.getCookies())) { + //redirect to portal app + log.debug("SessionValidationFilter: Cookie from request {} is not valid, redirecting request to portal", httpRequest.getRequestURL()); + httpResponse.sendRedirect(filterConfiguration.getRedirectURL()); + isPreProcessingSucceeded = false; + } + return isPreProcessingSucceeded; + } + + private boolean processRequest(HttpServletRequest httpRequest, HttpServletResponse httpResponse, Cookie cookie) throws IOException { + boolean isProcessSuccessful= true; + try { + if (AuthenticationCookieUtils.isSessionExpired(cookie, filterConfiguration)) { + //redirect to portal app + log.debug("SessionValidationFilter: Session is expired, redirecting request {} to portal", httpRequest.getRequestURL()); + httpResponse.sendRedirect(filterConfiguration.getRedirectURL()); + isProcessSuccessful = false; + } + } catch (CipherUtilException e) { + log.error("SessionValidationFilter: Cookie decryption error : {}", e.getMessage()); + log.debug("SessionValidationFilter: Cookie decryption error : {}", e.getMessage(), e); + isProcessSuccessful = false; + } + + if (!isRoleValid(cookie)) { + //redirect to portal app + log.debug("SessionValidationFilter: Role is not valid, redirecting request {} to portal", httpRequest.getRequestURL()); + httpResponse.sendRedirect(filterConfiguration.getRedirectURL()); + isProcessSuccessful = false; + } + return isProcessSuccessful; + } + + private Cookie processResponse(Cookie cookie) throws IOException, ServletException { + Cookie updatedCookie; + try { + updatedCookie = AuthenticationCookieUtils.updateSessionTime(cookie, filterConfiguration); + } catch (CipherUtilException e) { + log.error("SessionValidationFilter: Cookie cipher error ..."); + log.debug("SessionValidationFilter: Cookie cipher error : {}", e.getMessage(), e); + throw new ServletException(e.getMessage()); + } + updatedCookie = addRoleToCookie(updatedCookie); + return updatedCookie; + } + + private boolean isCookiePresent(Cookie[] cookies) { + if (cookies == null) { + return false; + } + String actualCookieName = filterConfiguration.getCookieName(); + boolean isPresent = Arrays.stream(cookies).anyMatch(c -> isCookieNameMatch(actualCookieName, c)); + if (!isPresent) { + log.error("SessionValidationFilter: Session Validation Cookie missing ..."); + return false; + } + return true; + } + + private List extractAuthenticationCookies(Cookie[] cookies) { + String actualCookieName = filterConfiguration.getCookieName(); + log.debug("SessionValidationFilter: Extracting authentication cookies, {} cookies in request", cookies.length); + List authenticationCookies = Arrays.stream(cookies).filter(c -> isCookieNameMatch(actualCookieName, c)).collect(Collectors.toList()); + log.debug("SessionValidationFilter: Extracted {} authentication cookies from request", authenticationCookies.size()); + if( authenticationCookies.size() > 1 ){ + authenticationCookies.forEach( cookie -> log.debug("SessionValidationFilter: Multiple cookies found cookie name, {} cookie value {}", cookie.getName(), cookie.getValue())); + } + return authenticationCookies; + } + + + // use contains for matching due issue with ecomp portal ( change cookie name, add prefix ), temp solution + private boolean isCookieNameMatch(String actualCookieName, Cookie c) { + return c.getName().contains(actualCookieName); + } + + private boolean isUrlFromWhiteList(HttpServletRequest httpRequest) { + if (httpRequest.getPathInfo() == null){ + final String servletPath = httpRequest.getServletPath().toLowerCase(); + log.debug("SessionValidationFilter: pathInfo is null, trying to check by servlet path white list validation -> ServletPath: {} ", servletPath); + return excludedUrls.stream(). + anyMatch( e -> servletPath.matches(e)); + } + String pathInfo = httpRequest.getPathInfo().toLowerCase(); + log.debug("SessionValidationFilter: white list validation -> PathInfo: {} ", pathInfo); + return excludedUrls.stream(). + anyMatch( e -> pathInfo.matches(e)); + } + + private void cleanResponceFromLeftoverCookies(HttpServletResponse httpResponse, List cookiesList) { + for (Cookie cookie:cookiesList){ + Cookie cleanCookie = AuthenticationCookieUtils.createUpdatedCookie(cookie, null, filterConfiguration); + cleanCookie.setMaxAge(0); + log.debug("SessionValidationFilter Cleaning Cookie cookie name: {} added to responce", cleanCookie.getName()); + httpResponse.addCookie(cleanCookie); + } + } + + @Override + public void destroy() { + + } +} diff --git a/openecomp-be/backend/openecomp-sdc-security-util/src/test/AuthenticationCookieUtilsTest.java b/openecomp-be/backend/openecomp-sdc-security-util/src/test/AuthenticationCookieUtilsTest.java new file mode 100644 index 0000000000..c03bf9c65d --- /dev/null +++ b/openecomp-be/backend/openecomp-sdc-security-util/src/test/AuthenticationCookieUtilsTest.java @@ -0,0 +1,54 @@ +package org.onap.sdc.security; + +import org.junit.Test; +import org.onap.sdc.security.filters.SampleFilter; + +import javax.servlet.http.Cookie; + +import java.io.IOException; + +import static org.junit.Assert.*; + +public class AuthenticationCookieUtilsTest { + + private SampleFilter sessionValidationFilter = new SampleFilter(); + private ISessionValidationFilterConfiguration filterCfg = sessionValidationFilter.getFilterConfiguration(); + + @Test + public void vaildateThatCookieCurrentSessionTimeIncreased() throws IOException, CipherUtilException { + // original cookie, pojo and servlet cookie + AuthenticationCookie authenticationCookieOriginal = new AuthenticationCookie("kuku"); + Cookie cookieWithOriginalTime = new Cookie(filterCfg.getCookieName(), AuthenticationCookieUtils.getEncryptedCookie(authenticationCookieOriginal,filterCfg )); + // cookie with increased time, pojo and servlet cookie + Cookie cookieWithIncreasedTime = AuthenticationCookieUtils.updateSessionTime(cookieWithOriginalTime, filterCfg); + AuthenticationCookie authenticationCookieIncreasedTime = AuthenticationCookieUtils.getAuthenticationCookie(cookieWithIncreasedTime, filterCfg); + // validation + long currentSessionTimeOriginal = authenticationCookieOriginal.getCurrentSessionTime(); + long currentSessionTimeIncreased = authenticationCookieIncreasedTime.getCurrentSessionTime(); + assertTrue(currentSessionTimeOriginal < currentSessionTimeIncreased); + } + + @Test + public void validateSerializationEncriptionDeserializationDecryption() throws IOException, CipherUtilException { + // original cookie, pojo and servlet cookie + AuthenticationCookie authenticationCookieOriginal = new AuthenticationCookie("kuku"); + Cookie cookieWithOriginalTime = new Cookie(filterCfg.getCookieName(), AuthenticationCookieUtils.getEncryptedCookie(authenticationCookieOriginal,filterCfg )); + // cookie with increased time, pojo and servlet cookie + AuthenticationCookie decriptedAndDeserializedAuthenticationCookie = AuthenticationCookieUtils.getAuthenticationCookie(cookieWithOriginalTime,filterCfg); + assertTrue(authenticationCookieOriginal.equals(decriptedAndDeserializedAuthenticationCookie)); + } + + + +// @Test +// public void getEncryptedCookie() { +// } +// +// @Test +// public void getAuthenticationCookie() { +// } +// +// @Test +// public void isSessionExpired() { +// } +} \ No newline at end of file diff --git a/openecomp-be/backend/openecomp-sdc-security-util/src/test/CipherUtilTest.java b/openecomp-be/backend/openecomp-sdc-security-util/src/test/CipherUtilTest.java new file mode 100644 index 0000000000..753902fcf8 --- /dev/null +++ b/openecomp-be/backend/openecomp-sdc-security-util/src/test/CipherUtilTest.java @@ -0,0 +1,56 @@ +package org.onap.sdc.security; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang.RandomStringUtils; +import org.junit.Test; + +import java.util.Random; + +import static org.apache.commons.codec.binary.Base64.encodeBase64String; +import static org.junit.Assert.*; + +public class CipherUtilTest { + + private static final String KEY = "AGLDdG4D04BKm2IxIWEr8o=="; + private static final String DATA = "data"; + + @Test + public void encryptDecryptPKC() throws CipherUtilException { + String generatedKey = RandomStringUtils.randomAlphabetic(16); + String base64Key = Base64.encodeBase64String(generatedKey.getBytes()); + String encrypted = CipherUtil.encryptPKC(DATA, base64Key); + assertNotEquals(DATA, encrypted); + String decrypted = CipherUtil.decryptPKC(encrypted, base64Key); + assertEquals(decrypted, DATA); + } + + @Test + public void encryptInvalidKey() { + try { + CipherUtil.encryptPKC(DATA, "invalidKey"); + fail(); + } catch (CipherUtilException ex) { + assertTrue(ex.getMessage().contains("Invalid AES key length")); + } + } + + @Test + public void decryptInvalidKey() { + try { + CipherUtil.decryptPKC(DATA, "invalidKey"); + fail(); + } catch (CipherUtilException ex) { + assertTrue(ex.getMessage().contains("length")); + } + } + + @Test + public void decryptInvalidData() { + try { + CipherUtil.decryptPKC(DATA, KEY); + fail(); + } catch (CipherUtilException ex) { + assertTrue(ex.getMessage().contains("Wrong IV length")); + } + } +} diff --git a/openecomp-be/backend/openecomp-sdc-security-util/src/test/PasswordsTest.java b/openecomp-be/backend/openecomp-sdc-security-util/src/test/PasswordsTest.java new file mode 100644 index 0000000000..6da62a92e5 --- /dev/null +++ b/openecomp-be/backend/openecomp-sdc-security-util/src/test/PasswordsTest.java @@ -0,0 +1,77 @@ +package org.onap.sdc.security; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class PasswordsTest { + + @Test + public void hashPassword() throws Exception { + String hash = Passwords.hashPassword("hello1234"); + assertTrue(Passwords.isExpectedPassword("hello1234", hash)); + + //test different salt-> result in different hash + String hash2 = Passwords.hashPassword("hello1234"); + assertFalse(hash.equals(hash2)); + + String hash3 = Passwords.hashPassword(""); + assertTrue(Passwords.isExpectedPassword("", hash3)); + + String hash4 = Passwords.hashPassword(null); + assertTrue(hash4 == null); + } + + @Test + public void isExpectedPassword() throws Exception { + //region isExpectedPassword(String password, String salt, String hash) + assertTrue(Passwords.isExpectedPassword(null, null, null)); + //valid hash + assertTrue(Passwords.isExpectedPassword("hello1234", "e0277df331f4ff8f74752ac4a8fbe03b", "6dfbad308cdf53c9ff2ee2dca811ee92f1b359586b33027580e2ff92578edbd0")); + //invalid salt + assertFalse(Passwords.isExpectedPassword("hello1234", "c0000df331f4ff8f74752ac4a00be03c", "6dfbad308cdf53c9ff2ee2dca811ee92f1b359586b33027580e2ff92578edbd0")); + assertFalse(Passwords.isExpectedPassword("hello1234", null, "6dfbad308cdf53c9ff2ee2dca811ee92f1b359586b33027580e2ff92578edbd0")); + //exacly 1 param uninitialized + assertFalse(Passwords.isExpectedPassword("hello1234", "", null)); + assertFalse(Passwords.isExpectedPassword(null, "", "hello1234")); + //no salt & no hash + assertFalse(Passwords.isExpectedPassword("hello1234", null, "hello1234")); + //endregion + + //region isExpectedPassword(String password, String expectedHash) + assertTrue(Passwords.isExpectedPassword(null, null)); + //valid hash + assertTrue(Passwords.isExpectedPassword("hello1234", "e0277df331f4ff8f74752ac4a8fbe03b:6dfbad308cdf53c9ff2ee2dca811ee92f1b359586b33027580e2ff92578edbd0")); + //invalid salt + assertFalse(Passwords.isExpectedPassword("hello1234", "c0000df331f4ff8f74752ac4a00be03c:6dfbad308cdf53c9ff2ee2dca811ee92f1b359586b33027580e2ff92578edbd0")); + //exacly 1 param uninitialized + assertFalse(Passwords.isExpectedPassword("hello1234", null)); + assertFalse(Passwords.isExpectedPassword(null, "hello1234")); + //no salt & no hash + assertFalse(Passwords.isExpectedPassword("hello1234", "hello1234")); + //endregion + } + + @Test + public void hashtest() { + String password = "123456"; + String hash = Passwords.hashPassword(password); + assertTrue(Passwords.isExpectedPassword(password, hash)); + password = "1sdfgsgd23456"; + hash = Passwords.hashPassword(password); + assertTrue(Passwords.isExpectedPassword(password, hash)); + password = "1sdfgsgd2345((*&%$%6"; + hash = Passwords.hashPassword(password); + assertTrue(Passwords.isExpectedPassword(password, hash)); + password = ""; + hash = Passwords.hashPassword(password); + assertTrue(Passwords.isExpectedPassword(password, hash)); + password = " "; + hash = Passwords.hashPassword(password); + assertTrue(Passwords.isExpectedPassword(password, hash)); + } + + +} \ No newline at end of file diff --git a/openecomp-be/backend/openecomp-sdc-security-util/src/test/RepresentationUtilsTest.java b/openecomp-be/backend/openecomp-sdc-security-util/src/test/RepresentationUtilsTest.java new file mode 100644 index 0000000000..7b12ac7eb2 --- /dev/null +++ b/openecomp-be/backend/openecomp-sdc-security-util/src/test/RepresentationUtilsTest.java @@ -0,0 +1,34 @@ +package org.onap.sdc.security; + +import org.junit.Test; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +import static org.junit.Assert.assertTrue; + +public class RepresentationUtilsTest { + + private static AuthenticationCookie originalCookie = new AuthenticationCookie("kuku"); + + @Test + public void representationE2EwithRoleNull() throws IOException { + originalCookie.setRoles(null); + String jsonStr = RepresentationUtils.toRepresentation(originalCookie); + AuthenticationCookie cookieFromJson = RepresentationUtils.fromRepresentation(jsonStr, AuthenticationCookie.class); + assertTrue(originalCookie.equals(cookieFromJson)); + } + + @Test + public void representationE2EwithRoleNotNull() throws IOException { + Set roles = new HashSet(); + roles.add("Designer"); + roles.add("Admin"); + roles.add("Tester"); + originalCookie.setRoles(roles); + String jsonStr = RepresentationUtils.toRepresentation(originalCookie); + AuthenticationCookie cookieFromJson = RepresentationUtils.fromRepresentation(jsonStr, AuthenticationCookie.class); + assertTrue(originalCookie.equals(cookieFromJson)); + } +} diff --git a/openecomp-be/backend/openecomp-sdc-security-util/src/test/SecurityUtilTest.java b/openecomp-be/backend/openecomp-sdc-security-util/src/test/SecurityUtilTest.java new file mode 100644 index 0000000000..6a47434efc --- /dev/null +++ b/openecomp-be/backend/openecomp-sdc-security-util/src/test/SecurityUtilTest.java @@ -0,0 +1,30 @@ +package org.onap.sdc.security; + +import org.junit.Test; + +import java.util.Base64; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +public class SecurityUtilTest { + + @Test + public void encryptDecryptAES128() { + String data = "decrypt SUCCESS!!"; + String encrypted = SecurityUtil.INSTANCE.encrypt(data).left().value(); + assertNotEquals( data, encrypted ); + byte[] decryptMsg = Base64.getDecoder().decode(encrypted); + assertEquals( SecurityUtil.INSTANCE.decrypt( decryptMsg , false ).left().value() ,data ); + assertEquals( SecurityUtil.INSTANCE.decrypt( encrypted.getBytes() , true ).left().value() ,data ); + } + + @Test + public void obfuscateKey() { + String key = "abcdefghij123456"; + String expectedkey = "********ij123456"; + String obfuscated = SecurityUtil.INSTANCE.obfuscateKey( key ); + System.out.println( obfuscated ); + assertEquals( obfuscated , expectedkey ); + } +} \ No newline at end of file diff --git a/openecomp-be/backend/openecomp-sdc-security-util/src/test/SessionValidationFilterTest.java b/openecomp-be/backend/openecomp-sdc-security-util/src/test/SessionValidationFilterTest.java new file mode 100644 index 0000000000..6838e3bfb7 --- /dev/null +++ b/openecomp-be/backend/openecomp-sdc-security-util/src/test/SessionValidationFilterTest.java @@ -0,0 +1,157 @@ +package org.onap.sdc.security; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.onap.sdc.security.filters.ResponceWrapper; +import org.onap.sdc.security.filters.SampleFilter; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +//@RunWith(PowerMockRunner.class) +//@PrepareForTest(fullyQualifiedNames = "org.onap.sdc.security.*") +public class SessionValidationFilterTest { + + @Mock + private HttpServletRequest request; + @Spy + private HttpServletResponse response; + @Mock + private FilterChain filterChain; + @Mock + private FilterConfig filterConfig; + @Mock + private ResponceWrapper responceWrapper; + + // implementation of SessionValidationFilter + @InjectMocks + @Spy + private SampleFilter sessionValidationFilter = new SampleFilter(); + + @Before + public void setUpClass() throws ServletException { + sessionValidationFilter.init(filterConfig); + } + + @Test + public void excludedUrlHealthcheck() throws IOException, ServletException { + when(request.getPathInfo()).thenReturn("/healthCheck"); + sessionValidationFilter.doFilter(request, response, filterChain); + Mockito.verify(filterChain, times(1)).doFilter(request, response); + } + + @Test + public void excludedUrlUpload() throws IOException, ServletException { + when(request.getPathInfo()).thenReturn("/upload/123"); + sessionValidationFilter.doFilter(request, response, filterChain); + Mockito.verify(filterChain, times(1)).doFilter(request, response); + } + + // case when url pattern in web.xml is forward slash (/) + @Test + public void pathInfoIsNull() throws IOException, ServletException { + when(request.getServletPath()).thenReturn("/upload/2"); + when(request.getPathInfo()).thenReturn(null); + sessionValidationFilter.doFilter(request, response, filterChain); + Mockito.verify(filterChain, times(1)).doFilter(request, response); + } + + @Test + public void noCookiesInRequest() throws IOException, ServletException { + when(request.getPathInfo()).thenReturn("/resource"); + when(request.getCookies()).thenReturn(new Cookie[0]); + sessionValidationFilter.doFilter(request, response, filterChain); + Mockito.verify(response, times(1)).sendRedirect(sessionValidationFilter.getFilterConfiguration().getRedirectURL()); + } + + @Test + public void nullCookiesInRequest() throws IOException, ServletException { + when(request.getPathInfo()).thenReturn("/resource"); + when(request.getCookies()).thenReturn(null); + sessionValidationFilter.doFilter(request, response, filterChain); + Mockito.verify(response, times(1)).sendRedirect(sessionValidationFilter.getFilterConfiguration().getRedirectURL()); + } + + @Test + public void noCookiesWithCorrectNameInRequest() throws IOException, ServletException { + when(request.getPathInfo()).thenReturn("/resource"); + String newNameNotContainsRealName = sessionValidationFilter.getFilterConfiguration().getCookieName().substring(1); + Cookie cookie = new Cookie("fake" + newNameNotContainsRealName + "fake2", RepresentationUtils.toRepresentation(new AuthenticationCookie("kuku"))); + when(request.getCookies()).thenReturn(new Cookie[]{cookie}); + sessionValidationFilter.doFilter(request, response, filterChain); + Mockito.verify(response, times(1)).sendRedirect(sessionValidationFilter.getFilterConfiguration().getRedirectURL()); + } + + @Test + public void cookieMaxSessionTimeTimedOut() throws IOException, ServletException, CipherUtilException { + when(request.getPathInfo()).thenReturn("/resource"); + AuthenticationCookie authenticationCookie = new AuthenticationCookie("kuku"); + // set max session time to timout value + long maxSessionTimeOut = sessionValidationFilter.getFilterConfiguration().getMaxSessionTimeOut(); + long startTime = authenticationCookie.getMaxSessionTime(); + long timeout = startTime - maxSessionTimeOut - 1000l; + authenticationCookie.setMaxSessionTime(timeout); + Cookie cookie = new Cookie(sessionValidationFilter.getFilterConfiguration().getCookieName(), AuthenticationCookieUtils.getEncryptedCookie(authenticationCookie, sessionValidationFilter.getFilterConfiguration())); + + when(request.getCookies()).thenReturn(new Cookie[]{cookie}); + sessionValidationFilter.doFilter(request, response, filterChain); + Mockito.verify(response, times(1)).sendRedirect(sessionValidationFilter.getFilterConfiguration().getRedirectURL()); + } + + @Test + public void cookieSessionIdle() throws IOException, ServletException, CipherUtilException { + when(request.getPathInfo()).thenReturn("/resource"); + AuthenticationCookie authenticationCookie = new AuthenticationCookie("kuku"); + // set session time to timout to idle + long idleSessionTimeOut = sessionValidationFilter.getFilterConfiguration().getSessionIdleTimeOut(); + long sessionStartTime = authenticationCookie.getCurrentSessionTime(); + long timeout = sessionStartTime - idleSessionTimeOut - 2000; + authenticationCookie.setCurrentSessionTime(timeout); + Cookie cookie = new Cookie(sessionValidationFilter.getFilterConfiguration().getCookieName(), AuthenticationCookieUtils.getEncryptedCookie(authenticationCookie, sessionValidationFilter.getFilterConfiguration())); + + when(request.getCookies()).thenReturn(new Cookie[]{cookie}); + sessionValidationFilter.doFilter(request, response, filterChain); + Mockito.verify(response, times(1)).sendRedirect(sessionValidationFilter.getFilterConfiguration().getRedirectURL()); + } + + @Test + public void requestThatPassFilter() throws IOException, ServletException, CipherUtilException { + when(request.getPathInfo()).thenReturn("/resource"); + + AuthenticationCookie authenticationCookie = new AuthenticationCookie("kuku"); + Cookie cookie = new Cookie(sessionValidationFilter.getFilterConfiguration().getCookieName(), AuthenticationCookieUtils.getEncryptedCookie(authenticationCookie, sessionValidationFilter.getFilterConfiguration())); + + when(request.getCookies()).thenReturn(new Cookie[]{cookie}); + sessionValidationFilter.doFilter(request, response, filterChain); + Mockito.verify(filterChain, times(1)).doFilter(request, response); + } + +// test validate contains + @Test + public void requestThatPassFilterWithCookieNameAsPartOfOtherString() throws IOException, ServletException, CipherUtilException { + when(request.getPathInfo()).thenReturn("/resource"); + + AuthenticationCookie authenticationCookie = new AuthenticationCookie("kuku"); + Cookie cookie = new Cookie("some" +sessionValidationFilter.getFilterConfiguration().getCookieName() + "Thing", AuthenticationCookieUtils.getEncryptedCookie(authenticationCookie, sessionValidationFilter.getFilterConfiguration())); + + when(request.getCookies()).thenReturn(new Cookie[]{cookie}); + sessionValidationFilter.doFilter(request, response, filterChain); + Mockito.verify(filterChain, times(1)).doFilter(request, response); + } + +} \ No newline at end of file -- cgit 1.2.3-korg