diff --git a/pom.xml b/pom.xml index c58b2b26e..97ca375f1 100644 --- a/pom.xml +++ b/pom.xml @@ -119,11 +119,43 @@ jsr305 2.0.0 - + + + + org.apache.httpcomponents + httpclient + 4.3.3 + + + com.google.code.gson + gson + 2.2.4 + + + + commons-pool + commons-pool + 1.6 + + + + org.apache.thrift + libthrift + 0.9.0 + + + + org.apache.commons + commons-collections4 + 4.0 + + + org.apache.curator curator-recipes diff --git a/src/main/java/com/hp/csbu/cc/middleware/AuthClient.java b/src/main/java/com/hp/csbu/cc/middleware/AuthClient.java new file mode 100644 index 000000000..8f20ac391 --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/AuthClient.java @@ -0,0 +1,30 @@ +package com.hp.csbu.cc.middleware; + +import java.io.IOException; +import java.util.Map; + +import org.apache.http.client.ClientProtocolException; +import org.apache.thrift.TException; +/*import org.apache.thrift.TException; + +import com.hp.csbu.cc.security.cs.thrift.service.AuthResponse; +import com.hp.csbu.cc.security.cs.thrift.service.ResourceException; +import com.hp.csbu.cc.security.cs.thrift.service.SigAuthRequest; */ + + +/** + * A client that can communicate to an authentication server for authentication. + * + * @author liemmn + * + */ +public interface AuthClient { + public Object validateTokenForServiceEndpointV2(String token, + String serviceIds, String endpointIds, boolean includeCatalog) + throws TException, ClientProtocolException; //ResourceException + public Object validateTokenForServiceEndpointV3(String token, + Map inputParams) throws TException, ClientProtocolException; //ResourceException + + //public AuthResponse validateSignature(SigAuthRequest request) throws ResourceException, TException; + +} diff --git a/src/main/java/com/hp/csbu/cc/middleware/AuthClientFactory.java b/src/main/java/com/hp/csbu/cc/middleware/AuthClientFactory.java new file mode 100644 index 000000000..2e9f78bdd --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/AuthClientFactory.java @@ -0,0 +1,145 @@ +package com.hp.csbu.cc.middleware; + +import org.apache.commons.pool.impl.GenericObjectPool; + +//import com.hp.csbu.cc.security.cs.thrift.service.CsThriftService.Client; + +/** + * A factory for building {@link AuthClient}s. + * + * @author liemmn + * + */ +public abstract class AuthClientFactory { + private static AuthClientFactory instance = null; + protected static GenericObjectPool pool; + + /** + * Build a AuthClientFactory. Singleton. + * + * @param host + * Auth host + * @param port + * Auth port + * @param timeout + * Auth connection timeout + * @param clientAuth + * 2-way SSL (if false, 1-way SSL is used) + * @param keyStore + * Keystore + * @param keyPass + * Keystore password + * @param trustStore + * Truststore + * @param trustPass + * Truststore password + * @param maxActive + * Maximum number of objects that can be allocated by the pool + * (checked out to clients, or idle awaiting checkout) at a given + * time. When non-positive, there is no limit to the number of + * objects that can be managed by the pool at one time. When + * maxActive is reached, the pool is said to be exhausted. The + * default setting for this parameter is 8. + * @param maxIdle + * Maximum number of objects that can sit idle in the pool at any + * time. When negative, there is no limit to the number of + * objects that may be idle at one time. The default setting for + * this parameter is 8. + * @param timeBetweenEvictionRunsMillis + * How long the eviction thread should sleep before "runs" of + * examining idle objects. When non-positive, no eviction thread + * will be launched. The default setting for this parameter is -1 + * (i.e., idle object eviction is disabled by default). + * @param minEvictableIdleTimeMillis + * Minimum amount of time that an object may sit idle in the pool + * before it is eligible for eviction due to idle time. When + * non-positive, no object will be dropped from the pool due to + * idle time alone. This setting has no effect unless + * timeBetweenEvictionRunsMillis > 0. The default setting for + * this parameter is 30 minutes. + * @param adminToken + * Admin token for use with vanilla Keystone. + * + * @return AuthClientFactory singleton. + * @throws Exception + */ + public static synchronized AuthClientFactory build(String host, int port, + int timeout, boolean clientAuth, String keyStore, String keyPass, + String trustStore, String trustPass, int maxActive, int maxIdle, + long timeBetweenEvictionRunsMillis, + long minEvictableIdleTimeMillis, String adminToken) + throws Exception { + if (instance == null) { + /*if (port == 9543) { + instance = new ThriftClientFactory(host, port, timeout, + clientAuth, keyStore, keyPass, trustStore, trustPass); + } else {*/ + instance = new HttpClientFactory(host, port, timeout, + clientAuth, keyStore, keyPass, trustStore, trustPass, + adminToken, maxActive, timeBetweenEvictionRunsMillis, + minEvictableIdleTimeMillis); + // } + // Pool tweaking + pool.setMaxActive(maxActive); + pool.setMaxIdle(maxIdle); + pool.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); + pool.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); + } + return instance; + } + + /** + * Get a client. Don't forget to {@link #recycleClient(Client)} after you + * are done using it, successfully or not. + * + * @return Client + * @throws Exception + */ + public AuthClient getClient() { + try { + return (AuthClient) pool.borrowObject(); + } catch (Exception e) { + throw new AuthConnectionException("Failed to get a client "+ e.getMessage(), e); + } + } + + /** + * Recycle the client for next usage. + * + * @param client + * Client to recycle + * @throws Exception + */ + public void recycle(AuthClient client) { + try { + pool.returnObject(client); + } catch (Exception e) { + throw new AuthConnectionException("Failed to recycle client", e); + } + } + + /** + * Call this if the client is unusable (i.e., exception). + * + * @param client + * Client to discard. + */ + public void discard(AuthClient client) { + try { + pool.invalidateObject(client); + } catch (Exception e) { + throw new AuthConnectionException("Failed to destroy client", e); + } + } + + /** + * Shut down this factory. + */ + public void shutdown() { + try { + pool.close(); + } catch (Exception e) { + throw new AuthConnectionException("Failed to close client pool", e); + } + } +} diff --git a/src/main/java/com/hp/csbu/cc/middleware/AuthConnectionException.java b/src/main/java/com/hp/csbu/cc/middleware/AuthConnectionException.java new file mode 100644 index 000000000..38d417be5 --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/AuthConnectionException.java @@ -0,0 +1,18 @@ +package com.hp.csbu.cc.middleware; + +/** + * An exception to indicate any connection issue. + * + * @author liemmn + * + */ +public class AuthConnectionException extends RuntimeException { + private static final long serialVersionUID = 4318025130590973448L; + + public AuthConnectionException(String msg) { + super(msg); + } + public AuthConnectionException(String msg, Exception e) { + super(msg, e); + } +} diff --git a/src/main/java/com/hp/csbu/cc/middleware/AuthConstants.java b/src/main/java/com/hp/csbu/cc/middleware/AuthConstants.java new file mode 100644 index 000000000..e4735b472 --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/AuthConstants.java @@ -0,0 +1,183 @@ +package com.hp.csbu.cc.middleware; + +public interface AuthConstants { + /** 'Confirmed' or 'Invalid' */ + public static enum IdentityStatus { + Confirmed, Invalid + } + + // =============================== TOKEN =================================== + /** Credential (token) header */ + public static final String TOKEN = "X-AUTH-TOKEN"; + /** Auth status parameter */ + public static final String AUTH_IDENTITY_STATUS = "X-IDENTITY-STATUS"; + /** Auth user Id parameter */ + public static final String AUTH_USER_ID = "X-USER-ID"; + /** Auth user name parameter */ + public static final String AUTH_USER_NAME = "X-USER-NAME"; + + /** Auth user roles parameter, comma-separated roles */ + public static final String AUTH_ROLES = "X-ROLES"; + /** json encoded keystone service catalog */ + public static final String AUTH_SERVICE_CATALOG = "X-SERVICE-CATALOG"; + /** Service Ids initialization parameter */ + public static final String SERVICE_IDS = "ServiceIds"; + /** Endpoint Ids initialization parameter */ + public static final String ENDPOINT_IDS = "EndpointIds"; + /** Keystone admin token for use in vanilla Keystone */ + public static final String ADMIN_TOKEN = "AdminToken"; + + // ============================ CONNECTION ================================= + /** Auth server initialization parameter */ + public static final String SERVER_VIP = "ServerVIP"; + /** Auth server port: 9543 for Thrift, 35357 for HTTP. */ + public static final String SERVER_PORT = "ServerPort"; + /** connection timeout initialization parameter */ + public static final String CONN_TIMEOUT = "ConnTimeout"; + /** 2-way SSL initialization parameter: True or False */ + public static final String CONN_SSL_CLIENT_AUTH = "ConnSSLClientAuth"; + /** SSL keystore initialization parameter */ + public static final String KEYSTORE = "Keystore"; + /** SSL keystore password initialization parameter */ + public static final String KEYSTORE_PASS = "KeystorePass"; + /** SSL truststore initialization parameter */ + public static final String TRUSTSTORE = "Truststore"; + /** SSL truststore password initialization parameter */ + public static final String TRUSTSTORE_PASS = "TruststorePass"; + + // ============================== POOLING ================================== + /** + * Maximum number of objects that can be allocated by the pool (checked out + * to clients, or idle awaiting checkout) at a given time. When + * non-positive, there is no limit to the number of objects that can be + * managed by the pool at one time. When maxActive is reached, the pool is + * said to be exhausted. The default setting for this parameter is 8. + */ + public static final String CONN_POOL_MAX_ACTIVE = "ConnPoolMaxActive"; + /** + * Maximum number of objects that can sit idle in the pool at any time. When + * negative, there is no limit to the number of objects that may be idle at + * one time. The default setting for this parameter is 8. + */ + public static final String CONN_POOL_MAX_IDLE = "ConnPoolMaxIdle"; + /** + * How long the eviction thread should sleep before "runs" of examining idle + * objects. When non-positive, no eviction thread will be launched. The + * default setting for this parameter is -1 (i.e., idle object eviction is + * disabled by default). + */ + public static final String CONN_POOL_EVICT_PERIOD = "ConnPoolEvictPeriod"; + /** + * Minimum amount of time that an object may sit idle in the pool before it + * is eligible for eviction due to idle time. When non-positive, no object + * will be dropped from the pool due to idle time alone. This setting has no + * effect unless ConnPoolEvictPeriod > 0. The default setting for this + * parameter is 30 minutes. + */ + public static final String CONN_POOL_MIN_IDLE_TIME = "ConnPoolMinIdleTime"; + + // ============================== CACHING ================================== + /** Memcache hosts */ + public static final String MEMCACHE_HOSTS = "MemcacheHosts"; + /** Memcache connection timeout */ + public static final String MEMCACHE_TIMEOUT = "MemcacheTimeout"; + /** Memcache encryption */ + public static final String MEMCACHE_ENCRYPT = "MemcacheEncrypt"; + + /** Number of connection timeout retries **/ + public static final String CONN_TIMEOUT_RETRIES = "ConnRetryTimes"; + /** Number of connection timeout retries **/ + public static final String PAUSE_BETWEEN_RETRIES = "ConnRetryInterval"; + /** Authentication decision is forwarded to next filter **/ + public static final String DELAY_AUTH_DECISION = "DelayAuthDecision"; + + public static final String SIGNATURE_METHOD = "HmacSHA1"; + + /** Version of CS to authenticate the credentials **/ + public static final String AUTH_VERSION = "AuthVersion"; + + /** Include Service Catalog as part of Authentication Response **/ + public static final String INCLUDE_SERVICE_CATALOG = "IncludeServiceCatalog"; + + /** + * Identity service managed unique identifier, string. Only present if this + * is a project-scoped v3 token, or a tenant-scoped v2 token. + **/ + public static final String AUTH_PROJECT_ID = "X-PROJECT-ID"; + + /** + * Project name, unique within owning domain, string. Only present if this + * is a project-scoped v3 token, or a tenant-scoped v2 token. + **/ + public static final String AUTH_PROJECT_NAME = "X-PROJECT-NAME"; + + /** + * Identity service managed unique identifier of owning domain of project, + * string. Only present if this is a project-scoped v3 token. If this + * variable is set, this indicates that the PROJECT_NAME can only be assumed + * to be unique within this domain. + **/ + public static final String AUTH_PROJECT_DOMAIN_ID = "X-PROJECT-DOMAIN-ID"; + + /** + * Name of owning domain of project, string. Only present if this is a + * project-scoped v3 token. If this variable is set, this indicates that the + * PROJECT_NAME can only be assumed to be unique within this domain. + **/ + public static final String AUTH_PROJECT_DOMAIN_NAME = "X-PROJECT-DOMAIN-NAME"; + + /** + * Identity service managed unique identifier of owning domain of user, + * string. If this variable is set, this indicates that the USER_NAME can + * only be assumed to be unique within this domain. + **/ + public static final String AUTH_USER_DOMAIN_ID = "X-USER-DOMAIN-ID"; + + /** + * Name of owning domain of user, string. If this variable is set, this + * indicates that the USER_NAME can only be assumed to be unique within this + * domain. + **/ + public static final String AUTH_USER_DOMAIN_NAME = "X-USER-DOMAIN-NAME"; + + /** + * Identity service managed unique identifier, string. Only present if this + * is a domain-scoped v3 token. + **/ + public static final String AUTH_DOMAIN_ID = "X-DOMAIN-ID"; + + /** + * Unique domain name, string. Only present if this is a domain-scoped v3 + * token. + **/ + public static final String AUTH_DOMAIN_NAME = "X-DOMAIN-NAME"; + + public static final String AUTH_HP_IDM_ROLES = "X-HP-IDM-Non-Tenant-Roles"; + + public static final String REMOTE_HOST = "RemoteHost"; + public static final String REMOTE_ADDR = "RemoteAddress"; + + // Depracated Headers. + /** Auth user roles parameter, comma-separated roles */ + public static final String AUTH_ROLE = "X-ROLE"; + /** Auth tenant Id parameter */ + public static final String AUTH_TENANT_ID = "X-TENANT-ID"; + /** Auth tenant name parameter */ + public static final String AUTH_TENANT_NAME = "X-TENANT-NAME"; + /** Auth tenant name parameter */ + public static final String AUTH_TENANT = "X-TENANT"; + /** + * *Deprecated* in favor of HTTP_X_USER_ID and HTTP_X_USER_NAME User name, + * unique within owning domain, string + **/ + public static final String AUTH_USER = "X-USER"; + + public static final String AUTH_SUBJECT_TOKEN = "X-Subject-Token"; + public static final String ADMIN_USER = "AdminUser"; + public static final String ADMIN_PASSWORD = "AdminPassword"; + public static final String ADMIN_AUTH_METHOD = "AdminAuthMethod"; + public static final String ADMIN_ACCESS_KEY = "AdminAccessKey"; + public static final String ADMIN_SECRET_KEY = "AdminSecretKey"; + public static final String ADMIN_PROJECT_ID = "AdminProjectId"; + +} \ No newline at end of file diff --git a/src/main/java/com/hp/csbu/cc/middleware/AuthException.java b/src/main/java/com/hp/csbu/cc/middleware/AuthException.java new file mode 100644 index 000000000..ecc48f886 --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/AuthException.java @@ -0,0 +1,19 @@ +package com.hp.csbu.cc.middleware; + +/** + * An exception to indicate any authentication error. + * + * @author liemmn + * + */ +public class AuthException extends RuntimeException { + private static final long serialVersionUID = 2287073516214658461L; + + public AuthException(String msg) { + super(msg); + } + + public AuthException(String msg, Exception e) { + super(msg, e); + } +} diff --git a/src/main/java/com/hp/csbu/cc/middleware/CatalogV3.java b/src/main/java/com/hp/csbu/cc/middleware/CatalogV3.java new file mode 100644 index 000000000..992bbfeb0 --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/CatalogV3.java @@ -0,0 +1,31 @@ +package com.hp.csbu.cc.middleware; + +import java.util.List; + +public class CatalogV3 { + + public String getId() { + return id; + } + public void setId(String id) { + this.id = id; + } + public String getType() { + return type; + } + public void setType(String type) { + this.type = type; + } + public List getEndPoints() { + return endPoints; + } + public void setEndPoints(List endPoints) { + this.endPoints = endPoints; + } + String id; + String type; + List endPoints; + +} + + diff --git a/src/main/java/com/hp/csbu/cc/middleware/Config.java b/src/main/java/com/hp/csbu/cc/middleware/Config.java new file mode 100644 index 000000000..5ac011d3e --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/Config.java @@ -0,0 +1,294 @@ +package com.hp.csbu.cc.middleware; + +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Config implements AuthConstants { + + // Thee faithful logger + private static final Logger logger = LoggerFactory + .getLogger(Config.class); + + private static final Config instance = new Config(); + + private static final String PASSWORD = "password"; + private static final String ACCESS_KEY = "accesskey"; + + // Application wide init param -- ServletContext + private ServletContext context = null; + + // Memcache client--There shall only be one + ///private MemcacheCrypt client = null; + + private TokenCache client = null; + + // Auth client factory + private AuthClientFactory factory = null; + + // The service IDs that this filter serves + private String serviceIds; + + // The optional endpoint IDs that this filter serves + private String endpointIds; + + // Memcache timeout value + private long memCacheTimeOut; + + // flag to set if auth decision can be delegated to next filter + private boolean delayAuthDecision; + + // retries and pauseTime configuration for retry logic + private int retries; + private int pauseTime; + + // configuration to authenticate against CS api + private String authVersion; + + // flag to include catalog in the response + private boolean includeCatalog; + + // configuration for admin authentication method to be used for 2-way SSL + private String adminAuthMethod; + + // configuration for admin default project + private String adminProjectId; + + // flag to indicate if the filter is already intialized with required parameters + private volatile boolean initialized = false; + + //context is not getting properly filed so will use FilterConfig + private FilterConfig filterConfig; + private Config() { + } + + public static Config getInstance() { + return instance; + } + + public synchronized void initialize(FilterConfig config) throws ServletException { + this.context = config.getServletContext(); + this.filterConfig = config; + + try { + // Initialize serviceIds... + //serviceIds = context.getInitParameter(SERVICE_IDS); + serviceIds = filterConfig.getInitParameter(SERVICE_IDS); + // Initialize endpointIds... + //endpointIds = context.getInitParameter(ENDPOINT_IDS); + endpointIds = filterConfig.getInitParameter(ENDPOINT_IDS); + + String somthing = context + .getInitParameter(SERVER_PORT); + // Initialize auth server connection parameters... + //String host = context.getInitParameter(SERVER_VIP); + String host = filterConfig.getInitParameter(SERVER_VIP); + + //int port = Integer.parseInt(context + // .getInitParameter(SERVER_PORT)); + + int port = Integer.parseInt(filterConfig.getInitParameter(SERVER_PORT)); + + // HP Keystone Server only supports authentication against + // V3.0 api + authVersion = getValue(AUTH_VERSION, "v3.0"); + + if ((serviceIds == null || serviceIds.isEmpty()) + && (endpointIds == null || endpointIds.isEmpty()) + && authVersion.equalsIgnoreCase("v2.0")) { + throw new Throwable("Need to specify " + SERVICE_IDS); + } + + // Initialize memcache... + String cacheHosts = context.getInitParameter(MEMCACHE_HOSTS); + boolean isEncrypted = Boolean.valueOf(context + .getInitParameter(MEMCACHE_ENCRYPT)); + memCacheTimeOut = getValue(MEMCACHE_TIMEOUT, 2000L); + /* if (cacheHosts != null && !cacheHosts.isEmpty()) { + this.client = new MemcacheCrypt(cacheHosts, isEncrypted); + }*/ + + this.client = new TokenCache<>(getValue(MEMCACHE_TIMEOUT,2000L)); + // Initialize Certificates + /*String keyStore = context.getInitParameter(KEYSTORE); + String keyPass = context.getInitParameter(KEYSTORE_PASS); + String trustStore = context.getInitParameter(TRUSTSTORE); + String trustPass = context.getInitParameter(TRUSTSTORE_PASS);*/ + + String keyStore = filterConfig.getInitParameter(KEYSTORE); + String keyPass = filterConfig.getInitParameter(KEYSTORE_PASS); + String trustStore = filterConfig.getInitParameter(TRUSTSTORE); + String trustPass = filterConfig.getInitParameter(TRUSTSTORE_PASS); + + String adminToken = getValue(ADMIN_TOKEN, ""); + int timeout = getValue(CONN_TIMEOUT, 0); + boolean clientAuth = getValue(CONN_SSL_CLIENT_AUTH, true); + int maxActive = getValue(CONN_POOL_MAX_ACTIVE, 3); + int maxIdle = getValue(CONN_POOL_MAX_IDLE, 3); + long evictPeriod = getValue(CONN_POOL_EVICT_PERIOD, 60000L); + long minIdleTime = getValue(CONN_POOL_MIN_IDLE_TIME, 90000L); + retries = getValue(CONN_TIMEOUT_RETRIES, 3); + pauseTime = getValue(PAUSE_BETWEEN_RETRIES, 100); + delayAuthDecision = getValue(DELAY_AUTH_DECISION, false); + includeCatalog = getValue(INCLUDE_SERVICE_CATALOG, true); + adminAuthMethod = getValue(ADMIN_AUTH_METHOD, ""); + adminProjectId = getValue(ADMIN_PROJECT_ID, ""); + this.factory = AuthClientFactory.build(host, port, timeout, + clientAuth, keyStore, keyPass, trustStore, trustPass, + maxActive, maxIdle, evictPeriod, minIdleTime, adminToken); + verifyRequiredParamsForAuthMethod(); + logger.info("Auth host (2-way SSL: " + clientAuth + "): " + host); + logger.info("Read Servlet Initialization Parameters "); + initialized = true; + } catch (Throwable t) { + logger.error("Failed to read Servlet Initialization Parameters ", + t.getMessage()); + throw new ServletException( + "Failed to read Servlet Initialization Parameters :: " + + t.getMessage(), t); + } + } + + public boolean isInitialized() { + return initialized; + } + + protected String getAdminProject() { + return adminProjectId; + } + + protected String getAdminAccessKey() { + if (context.getAttribute(ADMIN_ACCESS_KEY) != null) { + return (String) context.getAttribute(ADMIN_ACCESS_KEY); + } else { + return getValue(ADMIN_ACCESS_KEY, ""); + } + } + + protected String getAdminSecretKey() { + if (context.getAttribute(ADMIN_SECRET_KEY) != null) { + return (String) context.getAttribute(ADMIN_SECRET_KEY); + } else { + return getValue(ADMIN_SECRET_KEY, ""); + } + } + + protected String getAdminAuthMethod() { + return adminAuthMethod; + } + + protected String getAdminUser() { + if (context.getAttribute(ADMIN_USER) != null) { + return (String) context.getAttribute(ADMIN_USER); + } else { + return getValue(ADMIN_USER, ""); + } + } + + protected String getAdminPassword() { + if (context.getAttribute(ADMIN_PASSWORD) != null) { + return (String) context.getAttribute(ADMIN_PASSWORD); + } else { + return getValue(ADMIN_PASSWORD, ""); + } + } + + protected boolean isIncludeCatalog() { + return includeCatalog; + } + + protected long getMemCacheTimeOut() { + return memCacheTimeOut; + } + + protected String getAuthVersion() { + return authVersion; + } + + protected void setMemCacheTimeOut(long memCacheTimeOut) { + this.memCacheTimeOut = memCacheTimeOut; + } + + // Is caching enabled? + protected boolean isCaching() { + //return this.client != null; + return false; + } + + protected ServletContext getConfig() { + return context; + } + + /*protected MemcacheCrypt getClient() { + return client; + }*/ + protected TokenCache getClient() { + return client; + } + + protected AuthClientFactory getFactory() { + return factory; + } + + protected String getServiceIds() { + return serviceIds; + } + + protected String getEndpointIds() { + return endpointIds; + } + + protected boolean isDelayAuthDecision() { + return delayAuthDecision; + } + + protected int getRetries() { + return retries; + } + + protected int getPauseTime() { + return pauseTime; + } + + private T getValue(String paramName, T defaultValue) { + Class type = defaultValue.getClass(); + //String initparamValue = context.getInitParameter(paramName); + String initparamValue = filterConfig.getInitParameter(paramName); + if (initparamValue != null && !initparamValue.isEmpty()) { + if (type.equals(Integer.class)) { + int paramValue = Integer.parseInt(initparamValue); + return (T) type.cast(paramValue); + } else if (type.equals(Long.class)) { + long paramValue = Long.parseLong(initparamValue); + return (T) type.cast(paramValue); + } else if (type.equals(Boolean.class)) { + boolean paramValue = Boolean.parseBoolean(initparamValue); + return (T) type.cast(paramValue); + } else if (type.equals(String.class)) { + return (T) type.cast(initparamValue); + } + } + return defaultValue; + } + + private void verifyRequiredParamsForAuthMethod() { + if (adminAuthMethod.equalsIgnoreCase(PASSWORD)) { + if (getAdminUser().isEmpty() || getAdminPassword().isEmpty()) { + String msg = String + .format("admin user and password must be specified if admin auth method is %s", + adminAuthMethod); + throw new AuthException(msg); + } + } else if (adminAuthMethod.equalsIgnoreCase(ACCESS_KEY)) { + if (getAdminAccessKey().isEmpty() || getAdminSecretKey().isEmpty()) { + String msg = String + .format("admin access and secret key must be specified if admin auth method is %s", + adminAuthMethod); + throw new AuthException(msg); + } + } + } +} diff --git a/src/main/java/com/hp/csbu/cc/middleware/DecryptionException.java b/src/main/java/com/hp/csbu/cc/middleware/DecryptionException.java new file mode 100644 index 000000000..e8ef6783b --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/DecryptionException.java @@ -0,0 +1,19 @@ +package com.hp.csbu.cc.middleware; + +/** + * An exception caused for memcache decryption issues. + * + * @author liemmn + * + */ +public class DecryptionException extends RuntimeException { + private static final long serialVersionUID = 5463487714560524511L; + + public DecryptionException(String msg) { + super(msg); + } + + public DecryptionException(String msg, Exception e) { + super(msg, e); + } +} diff --git a/src/main/java/com/hp/csbu/cc/middleware/EncryptionException.java b/src/main/java/com/hp/csbu/cc/middleware/EncryptionException.java new file mode 100644 index 000000000..56cb82b12 --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/EncryptionException.java @@ -0,0 +1,19 @@ +package com.hp.csbu.cc.middleware; + +/** + * Memcache encryption exception. + * + * @author liemmn + * + */ +public class EncryptionException extends RuntimeException { + private static final long serialVersionUID = 8249423387842730866L; + + public EncryptionException(String msg) { + super(msg); + } + + public EncryptionException(String msg, Exception e) { + super(msg, e); + } +} diff --git a/src/main/java/com/hp/csbu/cc/middleware/ExceptionHandlerUtil.java b/src/main/java/com/hp/csbu/cc/middleware/ExceptionHandlerUtil.java new file mode 100644 index 000000000..4aa14e54b --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/ExceptionHandlerUtil.java @@ -0,0 +1,42 @@ +package com.hp.csbu.cc.middleware; + +public class ExceptionHandlerUtil { + + public final static String SERVICE_UNAVAILABLE = "Service Unavailable"; + public final static String UNAUTHORIZED_TOKEN = "Unauthorized Token"; + public final static String INTERNAL_SERVER_ERROR = "Internal Server Error"; + + private ExceptionHandlerUtil() { + } + + public static String getStatusText(int errorCode) { + if (errorCode == 401) { + return UNAUTHORIZED_TOKEN; + } + if (errorCode == 503) { + return SERVICE_UNAVAILABLE; + } + if (errorCode == 500) { + return INTERNAL_SERVER_ERROR; + } + return "Unknown Error"; + + } + + public static TokenExceptionHandler lookUpTokenException(Exception ex) { + try { + return TokenExceptionHandler.valueOf(ex.getClass().getSimpleName()); + } catch (IllegalArgumentException iae) { + return TokenExceptionHandler.valueOf("ResourceException"); + } + } + + /*public static SignatureExceptionHandler lookUpSignatureException(Exception ex) { + try { + return SignatureExceptionHandler.valueOf(ex.getClass().getSimpleName()); + } catch (IllegalArgumentException iae) { + return SignatureExceptionHandler.valueOf("ResourceException"); + } + } */ + +} diff --git a/src/main/java/com/hp/csbu/cc/middleware/FilterUtils.java b/src/main/java/com/hp/csbu/cc/middleware/FilterUtils.java new file mode 100644 index 000000000..5942a65d3 --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/FilterUtils.java @@ -0,0 +1,471 @@ +package com.hp.csbu.cc.middleware; + +import static com.hp.csbu.cc.middleware.AuthConstants.AUTH_DOMAIN_ID; +import static com.hp.csbu.cc.middleware.AuthConstants.AUTH_DOMAIN_NAME; +import static com.hp.csbu.cc.middleware.AuthConstants.AUTH_HP_IDM_ROLES; +import static com.hp.csbu.cc.middleware.AuthConstants.AUTH_IDENTITY_STATUS; +import static com.hp.csbu.cc.middleware.AuthConstants.AUTH_PROJECT_DOMAIN_ID; +import static com.hp.csbu.cc.middleware.AuthConstants.AUTH_PROJECT_DOMAIN_NAME; +import static com.hp.csbu.cc.middleware.AuthConstants.AUTH_PROJECT_ID; +import static com.hp.csbu.cc.middleware.AuthConstants.AUTH_PROJECT_NAME; +import static com.hp.csbu.cc.middleware.AuthConstants.AUTH_ROLE; +import static com.hp.csbu.cc.middleware.AuthConstants.AUTH_ROLES; +import static com.hp.csbu.cc.middleware.AuthConstants.AUTH_SERVICE_CATALOG; +import static com.hp.csbu.cc.middleware.AuthConstants.AUTH_TENANT; +import static com.hp.csbu.cc.middleware.AuthConstants.AUTH_TENANT_NAME; +import static com.hp.csbu.cc.middleware.AuthConstants.AUTH_USER; +import static com.hp.csbu.cc.middleware.AuthConstants.AUTH_USER_DOMAIN_ID; +import static com.hp.csbu.cc.middleware.AuthConstants.AUTH_USER_DOMAIN_NAME; +import static com.hp.csbu.cc.middleware.AuthConstants.AUTH_USER_ID; +import static com.hp.csbu.cc.middleware.AuthConstants.AUTH_TENANT_ID; +import static com.hp.csbu.cc.middleware.AuthConstants.AUTH_USER_NAME; +import static com.hp.csbu.cc.middleware.AuthConstants.IdentityStatus; + +/*import com.hp.csbu.cc.security.cs.thrift.service.AuthResponseV2; +import com.hp.csbu.cc.security.cs.thrift.service.AuthResponseV3; +import com.hp.csbu.cc.security.cs.thrift.service.EndpointV3; +import com.hp.csbu.cc.security.cs.thrift.service.Role; +import com.hp.csbu.cc.security.cs.thrift.service.ServiceForCatalogV3; +import com.hp.csbu.cc.security.cs.thrift.service.V3Role; +*/ + +import java.io.IOException; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.TimeoutException; + +import javax.servlet.ServletRequest; + +//import net.rubyeye.xmemcached.exception.MemcachedException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +public class FilterUtils { + + private FilterUtils() { + } + + private static final Config appConfig = Config.getInstance(); + + private static final Gson gson = new GsonBuilder() + .excludeFieldsWithModifiers(Modifier.PRIVATE, Modifier.FINAL) + .create(); + + // Thee faithful logger + private static final Logger logger = LoggerFactory + .getLogger(FilterUtils.class); + + public static void destroyFilter() { + /*MemcacheCrypt client = appConfig.getClient(); + // Shutdown memcache + if (client != null) { + try { + client.shutdown(); + } catch (IOException e) { + logger.warn("Failed to shutdown memcache", e); + } + } */ + + AuthClientFactory factory = appConfig.getFactory(); + // Shutdown factory + if (factory != null) { + factory.shutdown(); + } + } + + public static ServletRequest wrapRequestFromHttpResponse( + ServletRequest req, String data) { + if (appConfig.getAuthVersion().equalsIgnoreCase("v2.0")) { + wrapRequestFromHttpV2Response(req, data); + + } else { + wrapRequestFromHttpV3Response(req, data); + } + return req; + } + + private static void wrapRequestFromHttpV3Response(ServletRequest req, + String data) { + StringBuilder tenants = new StringBuilder(); + StringBuilder nonTenants = new StringBuilder(); + JsonParser jp = new JsonParser(); + JsonObject token = jp.parse(data).getAsJsonObject().get("token") + .getAsJsonObject(); + // Domain Scoped Token + if (token.get("domain") != null) { + JsonObject domain = token.get("domain").getAsJsonObject(); + req.setAttribute(AUTH_DOMAIN_ID, domain.get("id").getAsString()); + if (domain.get("name") != null) { + req.setAttribute(AUTH_DOMAIN_NAME, domain.get("name") + .getAsString()); + } + } + // Project Scoped Token + if (token.get("project") != null) { + JsonObject project = token.get("project").getAsJsonObject(); + req.setAttribute(AUTH_PROJECT_ID, project.get("id").getAsString()); + req.setAttribute(AUTH_PROJECT_NAME, project.get("name") + .getAsString()); + + JsonObject projectDomain = project.get("domain").getAsJsonObject(); + // special case where the value of id is null and the + // projectDomain.get("id") != null + if (!projectDomain.get("id").equals(new JsonNull())) { + req.setAttribute(AUTH_PROJECT_DOMAIN_ID, projectDomain + .get("id").getAsString()); + } + if (projectDomain.get("name") != null) { + req.setAttribute(AUTH_PROJECT_DOMAIN_NAME, + projectDomain.get("name")); + } + } + // User info + if (token.get("user") != null) { + JsonObject user = token.get("user").getAsJsonObject(); + req.setAttribute(AUTH_USER_ID, user.get("id").getAsString()); + req.setAttribute(AUTH_USER_NAME, user.get("name").getAsString()); + + JsonObject userDomain = user.get("domain").getAsJsonObject(); + if (userDomain.get("id") != null) { + req.setAttribute(AUTH_USER_DOMAIN_ID, userDomain.get("id") + .getAsString()); + } + if (userDomain.get("name") != null) { + req.setAttribute(AUTH_USER_DOMAIN_NAME, userDomain.get("name") + .getAsString()); + } + + } + // Roles + JsonArray roles = token.getAsJsonArray("roles"); + if (roles != null) { + Iterator it = roles.iterator(); + while (it.hasNext()) { + JsonObject role = it.next().getAsJsonObject(); + if (role.get("HP-IDM") != null) { + JsonObject hpIdm = role.get("HP-IDM").getAsJsonObject(); + if (hpIdm.get("projectId") != null) { + tenants.append(","); + tenants.append(role.get("name").getAsString()); + } else { + nonTenants.append(","); + nonTenants.append(role.get("name").getAsString()); + } + } + } + } + String tenantRoles = (tenants.length() > 0) ? tenants.substring(1) + : tenants.toString(); + String nonTenantRoles = (nonTenants.length() > 0) ? nonTenants + .substring(1) : nonTenants.toString(); + if (!tenantRoles.equals("")) { + req.setAttribute(AUTH_ROLES, tenantRoles); + } + if (!nonTenantRoles.equals("")) { + req.setAttribute(AUTH_HP_IDM_ROLES, nonTenantRoles); + } + // Catalog + if (token.get("catalog") != null && appConfig.isIncludeCatalog()) { + JsonArray catalog = token.get("catalog").getAsJsonArray(); + req.setAttribute(AUTH_SERVICE_CATALOG, catalog.toString()); + } + } + + private static void wrapRequestFromHttpV2Response(ServletRequest req, + String data) { + StringBuilder tenants = new StringBuilder(); + StringBuilder nonTenants = new StringBuilder(); + JsonParser jp = new JsonParser(); + JsonObject access = jp.parse(data).getAsJsonObject().get("access") + .getAsJsonObject(); + JsonObject token = access.get("token").getAsJsonObject(); + + // Tenant info + if (token.get("tenant") != null) { + JsonObject tenant = token.get("tenant").getAsJsonObject(); + + String id = tenant.get("id").getAsString(); + String name = tenant.get("name").getAsString(); + if (id != null) + req.setAttribute(AUTH_TENANT_ID, id); + if (name != null) + req.setAttribute(AUTH_TENANT_NAME, name); + } + // User info + if (access.get("user") != null) { + JsonObject user = access.get("user").getAsJsonObject(); + + String userId = user.get("id").getAsString(); + String username = user.get("name").getAsString(); + if (userId != null) + req.setAttribute(AUTH_USER_ID, userId); + if (username != null) + req.setAttribute(AUTH_USER_NAME, username); + // Roles + JsonArray roles = user.getAsJsonArray("roles"); + if (roles != null) { + Iterator it = roles.iterator(); + while (it.hasNext()) { + JsonObject role = it.next().getAsJsonObject(); + if (role.get("tenantId") != null) { + tenants.append(","); + tenants.append(role.get("name").getAsString()); + } else { + nonTenants.append(","); + nonTenants.append(role.get("name").getAsString()); + } + } + } + String tenantRoles = (tenants.length() > 0) ? tenants.substring(1) + : tenants.toString(); + if (!tenantRoles.equals("")) { + req.setAttribute(AUTH_ROLES, tenantRoles); + } + String nonTenantRoles = (nonTenants.length() > 0) ? nonTenants + .substring(1) : nonTenants.toString(); + if (!nonTenantRoles.equals("")) { + req.setAttribute(AUTH_HP_IDM_ROLES, nonTenantRoles); + } + } + // Service catalog + if (access.get("serviceCatalog") != null + && appConfig.isIncludeCatalog()) { + JsonArray serviceCatalog = access.get("serviceCatalog") + .getAsJsonArray(); + req.setAttribute(AUTH_SERVICE_CATALOG, serviceCatalog.toString()); + } + } + + public static ServletRequest wrapRequest(ServletRequest req, Object data) { + if (data == null) { + req.setAttribute(AUTH_IDENTITY_STATUS, + IdentityStatus.Invalid.toString()); + logger.debug("Failed Authentication. Setting identity status header to Invalid"); + } + req.setAttribute(AUTH_IDENTITY_STATUS, + IdentityStatus.Confirmed.toString()); + //if (data instanceof String) { + wrapRequestFromHttpResponse(req, ((String) data)); + //} else { + // wrapRequestFromThriftResponse(req, data); + //} + return req; + } + + /*private static void wrapRequestFromThriftResponse(ServletRequest req, + Object data) { + StringBuilder tenants = new StringBuilder(); + StringBuilder nonTenants = new StringBuilder(); + if (data instanceof AuthResponseV2) { + AuthResponseV2 auth = (AuthResponseV2) data; + req.setAttribute(AUTH_TENANT_ID, auth.userInfo.tenantId); + req.setAttribute(AUTH_TENANT_NAME, auth.userInfo.tenantName); + req.setAttribute(AUTH_USER_ID, auth.userInfo.userId); + req.setAttribute(AUTH_USER_NAME, auth.userInfo.username); + getRoles(auth.userInfo.roles, tenants, nonTenants); + String tenantRoles = (tenants.length() > 0) ? tenants.substring(1) + : tenants.toString(); + if (!tenantRoles.equals("")) { + req.setAttribute(AUTH_ROLES, tenantRoles); + } + String nonTenantRoles = (nonTenants.length() > 0) ? nonTenants + .substring(1) : nonTenants.toString(); + if (!nonTenantRoles.equals("")) { + req.setAttribute(AUTH_HP_IDM_ROLES, nonTenantRoles); + } + if (auth.getServiceCatalog() != null) { + req.setAttribute(AUTH_SERVICE_CATALOG, + gson.toJson(auth.getServiceCatalog())); + } + + } else if (data instanceof AuthResponseV3) { + AuthResponseV3 auth = (AuthResponseV3) data; + if (auth.getToken().getDomain() != null) { + req.setAttribute(AUTH_DOMAIN_ID, auth.getToken().getDomain() + .getId()); + if (auth.getToken().getDomain().getName() != null) { + req.setAttribute(AUTH_DOMAIN_NAME, auth.getToken() + .getDomain().getName()); + } + } else if (auth.getToken().getProject() != null) { + req.setAttribute(AUTH_PROJECT_ID, auth.getToken().getProject() + .getId()); + req.setAttribute(AUTH_PROJECT_NAME, auth.getToken() + .getProject().getName()); + req.setAttribute(AUTH_PROJECT_DOMAIN_ID, auth.getToken() + .getProject().getDomain().getId()); + if (auth.getToken().getProject().getDomain().getName() != null) { + req.setAttribute(AUTH_PROJECT_DOMAIN_NAME, auth.getToken() + .getProject().getDomain().getName()); + } + } + req.setAttribute(AUTH_USER_ID, auth.getToken().getUser() + .getUserId()); + req.setAttribute(AUTH_USER_NAME, auth.getToken().getUser() + .getUsername()); + req.setAttribute(AUTH_USER_DOMAIN_ID, auth.getToken().getUser() + .getDomain().getId()); + if (auth.getToken().getUser().getDomain().getName() != null) { + req.setAttribute(AUTH_USER_DOMAIN_NAME, auth.getToken() + .getUser().getDomain().getName()); + } + getRoles(auth.getToken().getRoles(), tenants, nonTenants); + String tenantRoles = (tenants.length() > 0) ? tenants.substring(1) + : tenants.toString(); + String nonTenantRoles = (nonTenants.length() > 0) ? nonTenants + .substring(1) : nonTenants.toString(); + if (!tenantRoles.equals("")) { + req.setAttribute(AUTH_ROLES, tenantRoles); + } + if (!nonTenantRoles.equals("")) { + req.setAttribute(AUTH_HP_IDM_ROLES, nonTenantRoles); + } + if (auth.getToken().getCatalog() != null) { + req.setAttribute(AUTH_SERVICE_CATALOG, gson + .toJson(buildServiceCatalogV3(auth.getToken() + .getCatalog()))); + } + + setDeprecatedHeaders(req, auth, tenantRoles); + } + } */ + + /*private static List buildServiceCatalogV3( + List catalogs) { + List v3Catalogs = new ArrayList(); + for (ServiceForCatalogV3 catalog : catalogs) { + CatalogV3 catalogv3 = new CatalogV3(); + catalogv3.setId(catalog.getId()); + catalogv3.setType(catalog.getType()); + List endPoints = catalog.getEndpoints(); + List endPointsv3 = new ArrayList(); + for (EndpointV3 endPoint : endPoints) { + Properties endPointv3 = new Properties(); + if (endPoint.getInterfaceName() != null) { + endPointv3.put("interface", endPoint.getInterfaceName()); + } + if (endPoint.getEndpointId() != null) { + endPointv3.put("id", endPoint.getEndpointId()); + } + if (endPoint.getServiceId() != null) { + endPointv3.put("service_id", endPoint.getServiceId()); + } + if (endPoint.getRegion() != null) { + endPointv3.put("region", endPoint.getRegion()); + } + if (endPoint.getUrl() != null) { + endPointv3.put("url", endPoint.getUrl()); + } + endPointsv3.add(endPointv3); + } + catalogv3.setEndPoints(endPointsv3); + v3Catalogs.add(catalogv3); + } + return v3Catalogs; + } */ + + // Method will be removed after keystone removes the deprecated headers. + /*private static void setDeprecatedHeaders(ServletRequest req, + AuthResponseV3 auth, String tenantRoles) { + // Deprecated + req.setAttribute(AUTH_USER, auth.getToken().getUser().getUsername()); + if (auth.getToken().getProject() != null) { + req.setAttribute(AUTH_TENANT_ID, auth.getToken().getProject() + .getId()); + req.setAttribute(AUTH_TENANT_NAME, auth.getToken().getProject() + .getName()); + req.setAttribute(AUTH_TENANT, auth.getToken().getProject() + .getName()); + } + if (!tenantRoles.equals("")) { + req.setAttribute(AUTH_ROLE, tenantRoles); + } + } */ + + // Insert token into cache + public static void cacheToken(String token, Object auth) { + if (isCaching()) { + appConfig.getClient().put(token, auth); + /*try { + appConfig.getClient().putToken(token, auth); + } catch (TimeoutException e) { + logger.error("Error timeout setting memcache: " + token); + } catch (InterruptedException e) { + logger.error("Error memcache interrupted"); + } catch (MemcachedException e) { + logger.error("Error memcache", e); + } */ + } + } + + // Get token from cache + public static Object getCachedToken(String token) { + if (isCaching()) { + long timeout = appConfig.getMemCacheTimeOut(); + + /*try { + return appConfig.getClient().getToken(token, timeout); + } catch (TimeoutException e) { + logger.error("Error timeout getting from memcache: " + token); + } catch (InterruptedException e) { + logger.error("Error memcache interrupted"); + } catch (MemcachedException e) { + logger.error("Error memcache", e); + } */ + } + return appConfig.getClient().getToken(token); + } + + public static void pause(long pauseTime) { + try { + Thread.currentThread().sleep(pauseTime); + } catch (InterruptedException e) { + logger.debug("Thread is interrupted while sleeping before " + + pauseTime + " seconds. "); + } + } + + // Is caching enabled? + private static boolean isCaching() { + return appConfig.getClient() != null; + } + + /* private static void getRoles(Object obj, StringBuilder tenants, + StringBuilder nonTenants) { + if (appConfig.getAuthVersion().equalsIgnoreCase("v2.0")) { + List roles = (List) obj; + for (Role role : roles) { + if (role.getTenantId() != null) { + tenants.append(","); + tenants.append(role.getName()); + } else { + nonTenants.append(","); + nonTenants.append(role.getName()); + } + } + } else { + List roles = (List) obj; + for (V3Role role : roles) { + if (role.getProjectId() != null) { + tenants.append(","); + tenants.append(role.getName()); + } else { + nonTenants.append(","); + nonTenants.append(role.getName()); + } + } + } + } */ +} diff --git a/src/main/java/com/hp/csbu/cc/middleware/HPS3Signer.java.txt b/src/main/java/com/hp/csbu/cc/middleware/HPS3Signer.java.txt new file mode 100644 index 000000000..c38bc35ce --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/HPS3Signer.java.txt @@ -0,0 +1,236 @@ +package com.hp.csbu.cc.middleware; + +import java.text.Format; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.SimpleTimeZone; +import java.util.TreeMap; +import java.util.concurrent.TimeUnit; + +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; + +import com.hp.csbu.cc.security.cs.thrift.service.SigAuthRequest; +import com.hp.csbu.cc.security.cs.thrift.service.SignatureCredentials; +import static com.hp.csbu.cc.middleware.AuthConstants.SIGNATURE_METHOD; + +public class HPS3Signer implements Signer { + + private final Set subResources = new HashSet() { + { + add("acl"); + add("logging"); + add("torrent"); + add("location"); + add("requestPayment"); + } + }; + + public SigAuthRequest sign(ServletRequest req, String serviceIds, + String endPointIds) { + String accessKeyId = null; + String signature = null; + String dataToSign = null; + if (req.getParameter("AWSAccessKeyId") != null) { + if (hasRequestExpired(req)) { + throw new SignatureBuilderException( + "Either signature is expired or expiration field is missing"); + } + req.setAttribute("Date", req.getParameter("Expires")); + accessKeyId = req.getParameter("AWSAccessKeyId"); + signature = req.getParameter("Signature"); + } else { + Date d = null; + if (((HttpServletRequest) req).getHeader("X-Amz-Date") != null) { + d = parseDate(((HttpServletRequest) req) + .getHeader("X-Amz-Date")); + } else if (((HttpServletRequest) req).getHeader("Date") != null) { + d = parseDate(((HttpServletRequest) req).getHeader("Date")); + } else { + throw new SignatureBuilderException( + "Either date is missing or an invalid date is provided"); + } + if (hasClockSkew(d)) { + throw new SignatureBuilderException( + "Date is invalid. Date is skewed by more than 15 mins"); + } + String authorizationStr = ((HttpServletRequest) req) + .getHeader("Authorization"); + authorizationStr = authorizationStr.substring( + authorizationStr.indexOf(" ")).trim(); + int colonDelimeter = authorizationStr.lastIndexOf(":"); + accessKeyId = authorizationStr.substring(0, colonDelimeter); + signature = authorizationStr.substring(colonDelimeter + 1); + } + dataToSign = getCanonicalString(req); + return getSignedRequest(dataToSign, accessKeyId, signature, + SIGNATURE_METHOD, serviceIds, endPointIds); + } + + protected boolean hasClockSkew(Date d) { + if (d == null) { + // Invalid date + return true; + } + Date currentDate = new Date(); + long currentTime = currentDate.getTime(); + long epochTime = getUnixEpochTime(); + long requestTime = d.getTime(); + long delta = TimeUnit.MILLISECONDS.convert(15L, TimeUnit.MINUTES); + + if (requestTime < epochTime) { + // Invalid date + return true; + } + + if (Math.abs(requestTime - currentTime) > delta) { + // Invalid date + return true; + } + return false; + } + + protected String getCanonicalString(ServletRequest req) { + HttpServletRequest request = ((HttpServletRequest) req); + StringBuffer canonicalStr = new StringBuffer(); + Map amzHeaders = new TreeMap(); + String contentMd5 = (request.getHeader("Content-MD5") == null) ? "" + : request.getHeader("Content-MD5"); + String contentType = (request.getHeader("Content-Type") == null) ? "" + : request.getHeader("Content-Type"); + String method = request.getMethod(); + canonicalStr.append(method).append("\n").append(contentMd5) + .append("\n").append(contentType).append("\n"); + + Enumeration headers = request.getHeaderNames(); + while (headers.hasMoreElements()) { + String headerName = ((String) headers.nextElement()).toLowerCase(); + if (headerName.startsWith("x-amz-")) { + Enumeration multiHeaderValues = request.getHeaders(headerName); + StringBuffer headerValues = new StringBuffer(); + headerValues.append((String) multiHeaderValues.nextElement()); + while (multiHeaderValues.hasMoreElements()) { + headerValues.append(",").append( + multiHeaderValues.nextElement()); + } + amzHeaders.put(headerName, headerValues.toString()); + } + } + if (amzHeaders.containsKey("X-Amz-Date")) { + canonicalStr.append("\n"); + } else { + String date = (request.getHeader("Date") != null) ? (String) request + .getHeader("Date") : (String) request.getAttribute("Date"); + canonicalStr.append(date).append("\n"); + } + + for (String key : amzHeaders.keySet()) { + canonicalStr.append(key).append(":").append(amzHeaders.get(key)) + .append("\n"); + } + String resource = getCanonicalResource(request); + canonicalStr.append(resource); + return canonicalStr.toString(); + } + + protected String getCanonicalResource(HttpServletRequest request) { + StringBuffer canonicalResource = new StringBuffer(); + canonicalResource.append(request.getRequestURI()); + String queryString = request.getQueryString(); + if (queryString != null) { + canonicalResource.append(getCanonicalSubResource(queryString)); + } + return canonicalResource.toString(); + } + + protected String getCanonicalSubResource(String queryString) { + String[] queryParams; + if (queryString.contains("&")) { + queryParams = queryString.split("&"); + } else { + queryParams = new String[1]; + queryParams[0] = queryString; + } + for (String param : queryParams) { + String paramName = param; + if (paramName.contains("=")) { + paramName = paramName.substring(0, paramName.indexOf("=")); + } + if (subResources.contains(paramName)) { + return "?" + paramName; + } + } + return ""; + + } + + protected long getUnixEpochTime() { + Format formatter = new SimpleDateFormat("dd MMM yyyy HH:mm:ss z"); + Date date; + try { + date = (Date) formatter.parseObject("01 Jan 1970 00:00:00 UTC"); + } catch (ParseException e) { + return 0; + } + return date.getTime(); + } + + protected boolean hasRequestExpired(ServletRequest req) { + if (req.getParameter("Expires") == null) { + return true; + } + long expirationTime = new Long(req.getParameter("Expires")); + return expirationTime < (System.currentTimeMillis()/1000); + } + + protected Date parseDate(String data) { + /* Currently date is parsed in RFC 822 format */ + SimpleDateFormat df = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z"); + df.setTimeZone(new SimpleTimeZone(0, "UTC")); + try { + return df.parse(data); + } catch (ParseException e) { + return null; + } + } + + protected SigAuthRequest getSignedRequest(String dataToSign, + String accessKeyId, String signature, String signatureMethod, + String serviceIds, String endPointIds) { + try { + SignatureCredentials credentials = null; + Map params = new HashMap(); + String tenantId = null; + String keyId = accessKeyId; + + if (accessKeyId.contains(":")) { + String[] strArr = accessKeyId.split(":"); + tenantId = strArr[0]; + keyId = strArr[1]; + } + credentials = new SignatureCredentials(keyId, "accesskey", + signatureMethod, dataToSign, signature); + //If tenantId is null, you get an unscoped token. + if (tenantId != null) { + params.put("tenantId", tenantId); + } + if (serviceIds != null) { + params.put("serviceIds", serviceIds); + } + if (endPointIds != null) { + params.put("endPointTemplateIds", endPointIds); + } + return new SigAuthRequest(credentials, params); + } catch (Exception e) { + throw new SignatureBuilderException( + "Exception building signature with given credentials"); + } + } + +} diff --git a/src/main/java/com/hp/csbu/cc/middleware/HttpAuthClient.java b/src/main/java/com/hp/csbu/cc/middleware/HttpAuthClient.java new file mode 100644 index 000000000..c8afc80be --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/HttpAuthClient.java @@ -0,0 +1,296 @@ +package com.hp.csbu.cc.middleware; + +import static com.hp.csbu.cc.middleware.AuthConstants.AUTH_SUBJECT_TOKEN; +import static com.hp.csbu.cc.middleware.AuthConstants.TOKEN; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map; +import java.util.TimeZone; + +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.message.BasicHeader; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +//import com.hp.csbu.cc.security.cs.thrift.service.AuthResponse; +//import com.hp.csbu.cc.security.cs.thrift.service.SigAuthRequest; + +public class HttpAuthClient implements AuthClient { + private static final String ACCESSKEY = "accesskey"; + private static final String PASSWORD = "password"; + private static final String SERVICE_IDS_PARAM = "serviceIds"; + private static final String ENDPOINT_IDS_PARAM = "endpointIds"; + private static final int DELTA_TIME_IN_SEC = 30; + private static SimpleDateFormat expiryFormat; + static { + expiryFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mmmmmm'Z'"); + expiryFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + } + private final Config appConfig = Config.getInstance(); + + private HttpClient client; + private String adminToken; + private String adminTokenExpiry; + private URI uri; + + public HttpAuthClient(HttpClient client, URI uri) { + this.client = client; + this.uri = uri; + } + + @Override + public Object validateTokenForServiceEndpointV2(String token, + String serviceIds, String endpointIds, boolean includeCatalog) + throws ClientProtocolException { + String newUri = uri.toString() + "/v2.0/tokens/" + token; + return verifyUUIDToken(token, newUri, null, serviceIds, endpointIds); + } + + @Override + public Object validateTokenForServiceEndpointV3(String token, + Map inputParams) throws ClientProtocolException { + String newUri = uri.toString() + "/v3/auth/tokens/"; + Header[] header = new Header[1]; + header[0] = new BasicHeader(AUTH_SUBJECT_TOKEN, token); + String serviceIds = null; + String endpointIds = null; + if (inputParams.containsKey(SERVICE_IDS_PARAM)) + serviceIds = inputParams.get(SERVICE_IDS_PARAM); + if (inputParams.containsKey(ENDPOINT_IDS_PARAM)) + endpointIds = inputParams.get(ENDPOINT_IDS_PARAM); + return verifyUUIDToken(token, newUri, header, serviceIds, endpointIds); + } + + private Object verifyUUIDToken(String token, String newUri, + Header[] header, String serviceIds, String endpointIds) + throws ClientProtocolException { + HttpResponse response = sendGet(newUri, header, serviceIds, endpointIds); + int code = response.getStatusLine().getStatusCode(); + if (code == 404) { + throw new AuthException("Authorization failed for token: " + token); + } + if (code != 200) { + adminToken = null; + throw new AuthException("Failed to validate via HTTP " + code + + " " +response.getStatusLine().getReasonPhrase()); + } + return parseResponse(response); + } + + private HttpResponse sendPost(String uri, StringEntity body) + throws ClientProtocolException { + HttpResponse response = null; + HttpPost post = new HttpPost(uri); + post.setHeader("Accept", "application/json"); + post.setHeader("Content-Type", "application/json"); + try { + post.setEntity(body); + response = client.execute(post); + int code = response.getStatusLine().getStatusCode(); + if (!(code == 201 || code == 200 || code == 203)) { + adminToken = null; + throw new AuthException( + "Failed to authenticate admin credentials " + code + + response.getStatusLine().getReasonPhrase()); + } + } catch (IOException e) { + post.abort(); + throw new ClientProtocolException( + "IO Exception during POST request ", e); + } + return response; + } + + private HttpResponse sendGet(String newUri, Header[] headers, + String serviceIds, String endpointIds) + throws ClientProtocolException { + HttpResponse response = null; + HttpGet get = null; + boolean hasServiceIds = false; + if (serviceIds != null && !serviceIds.isEmpty()) { + newUri += "?HP-IDM-serviceId=" + serviceIds; + hasServiceIds = true; + } + if (endpointIds != null && !endpointIds.isEmpty()) { + newUri += hasServiceIds ? "&HP-IDM-endpointTemplateId=" + + endpointIds : "?HP-IDM-endpointTemplateId=" + endpointIds; + } + + get = new HttpGet(newUri); + get.setHeader("Accept", "application/json"); + get.setHeader("Content-Type", "application/json"); + if (headers != null) { + for (Header header : headers) { + get.setHeader(header); + } + } + if (!appConfig.getAdminAuthMethod().isEmpty()) { + get.setHeader(new BasicHeader(TOKEN, getAdminToken())); + } + try { + response = client.execute(get); + } catch (IOException e) { + get.abort(); + throw new ClientProtocolException( + "IO Exception during GET request ", e); + } + return response; + } + + private String parseResponse(HttpResponse response) { + StringBuffer json = new StringBuffer(); + HttpEntity entity = response.getEntity(); + if (entity != null) { + InputStream instream; + try { + instream = entity.getContent(); + + BufferedReader reader = new BufferedReader( + new InputStreamReader(instream)); + String line = reader.readLine(); + while (line != null) { + json.append(line); + line = reader.readLine(); + } + } catch (Exception e) { + throw new AuthException("Failed to parse Http Response ", e); + } + } + return json.toString(); + } + + private String getAdminToken() throws ClientProtocolException { + HttpResponse response; + String json; + JsonParser jp = new JsonParser(); + + if (adminTokenExpiry != null) { + if (isExpired(adminTokenExpiry)) { + adminToken = null; + } + } + if (adminToken == null) { + if (appConfig.getAuthVersion().equalsIgnoreCase("v2.0")) { + StringEntity params = getUnscopedV2AdminTokenRequest(); + String authUri = uri + "/v2.0/tokens"; + response = sendPost(authUri, params); + json = parseResponse(response); + JsonObject access = jp.parse(json).getAsJsonObject() + .get("access").getAsJsonObject(); + JsonObject token = access.get("token").getAsJsonObject(); + adminToken = token.get("id").getAsString(); + adminTokenExpiry = token.get("expires").getAsString(); + } else { + StringEntity params = getUnscopedV3AdminTokenRequest(); + String authUri = uri + "/v3/auth/tokens"; + response = sendPost(authUri, params); + adminToken = response.getFirstHeader(AUTH_SUBJECT_TOKEN) + .getValue(); + json = parseResponse(response); + JsonObject token = jp.parse(json).getAsJsonObject() + .get("token").getAsJsonObject(); + adminTokenExpiry = token.get("expires_at").getAsString(); + + } + } + return adminToken; + } + + private StringEntity getUnscopedV2AdminTokenRequest() { + StringBuffer bfr = new StringBuffer(); + if (appConfig.getAdminAuthMethod().equalsIgnoreCase(PASSWORD)) { + bfr.append("{\"auth\": {\"passwordCredentials\": {\"username\": \""); + bfr.append(appConfig.getAdminUser()); + bfr.append("\",\"password\": \""); + bfr.append(appConfig.getAdminPassword()); + if (appConfig.getAdminProject() != null && !appConfig.getAdminProject().isEmpty()) { + bfr.append("\"}, \"tenantId\": \""); + bfr.append(appConfig.getAdminProject()); + bfr.append("\"}}"); + } else { + bfr.append("\"}}}"); + } + try { + return new StringEntity(bfr.toString()); + } catch (UnsupportedEncodingException e) { + throw new AuthException("Invalid V2 authentication request " + + e); + } + } else { + String msg = String.format("Admin auth method %s not supported",appConfig.getAdminAuthMethod()); + throw new AuthException(msg); + } + } + + private StringEntity getUnscopedV3AdminTokenRequest() { + StringBuffer bfr = new StringBuffer(); + if (appConfig.getAdminAuthMethod().equalsIgnoreCase(PASSWORD)) { + bfr.append("{\"auth\": {\"identity\": {\"methods\": [\"password\"],\"password\": {\"user\": {\"name\": \""); + bfr.append(appConfig.getAdminUser()); + bfr.append("\",\"password\": \""); + bfr.append(appConfig.getAdminPassword()); + if (appConfig.getAdminProject() != null && !appConfig.getAdminProject().isEmpty()) { + bfr.append("\"},\"scope\": { \"project\": { \"id\": \""); + bfr.append(appConfig.getAdminProject()); + bfr.append("\"}}}}}}"); + } else { + bfr.append("\"}}}}}"); + } + } else if (appConfig.getAdminAuthMethod().equalsIgnoreCase(ACCESSKEY)) { + bfr.append("{\"auth\": {\"identity\": {\"methods\": [\"accessKey\"], \"accessKey\": { \"accessKey\": \""); + bfr.append(appConfig.getAdminAccessKey()); + bfr.append("\", \"secretKey\": \""); + bfr.append(appConfig.getAdminSecretKey()); + if (appConfig.getAdminProject() != null && !appConfig.getAdminProject().isEmpty()) { + bfr.append("\"},\"scope\": { \"project\": { \"id\": \""); + bfr.append(appConfig.getAdminProject()); + bfr.append("\"}}}}}"); + } else { + bfr.append("\"}}}}"); + } + } else { + String msg = String.format("Admin auth method %s not supported",appConfig.getAdminAuthMethod()); + throw new AuthException(msg); + } + try { + return new StringEntity(bfr.toString()); + } catch (UnsupportedEncodingException e) { + throw new AuthException("Invalid V3 authentication request " + e); + } + } + + private boolean isExpired(String expires) { + Date tokenExpiryDate = null; + try { + tokenExpiryDate = expiryFormat.parse(expires); + } catch (ParseException e) { + return true; + } + Date current = new Date(); + return tokenExpiryDate.getTime() < (current.getTime() + DELTA_TIME_IN_SEC * 1000); + } + + public void reset() { + } + +/* @Override + public AuthResponse validateSignature(SigAuthRequest request) { + // TODO Auto-generated method stub + return null; + }*/ +} diff --git a/src/main/java/com/hp/csbu/cc/middleware/HttpClientFactory.java b/src/main/java/com/hp/csbu/cc/middleware/HttpClientFactory.java new file mode 100644 index 000000000..50e3aec22 --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/HttpClientFactory.java @@ -0,0 +1,30 @@ +package com.hp.csbu.cc.middleware; + +import org.apache.commons.pool.impl.GenericObjectPool; + +/** + * An HTTP factory. + * + * @author liemmn + * + */ +public class HttpClientFactory extends AuthClientFactory { + private HttpClientPoolFactory clientPool; + + HttpClientFactory(String host, int port, int timeout, boolean clientAuth, + String keyStore, String keyPass, String trustStore, + String trustPass, String adminToken, int maxActive, + long timeBetweenEvictionRunsMillis, long minEvictableIdleTimeMillis) { + clientPool = new HttpClientPoolFactory(host, port, timeout, clientAuth, + keyStore, keyPass, trustStore, trustPass, adminToken, + maxActive, timeBetweenEvictionRunsMillis, + minEvictableIdleTimeMillis); + pool = new GenericObjectPool(clientPool); + } + + @Override + public void shutdown() { + clientPool.shutDown(); + super.shutdown(); + } +} diff --git a/src/main/java/com/hp/csbu/cc/middleware/HttpClientPoolFactory.java b/src/main/java/com/hp/csbu/cc/middleware/HttpClientPoolFactory.java new file mode 100644 index 000000000..cae1126f7 --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/HttpClientPoolFactory.java @@ -0,0 +1,130 @@ +package com.hp.csbu.cc.middleware; + +import java.io.File; +import java.io.FileInputStream; +import java.net.URI; +import java.security.KeyStore; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.pool.BasePoolableObjectFactory; +import org.apache.http.client.HttpClient; +import org.apache.http.conn.scheme.PlainSocketFactory; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.conn.PoolingClientConnectionManager; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.CoreConnectionPNames; +import org.apache.http.params.HttpParams; + +/** + * A Http request pool factory. Based on Apache Commons Pool. Singleton. + * Note that the Apache HttpClient maintains its own connection pool and + * does not participate in Apache Commons pool' lifecycle other than creating + * HTTPRequests. + * + * @author liemmn + * + */ +public class HttpClientPoolFactory extends BasePoolableObjectFactory { + private URI uri; + private PoolingClientConnectionManager connMgr; + private HttpPoolCleaner cleaner; + private HttpClient client; + + HttpClientPoolFactory(String host, int port, int timeout, + boolean clientAuth, String keyStore, String keyPass, + String trustStore, String trustPass, String adminToken, + int maxActive, long timeBetweenEvictionRunsMillis, + long minEvictableIdleTimeMillis) { + // Setup auth URL + String protocol = (port == 35357) ? "https://" : "http://"; + String urlStr = protocol + host + ":" + port; + uri = URI.create(urlStr); + + // Setup connection pool + SchemeRegistry schemeRegistry = new SchemeRegistry(); + if (protocol.startsWith("https")) { + SSLSocketFactory sslf = sslFactory(keyStore, keyPass, trustStore, + trustPass, clientAuth); + schemeRegistry.register(new Scheme("https", port, sslf)); + } else { + schemeRegistry.register(new Scheme("http", port, PlainSocketFactory + .getSocketFactory())); + } + connMgr = new PoolingClientConnectionManager(schemeRegistry, + minEvictableIdleTimeMillis, TimeUnit.MILLISECONDS); + connMgr.setMaxTotal(maxActive); + connMgr.setDefaultMaxPerRoute(maxActive); + + // Http connection timeout + HttpParams params = new BasicHttpParams(); + params.setParameter(CoreConnectionPNames.SO_TIMEOUT, timeout); + params.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, timeout); + + // Create a single client + client = new DefaultHttpClient(connMgr, params); + + // Create and start the connection pool cleaner + cleaner = new HttpPoolCleaner(connMgr, timeBetweenEvictionRunsMillis, + minEvictableIdleTimeMillis); + new Thread(cleaner).start(); + + } + + @Override + public Object makeObject() throws Exception { + return new HttpAuthClient(client, uri); + } + + @Override + public void passivateObject(Object obj) throws Exception { + ((HttpAuthClient) obj).reset(); + } + + @Override + public void destroyObject(Object obj) throws Exception { + ((HttpAuthClient) obj).reset(); + obj = null; + } + + public void shutDown() { + // Shutdown all connections + connMgr.shutdown(); + // Shutdown connection pool cleaner + cleaner.shutdown(); + } + + // get a socket factory + private static SSLSocketFactory sslFactory(String keyStore, String keyPass, + String trustStore, String trustPass, boolean clientAuth) { + try { + // keystore + KeyStore ks = null; + if (clientAuth) { + ks = KeyStore.getInstance("jks"); + FileInputStream is1 = new FileInputStream(new File(keyStore)); + try { + ks.load(is1, keyPass.toCharArray()); + } finally { + is1.close(); + } + } + // truststore + KeyStore ts = KeyStore.getInstance("jks"); + FileInputStream is2 = new FileInputStream( + new File(trustStore)); + try { + ts.load(is2, trustPass.toCharArray()); + } finally { + is2.close(); + } + SSLSocketFactory sslf = new SSLSocketFactory(ks, keyPass, ts); + return sslf; + } catch (Exception e) { + throw new AuthConnectionException( + "Failed to create SSLSocketFactory", e); + } + } +} diff --git a/src/main/java/com/hp/csbu/cc/middleware/HttpPoolCleaner.java b/src/main/java/com/hp/csbu/cc/middleware/HttpPoolCleaner.java new file mode 100644 index 000000000..70017851f --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/HttpPoolCleaner.java @@ -0,0 +1,56 @@ +package com.hp.csbu.cc.middleware; + +import java.util.concurrent.TimeUnit; + +import org.apache.http.conn.ClientConnectionManager; + +/** + * A runner to clean the connection pool! There should only be one! + * + * @author liemmn + * + */ +public class HttpPoolCleaner implements Runnable { + private final ClientConnectionManager connMgr; + private long timeBetweenEvictionRunsMillis, minEvictableIdleTimeMillis; + private volatile boolean shutdown; + + public HttpPoolCleaner(ClientConnectionManager connMgr, + long timeBetweenEvictionRunsMillis, long minEvictableIdleTimeMillis) { + this.connMgr = connMgr; + this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis; + this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis; + } + + /** + * Start the cleaner. + */ + @Override + public void run() { + try { + while (!shutdown) { + synchronized (this) { + wait(timeBetweenEvictionRunsMillis); + // Close expired connections + connMgr.closeExpiredConnections(); + // Close connections that have been idle longer than x sec + connMgr.closeIdleConnections(minEvictableIdleTimeMillis, + TimeUnit.MILLISECONDS); + } + } + } catch (InterruptedException ex) { + // terminate + } + } + + /** + * Shutdown the cleaner. + */ + public void shutdown() { + shutdown = true; + synchronized (this) { + notifyAll(); + } + } + +} diff --git a/src/main/java/com/hp/csbu/cc/middleware/MemcacheCrypt.text b/src/main/java/com/hp/csbu/cc/middleware/MemcacheCrypt.text new file mode 100644 index 000000000..21c2165d9 --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/MemcacheCrypt.text @@ -0,0 +1,283 @@ +package com.hp.csbu.cc.middleware; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.SimpleTimeZone; +import java.util.concurrent.TimeoutException; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import net.rubyeye.xmemcached.MemcachedClient; +import net.rubyeye.xmemcached.XMemcachedClientBuilder; +import net.rubyeye.xmemcached.exception.MemcachedException; +import net.rubyeye.xmemcached.utils.AddrUtil; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +//import com.hp.csbu.cc.security.cs.thrift.service.AuthResponseV2; +//import com.hp.csbu.cc.security.cs.thrift.service.AuthResponseV3; + +/** + * An internal class to allow encryption and decryption of validated tokens + * stored in memcache. + * + * @author liemmn + * + */ +public class MemcacheCrypt { + // GMT date format for expiration time stamp in token + static SimpleDateFormat expiryFormat; + static { + expiryFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + Calendar cal = Calendar.getInstance(new SimpleTimeZone(0, "GMT")); + expiryFormat.setCalendar(cal); + } + + // character set to use for converting between chars and bytes + private static final String CHARSET_NAME = "UTF-8"; + + // message digest algorithm + private static final String DIGEST_ALGORITHM = "SHA-256"; + + // key algorithm + private static final String KEY_ALGORITHM = "AES"; + + // cipher algorithm + private static final String CIPHER_ALGORITHM = KEY_ALGORITHM + + "/CBC/PKCS5Padding"; + + // Thee password + private static final String PASSWORD = "G0n30ffTehC11ff!"; + + // Wrapped memcache client instance + //private MemcachedClient client; + private TokenCache client; + + // Cache value to be encrypted or not + private boolean isEncrypted; + + // Thee faithful logger + private static final Logger logger = LoggerFactory + .getLogger(MemcacheCrypt.class); + + /** + * Construct a memcache client instance. + * + * @param cacheHosts + * List of memcache servers + * @param isEncrypted + * Uses encryption or not + * @throws IOException + */ + public MemcacheCrypt(String cacheHosts, boolean isEncrypted) + throws IOException { + XMemcachedClientBuilder builder = new XMemcachedClientBuilder( + AddrUtil.getAddresses(cacheHosts)); + this.client = builder.build(); + this.isEncrypted = isEncrypted; + } + + /** + * Shutdown this client instance. + * + * @throws IOException + */ + public void shutdown() throws IOException { + client.shutdown(); + } + + /** + * Store given token into memcache. + * + * @param token + * Token + * @param auth + * Token info + * @throws TimeoutException + * @throws InterruptedException + * @throws MemcachedException + */ + public void putToken(String token, Object auth) throws TimeoutException, + InterruptedException, MemcachedException { + String expires = null; + /* if (auth instanceof AuthResponseV2) { + expires = ((AuthResponseV2) auth).getTokenInfo().getExpires(); + } else { + expires = ((AuthResponseV3) auth).getToken().getExpires_at(); + } */ + if (isEncrypted) { + auth = encrypt(token, PASSWORD, auth); + } + client.set("tokens/" + token, expireFromNow(expires), auth); + } + + /** + * Get a token, or null if no token found in cache. + * + * @param token + * Token + * @param timeout + * Timeout waiting to get token + * @return Token info + * @throws TimeoutException + * @throws InterruptedException + * @throws MemcachedException + */ + public Object getToken(String token, long timeout) throws TimeoutException, + InterruptedException, MemcachedException { + Object o = client.get("tokens/" + token, timeout); + /*if (o instanceof AuthResponseV2) { + return (AuthResponseV2) o; + } else if (o instanceof AuthResponseV3) { + return (AuthResponseV3) o; + } else if (o instanceof byte[]) { + return decrypt(token, PASSWORD, (byte[]) o); + } + */ + if (o instanceof byte[]) { + return decrypt(token, PASSWORD, (byte[]) o); + } + return null; + } + + // Test method... Don't use! + void setEncrypted(boolean isEncrypted) { + this.isEncrypted = isEncrypted; + } + + // Expiration from now in seconds + private int expireFromNow(String expires) { + Date tokenDate = null; + try { + tokenDate = expiryFormat.parse(expires); + } catch (ParseException e) { + logger.error("Error parsing token expiration: " + expires); + } + Date current = new Date(); + return (int) ((tokenDate.getTime() - current.getTime()) / 1000); + } + + /** + * Encrypts the given token information + * + * @param token + * Token + * @param password + * Password used for encryption/decryption + * @param auth + * Token info to encrypt + * @return Encrypted token info + */ + static byte[] encrypt(final String token, final String password, + final Object tokenData) { + byte[] encryptedAuth = null; + ByteArrayOutputStream baos = null; + + try { + // Compute key and iv from token and password + Secret secret = genSecret(token, password); + + // Serialize the token info + baos = new ByteArrayOutputStream(); + ObjectOutputStream os = new ObjectOutputStream(baos); + /* if (tokenData instanceof AuthResponseV2) { + os.writeObject((AuthResponseV2) tokenData); + } else { + os.writeObject((AuthResponseV3) tokenData); + }*/ + + byte[] data = baos.toByteArray(); + + // Performs 128-AES encryption + Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(secret.key, + KEY_ALGORITHM), new IvParameterSpec(secret.iv)); + encryptedAuth = cipher.doFinal(data); + } catch (Exception e) { + throw new EncryptionException("Failed to encrypt " + token, e); + } finally { + if (baos != null) + try { + baos.close(); + } catch (IOException e) { + } + } + return encryptedAuth; + } + + /** + * Decrypts the given token info data. + * + * @param token + * Token + * @param password + * Password used for encryption/decryption. + * @param data + * Token info data + * @return Token info + */ + static Object decrypt(final String token, final String password, + final byte[] data) { + ByteArrayInputStream bais = null; + Object auth = null; + + try { + // Compute key and iv from token and password + Secret secret = genSecret(token, password); + + // Performs 128-AES decryption + Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(secret.key, + KEY_ALGORITHM), new IvParameterSpec(secret.iv)); + byte[] decryptedAuth = cipher.doFinal(data); + + // Deserialize the token info + bais = new ByteArrayInputStream(decryptedAuth); + ObjectInputStream ois = new ObjectInputStream(bais); + auth = ois.readObject(); + bais.close(); + } catch (Exception e) { + throw new DecryptionException("Failed to decrypt " + token, e); + } finally { + if (bais != null) + try { + bais.close(); + } catch (IOException e) { + } + } + return auth; + } + + // Compute key and iv from token and password + private static Secret genSecret(final String token, final String password) + throws UnsupportedEncodingException, NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance(DIGEST_ALGORITHM); + byte[] seed = (token + password).getBytes(CHARSET_NAME); + byte[] pw = md.digest(seed); + Secret secret = new Secret(); + System.arraycopy(pw, 0, secret.key, 0, 16); + System.arraycopy(pw, 16, secret.iv, 0, 16); + Arrays.fill(pw, (byte) 0x00); + return secret; + } + + // Data object to hold the key and iv for encryption/decryption + private static final class Secret { + byte[] key = new byte[16]; + byte[] iv = new byte[16]; + } +} diff --git a/src/main/java/com/hp/csbu/cc/middleware/S3SignatureAuth.java.txt b/src/main/java/com/hp/csbu/cc/middleware/S3SignatureAuth.java.txt new file mode 100644 index 000000000..99db35fa8 --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/S3SignatureAuth.java.txt @@ -0,0 +1,95 @@ +package com.hp.csbu.cc.middleware; + +import java.io.IOException; + +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.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.hp.csbu.cc.middleware.AuthConstants.IdentityStatus; +import com.hp.csbu.cc.security.cs.thrift.service.AuthResponse; +import com.hp.csbu.cc.security.cs.thrift.service.SigAuthRequest; + +public class S3SignatureAuth implements Filter, AuthConstants { + + private final Config appConfig = Config.getInstance(); + private FilterConfig filterConfig; + + // Thee faithful logger + private static final Logger logger = LoggerFactory + .getLogger(S3SignatureAuth.class); + + private static final String SIGNATURE_NOT_FOUND = "Invalid Credentials: Token or Signature not found in the request"; + + @Override + public void destroy() { + FilterUtils.destroyFilter(); + } + + @Override + public void doFilter(ServletRequest req, ServletResponse resp, + FilterChain chain) throws IOException, ServletException { + AuthResponse auth = null; + if (!appConfig.isInitialized()) { + appConfig.initialize(filterConfig); + } + // Flow has reached here by setting DelayAuthDecision. + // Check if the token validation has failed and then continue to + // signatue validation. + if (req.getAttribute(AUTH_IDENTITY_STATUS).equals( + IdentityStatus.Invalid.toString())) { + HPS3Signer s3Signer = new HPS3Signer(); + if (isS3Request(req)) { + AuthClient client = null; + try { + SigAuthRequest signedRequest = s3Signer.sign(req, + appConfig.getServiceIds(), + appConfig.getEndpointIds()); + client = appConfig.getFactory().getClient(); + auth = client.validateSignature(signedRequest); + + // Return to connection pool for re-use + appConfig.getFactory().recycle(client); + } catch (Exception ex) { + if (client != null) + appConfig.getFactory().discard(client); + SignatureExceptionHandler handler = ExceptionHandlerUtil + .lookUpSignatureException(ex); + handler.onException(ex, resp); + } + } else { + logger.error(HttpServletResponse.SC_UNAUTHORIZED + + SIGNATURE_NOT_FOUND); + ((HttpServletResponse) resp).sendError( + HttpServletResponse.SC_UNAUTHORIZED, + SIGNATURE_NOT_FOUND); + } + req = FilterUtils.wrapRequest(req, auth); + } + // Continue in the filter chain as DelayAuthDecision has been set. + chain.doFilter(req, resp); + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + this.filterConfig = filterConfig; + } + + private boolean isS3Request(ServletRequest req) { + if (((HttpServletRequest) req).getHeader("Authorization") != null + || ((req.getParameter("AWSAccessKeyId")) != null && req + .getParameter("Signature") != null)) { + return true; + } + return false; + } + +} diff --git a/src/main/java/com/hp/csbu/cc/middleware/SignatureBuilderException.java b/src/main/java/com/hp/csbu/cc/middleware/SignatureBuilderException.java new file mode 100644 index 000000000..0af7e562e --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/SignatureBuilderException.java @@ -0,0 +1,15 @@ +package com.hp.csbu.cc.middleware; + +public class SignatureBuilderException extends RuntimeException { + + private static final long serialVersionUID = -2643382825421961020L; + + public SignatureBuilderException(String msg) { + super(msg); + } + public SignatureBuilderException(String msg, Exception e) { + super(msg, e); + } + + +} diff --git a/src/main/java/com/hp/csbu/cc/middleware/SignatureExceptionHandler.java b/src/main/java/com/hp/csbu/cc/middleware/SignatureExceptionHandler.java new file mode 100644 index 000000000..a5cbc72a7 --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/SignatureExceptionHandler.java @@ -0,0 +1,99 @@ +package com.hp.csbu.cc.middleware; + +import java.io.IOException; + +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; + +//import org.apache.thrift.TException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +//import com.hp.csbu.cc.security.cs.thrift.service.ResourceException; + +public enum SignatureExceptionHandler { + + AuthConnectionException { + @Override + public void onException(Exception e, ServletResponse resp) { + AuthConnectionException ae = (AuthConnectionException) e; + logger.error(ae.getMessage() + " " + ae); + try { + ((HttpServletResponse) resp).sendError( + HttpServletResponse.SC_UNAUTHORIZED, + ExceptionHandlerUtil.getStatusText(HttpServletResponse.SC_UNAUTHORIZED)); + } catch (IOException ie) { + logger.debug("Error in writing the HTTP response " + + ie.getMessage() + " " + ie); + } + } + }, + TException { + @Override + public void onException(Exception e, ServletResponse resp) { + // TException t = (TException) e; + //logger.error("Thrift Exception " + t.getMessage() + " " + t); + try { + ((HttpServletResponse) resp).sendError( + HttpServletResponse.SC_UNAUTHORIZED, + ExceptionHandlerUtil.getStatusText(HttpServletResponse.SC_UNAUTHORIZED)); + } catch (IOException ie) { + logger.debug("Error in writing the HTTP response " + + ie.getMessage() + " " + ie); + } + } + }, + SignatureBuilderException { + @Override + public void onException(Exception e, ServletResponse resp) { + SignatureBuilderException sbe = (SignatureBuilderException) e; + logger.error(sbe.getMessage() + " " + sbe); + try { + ((HttpServletResponse) resp).sendError( + HttpServletResponse.SC_UNAUTHORIZED, + ExceptionHandlerUtil.getStatusText(HttpServletResponse.SC_UNAUTHORIZED)); + } catch (IOException ie) { + logger.debug("Error in writing the HTTP response " + + ie.getMessage() + " " + ie); + } + } + }, + AuthException { + @Override + public void onException(Exception e, ServletResponse resp) { + AuthException ae = (AuthException) e; + logger.error(ae.getMessage() + " " + ae); + try { + ((HttpServletResponse) resp).sendError( + HttpServletResponse.SC_UNAUTHORIZED, + ExceptionHandlerUtil.getStatusText(HttpServletResponse.SC_UNAUTHORIZED)); + } catch (IOException ie) { + logger.debug("Error in writing the HTTP response " + + ie.getMessage() + " " + ie); + } + } + }; + /*ResourceException { + @Override + public void onException(Exception e, ServletResponse resp) { + // ResourceException re = (ResourceException) e; + logger.error(HttpServletResponse.SC_UNAUTHORIZED + + " " + re); + String statusText = re.getDetail(); + if (statusText == null || statusText.isEmpty()) { + statusText = ExceptionHandlerUtil.getStatusText(HttpServletResponse.SC_UNAUTHORIZED); + } + try { + ((HttpServletResponse) resp).sendError( + HttpServletResponse.SC_UNAUTHORIZED, statusText); + } catch (IOException ie) { + logger.debug("Error in writing the HTTP response " + + ie.getMessage() + " " + ie); + } + } + }; */ + + final Logger logger = LoggerFactory.getLogger(SignatureExceptionHandler.class); + abstract void onException(Exception e, ServletResponse resp); + +} diff --git a/src/main/java/com/hp/csbu/cc/middleware/Signer.java b/src/main/java/com/hp/csbu/cc/middleware/Signer.java new file mode 100644 index 000000000..76edfbbd6 --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/Signer.java @@ -0,0 +1,5 @@ +package com.hp.csbu.cc.middleware; + +public interface Signer { + +} diff --git a/src/main/java/com/hp/csbu/cc/middleware/ThriftAuthClient.java.txt b/src/main/java/com/hp/csbu/cc/middleware/ThriftAuthClient.java.txt new file mode 100644 index 000000000..d63c42465 --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/ThriftAuthClient.java.txt @@ -0,0 +1,68 @@ +package com.hp.csbu.cc.middleware; + +import java.util.Map; + +import org.apache.thrift.TException; + +import com.hp.csbu.cc.security.cs.thrift.service.AuthResponse; +//import com.hp.csbu.cc.security.cs.thrift.service.AuthResponseV2; +//import com.hp.csbu.cc.security.cs.thrift.service.AuthResponseV3; +import com.hp.csbu.cc.security.cs.thrift.service.CsThriftService.Client; +import com.hp.csbu.cc.security.cs.thrift.service.ResourceException; +import com.hp.csbu.cc.security.cs.thrift.service.SigAuthRequest; + +/** + * An AuthClient adapter for Thrift. + * + * @author liemmn + * + */ +public class ThriftAuthClient implements AuthClient { + private Client thrift; + + public ThriftAuthClient(Client client) { + this.thrift = client; + } + + public void close() { + thrift.getInputProtocol().getTransport().close(); + thrift.getOutputProtocol().getTransport().close(); + thrift = null; + } + + @Override + public AuthResponse validateSignature(SigAuthRequest request) + throws ResourceException, TException { + return thrift.validateSignature(request); + } + + @Override + /*public AuthResponseV2 validateTokenForServiceEndpointV2(String token, + String serviceIds, String endpointIds, boolean includeCatalog) + throws ResourceException, TException { + return thrift.validateTokenForEndpointWithCatalog(token, null, + serviceIds, endpointIds, includeCatalog); + + } */ + public Object validateTokenForServiceEndpointV2(String token, + String serviceIds, String endpointIds, boolean includeCatalog) + throws ResourceException, TException { + return null; + } + + + + @Override + /*public AuthResponseV3 validateTokenForServiceEndpointV3(String token, + Map inputParams) + throws ResourceException, TException { + return thrift.validateTokenApiForEndpoint(token, inputParams); + } */ + + public Object validateTokenForServiceEndpointV3(String token, + Map inputParams) + throws ResourceException, TException { + return null; + } + +} diff --git a/src/main/java/com/hp/csbu/cc/middleware/ThriftClientFactory.java.txt b/src/main/java/com/hp/csbu/cc/middleware/ThriftClientFactory.java.txt new file mode 100644 index 000000000..880864978 --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/ThriftClientFactory.java.txt @@ -0,0 +1,19 @@ +package com.hp.csbu.cc.middleware; + +import org.apache.commons.pool.impl.GenericObjectPool; + +/** + * A Thrift factory. + * + * @author liemmn + * + */ +public class ThriftClientFactory extends AuthClientFactory { + + ThriftClientFactory(String host, int port, + int timeout, boolean clientAuth, String keyStore, String keyPass, + String trustStore, String trustPass) { + pool = new GenericObjectPool(new ThriftClientPoolFactory(host, port, + timeout, clientAuth, keyStore, keyPass, trustStore, trustPass)); + } +} diff --git a/src/main/java/com/hp/csbu/cc/middleware/ThriftClientPoolFactory.java.txt b/src/main/java/com/hp/csbu/cc/middleware/ThriftClientPoolFactory.java.txt new file mode 100644 index 000000000..5428ae469 --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/ThriftClientPoolFactory.java.txt @@ -0,0 +1,71 @@ +package com.hp.csbu.cc.middleware; + +import org.apache.commons.pool.BasePoolableObjectFactory; +import org.apache.thrift.protocol.TCompactProtocol; +import org.apache.thrift.protocol.TProtocol; +import org.apache.thrift.transport.TSSLTransportFactory; +import org.apache.thrift.transport.TSocket; + +import com.hp.csbu.cc.security.cs.thrift.service.CsThriftService; + +/** + * ThriftConnection pool factory. Based on Apache Commons Pool. + * + * @author liemmn + * + */ +public class ThriftClientPoolFactory extends BasePoolableObjectFactory { + private static final String PROTOCOL = "TLS"; + private static final String[] CIPHER_SUITES = new String[] { + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA", + "TLS_RSA_WITH_AES_128_CBC_SHA", "SSL_RSA_WITH_3DES_EDE_CBC_SHA", + "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA" }; + + private String host; + private int port; + private int timeout; + private boolean clientAuth; + private String keyStore; + private String keyPass; + private String trustStore; + private String trustPass; + + + public ThriftClientPoolFactory(String host, int port, int timeout, + boolean clientAuth, String keyStore, String keyPass, + String trustStore, String trustPass) { + this.host = host; + this.port = port; + this.timeout = timeout; + this.clientAuth = clientAuth; + this.keyStore = keyStore; + this.keyPass = keyPass; + this.trustStore = trustStore; + this.trustPass = trustPass; + } + + @Override + public AuthClient makeObject() { + TSSLTransportFactory.TSSLTransportParameters params = new TSSLTransportFactory.TSSLTransportParameters( + PROTOCOL, CIPHER_SUITES, clientAuth); + params.setKeyStore(keyStore, keyPass); + params.setTrustStore(trustStore, trustPass); + + try { + TSocket clientSocket = TSSLTransportFactory.getClientSocket(host, + port, timeout, params); + TProtocol proto = new TCompactProtocol(clientSocket); + return new ThriftAuthClient(new CsThriftService.Client(proto)); + } catch (Exception e) { + throw new AuthConnectionException("Failed to open socket " + e.getMessage(), e); + } + } + + @Override + public void destroyObject(Object o) { + ((ThriftAuthClient) o).close(); + o = null; + } +} diff --git a/src/main/java/com/hp/csbu/cc/middleware/TokenAuth.java b/src/main/java/com/hp/csbu/cc/middleware/TokenAuth.java new file mode 100644 index 000000000..f226e0072 --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/TokenAuth.java @@ -0,0 +1,185 @@ +package com.hp.csbu.cc.middleware; + +import java.io.IOException; +import org.apache.http.client.ClientProtocolException; +import java.util.HashMap; +import java.util.Map; + +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.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.thrift.transport.TTransportException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A token-based authentication filter. This filter uses Thrift protocol to + * communicate with the CS server. The token to validate is set via the header + * {@link #TOKEN}. + *

+ * A token is required to validate. However, if no token is presented, the + * filter will set the {@link #AUTH_IDENTITY_STATUS} request parameter to + * Invalid and let any other filter downstream to decide what to + * do. For instance, if a downstream filter knows how to deal with signature + * rather than tokens, then it will go ahead and validate with signatures. + *

+ * Upon successful validation, all the Auth request parameters will be + * populated, including information such as tenant, user and user roles, and + * passed down to the next filter downstream. + *

+ * Upon unsuccessful validation, this filter will terminate the request by + * returning a 401 (unauthorized). + * + * @author liemmn + * + */ +public class TokenAuth implements Filter, AuthConstants { + + private static final String TOKEN_NOTFOUND = "Bad Request: Token not found in the request"; + private static final String SERVICE_IDS_PARAM = "serviceIds"; + private static final String ENDPOINT_IDS_PARAM = "endpointIds"; + private static final String SERVICE_CATALOG_PARAM = "includeCatalog"; + private static final String API_VERSION_PARAM = "apiVersion"; + + private final Config appConfig = Config.getInstance(); + + private FilterConfig filterConfig; + + // Thee faithful logger + private static final Logger logger = LoggerFactory + .getLogger(TokenAuth.class); + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + this.filterConfig = filterConfig; + } + + + /** + * {@inheritDoc} + */ + public void destroy() { + FilterUtils.destroyFilter(); + } + + /** + * {@inheritDoc} + */ + public void doFilter(ServletRequest req, ServletResponse resp, + FilterChain chain) throws IOException, ServletException { + Object auth = null; + int numberOfTries = 0; + if (!appConfig.isInitialized()) { + appConfig.initialize(filterConfig); + } + int retries = appConfig.getRetries(); + long pauseTime = appConfig.getPauseTime(); + AuthClientFactory factory = appConfig.getFactory(); + + // Extract credential + String token = ((HttpServletRequest) req).getHeader(TOKEN); + + if (token == null) { + if (!appConfig.isDelayAuthDecision()) { + logger.error(HttpServletResponse.SC_UNAUTHORIZED + + " No token found."); + ((HttpServletResponse) resp).sendError( + HttpServletResponse.SC_UNAUTHORIZED, TOKEN_NOTFOUND); + return; + } else { + logger.info("No token found...Skipping"); + } + } else { + // Retrieve from cache + auth = FilterUtils.getCachedToken(token); + if (auth == null) { + // Validate credential + AuthClient client = null; + do { + try { + client = factory.getClient(); + if (appConfig.getAuthVersion().equalsIgnoreCase("v2.0")) { + auth = client.validateTokenForServiceEndpointV2(token, appConfig.getServiceIds(), + appConfig.getEndpointIds(), appConfig.isIncludeCatalog()); + } else { + auth = client.validateTokenForServiceEndpointV3(token, getInputParams()); + } + // Cache token + FilterUtils.cacheToken(token, auth); + // Return to connection pool for re-use + factory.recycle(client); + logger.debug("Successful Authentication"); + break; + } catch (TTransportException t) { + if (client != null) + factory.discard(client); + if (numberOfTries < retries) { + FilterUtils.pause(pauseTime); + logger.debug("Retrying connection after " + + pauseTime + " seconds."); + numberOfTries++; + continue; + + } else { + TokenExceptionHandler handler = TokenExceptionHandler + .valueOf("TException"); + handler.onException(t, resp, token); + } + return; + } catch (ClientProtocolException c) { + if (client != null) + factory.discard(client); + if (numberOfTries < retries) { + FilterUtils.pause(pauseTime); + logger.debug("Retrying connection after " + + pauseTime + " seconds."); + numberOfTries++; + continue; + + } else { + TokenExceptionHandler handler = TokenExceptionHandler + .valueOf("ClientProtocolException"); + handler.onException(c, resp, token); + } + return; + }catch (Exception ex) { + if (client != null) + factory.recycle(client); + TokenExceptionHandler handler = ExceptionHandlerUtil + .lookUpTokenException(ex); + handler.onException(ex, resp, token); + return; + } + } while (numberOfTries <= retries); + } else { + // Got a cached token! + logger.debug("Got cached token: " + token); + } + } + req = FilterUtils.wrapRequest(req, auth); + logger.debug("TokenAuth: Forwarding down stream to next filter/servlet"); + // Forward downstream... + chain.doFilter(req, resp); + } + + private Map getInputParams() { + Map inputParams = new HashMap(); + if (appConfig.getServiceIds() != null) { + inputParams.put(SERVICE_IDS_PARAM, appConfig.getServiceIds()); + } + if (appConfig.getEndpointIds() != null) { + inputParams.put(ENDPOINT_IDS_PARAM, appConfig.getEndpointIds()); + } + inputParams.put(SERVICE_CATALOG_PARAM, String.valueOf(appConfig.isIncludeCatalog())); + inputParams.put(API_VERSION_PARAM, appConfig.getAuthVersion()); + return inputParams; + } + + +} diff --git a/src/main/java/com/hp/csbu/cc/middleware/TokenCache.java b/src/main/java/com/hp/csbu/cc/middleware/TokenCache.java new file mode 100644 index 000000000..4c9c07d6e --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/TokenCache.java @@ -0,0 +1,22 @@ +package com.hp.csbu.cc.middleware; + +import org.apache.commons.collections4.map.PassiveExpiringMap; +/** + * Created by johnderr on 6/9/14. + */ +public class TokenCache { + private PassiveExpiringMap map; + + public TokenCache(Long timeToExpire) { + map = new PassiveExpiringMap<>(timeToExpire); + } + + public V getToken(K key) { + + return map.get(key); + } + + public void put(K key, V value) { + map.put(key,value); + } +} diff --git a/src/main/java/com/hp/csbu/cc/middleware/TokenExceptionHandler.java b/src/main/java/com/hp/csbu/cc/middleware/TokenExceptionHandler.java new file mode 100644 index 000000000..7f830ae3f --- /dev/null +++ b/src/main/java/com/hp/csbu/cc/middleware/TokenExceptionHandler.java @@ -0,0 +1,108 @@ +package com.hp.csbu.cc.middleware; + +import java.io.IOException; +import org.apache.http.client.ClientProtocolException; + +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.thrift.TException; +//import com.hp.csbu.cc.security.cs.thrift.service.ResourceException; + +public enum TokenExceptionHandler { + + AuthConnectionException { + @Override + public void onException(Exception e, ServletResponse resp, String token) { + AuthConnectionException ae = (AuthConnectionException) e; + logger.error(ae.getMessage() + " " + ae); + try { + ((HttpServletResponse) resp).sendError( + HttpServletResponse.SC_UNAUTHORIZED, + ExceptionHandlerUtil.getStatusText(HttpServletResponse.SC_UNAUTHORIZED) + + " " + token); + } catch (IOException ie) { + logger.debug("Error in writing the HTTP response " + + ie.getMessage() + " " + ie); + } + } + }, + TException { + @Override + public void onException(Exception e, ServletResponse resp, String token) { + TException t = (TException) e; + logger.error("Thrift Exception " + t.getMessage() + " " + t); + try { + ((HttpServletResponse) resp).sendError( + HttpServletResponse.SC_UNAUTHORIZED, + ExceptionHandlerUtil.getStatusText(HttpServletResponse.SC_UNAUTHORIZED) + + " " + token); + } catch (IOException ie) { + logger.debug("Error in writing the HTTP response " + + ie.getMessage() + " " + ie); + } + } + }, + ClientProtocolException { + @Override + public void onException(Exception e, ServletResponse resp, String token) { + ClientProtocolException t = (ClientProtocolException) e; + logger.error("Http Client Exception " + t.getMessage() + " " + t); + try { + ((HttpServletResponse) resp).sendError( + HttpServletResponse.SC_UNAUTHORIZED, + ExceptionHandlerUtil.getStatusText(HttpServletResponse.SC_UNAUTHORIZED) + + " " + token); + } catch (IOException ie) { + logger.debug("Error in writing the HTTP response " + + ie.getMessage() + " " + ie); + } + } + }, + /*ResourceException { + @Override + public void onException(Exception e, ServletResponse resp, String token) { + ResourceException re = (ResourceException) e; + logger.error(HttpServletResponse.SC_UNAUTHORIZED + " " + token + + " " + re); + String statusText = re.getDetail(); + if (statusText == null || statusText.isEmpty()) { + statusText = ExceptionHandlerUtil.getStatusText(HttpServletResponse.SC_UNAUTHORIZED); + } + try { + ((HttpServletResponse) resp).sendError( + HttpServletResponse.SC_UNAUTHORIZED, statusText + " " + + token); + } catch (IOException ie) { + logger.debug("Error in writing the HTTP response " + + ie.getMessage() + " " + ie); + } + } + },*/ + + AuthException { + @Override + public void onException(Exception e, ServletResponse resp, String token) { + AuthException ae = (AuthException) e; + logger.error(ae.getMessage() + " " + ae); + String statusText = ae.getMessage(); + if (statusText == null || statusText.isEmpty()) { + statusText = ExceptionHandlerUtil.getStatusText(HttpServletResponse.SC_UNAUTHORIZED); + } + try { + ((HttpServletResponse) resp).sendError( + HttpServletResponse.SC_UNAUTHORIZED, + statusText + " " + token); + } catch (IOException ie) { + logger.debug("Error in writing the HTTP response " + + ie.getMessage() + " " + ie); + } + } + }; + + final Logger logger = LoggerFactory.getLogger(TokenExceptionHandler.class); + abstract void onException(Exception e, ServletResponse resp, String token); +} diff --git a/src/main/java/com/hpcloud/mon/infrastructure/servlet/PreAuthenticationFilter.java b/src/main/java/com/hpcloud/mon/infrastructure/servlet/PreAuthenticationFilter.java index f92f4819e..6e6752e15 100644 --- a/src/main/java/com/hpcloud/mon/infrastructure/servlet/PreAuthenticationFilter.java +++ b/src/main/java/com/hpcloud/mon/infrastructure/servlet/PreAuthenticationFilter.java @@ -34,7 +34,7 @@ import org.slf4j.LoggerFactory; import com.hpcloud.mon.resource.exception.Exceptions; import com.hpcloud.mon.resource.exception.Exceptions.FaultType; -import com.hp.csbu.cc.middleware.ExceptionHandler.*; +//import com.hp.csbu.cc.middleware.ExceptionHandler.*; /** * Authenticates requests using header information from the CsMiddleware. Provides the X-TENANT-ID