diff options
author | Yuli Shlosberg <ys9693@att.com> | 2019-01-07 16:23:36 +0200 |
---|---|---|
committer | Avi Gaffa <avi.gaffa@amdocs.com> | 2019-01-10 10:28:39 +0000 |
commit | a4eeb110b076672b3bb88f5e2f3420ae70c78f38 (patch) | |
tree | 73a205b1afa3ffca8057931cc080ff1b372af23a /openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org | |
parent | a1f23ec5e7cd191b76271b5f33c237bad38c61c6 (diff) |
Add restriction filter to onboarding
Change-Id: Ief36760c8d89ac3443c8b12bfdef09c2f83abfc3
Issue-ID: SDC-2039
Signed-off-by: Yuli Shlosberg <ys9693@att.com>
Diffstat (limited to 'openecomp-be/backend/openecomp-sdc-security-util/src/main/java/org')
13 files changed, 1209 insertions, 0 deletions
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<String> 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<String> 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<String> 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 <T> 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 <T> + * @return + */ + public static <T> T fromRepresentation(String json, Class<T> 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<len/2; i++){ + builder.setCharAt(i, '*'); + } + return builder.toString(); + } + return sensitiveData; + } + + /** + * @param strDataToEncrypt - plain string to encrypt + * Encrypt the Data + * a. Declare / Initialize the Data. Here the data is of type String + * b. Convert the Input Text to Bytes + * c. Encrypt the bytes using doFinal method + */ + public Either<String,String> 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<String,String> 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<String,String> 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<String> 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<String> 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<String> 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<String> 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<Cookie> 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<Cookie> extractAuthenticationCookies(Cookie[] cookies) { + String actualCookieName = filterConfiguration.getCookieName(); + log.debug("SessionValidationFilter: Extracting authentication cookies, {} cookies in request", cookies.length); + List<Cookie> 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<Cookie> 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() { + + } +} |