/* * Copyright (C) 2005-2012 BetaCONCEPT Limited * * This file is part of Astroboa. * * Astroboa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Astroboa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Astroboa. If not, see <http://www.gnu.org/licenses/>. */ package org.betaconceptframework.astroboa.service.secure.security; import java.util.Timer; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.PostConstruct; import javax.annotation.Resource; import javax.ejb.Local; import javax.ejb.Singleton; import org.apache.commons.lang.StringUtils; import org.betaconceptframework.astroboa.api.model.exception.CmsException; import org.betaconceptframework.astroboa.api.model.exception.TokenExpirationException; import org.betaconceptframework.astroboa.api.service.CacheService; import org.betaconceptframework.astroboa.context.AstroboaClientContext; import org.betaconceptframework.astroboa.context.AstroboaClientContextHolder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; /** * Service providing methods for managing security assets, like authentication token * * @Service An extension offered by JBoss EJB 3.0 is the notion of a @org.jboss.annotation.ejb.Service annotated bean. * They are singleton beans and are not pooled, so only one instance of the bean exists in the server. * They can have both @Remote and @Local interfaces so they can be accessed by java clients. * When different clients look up the interfaces for @Service beans, all clients will work on * the same instance of the bean on the server. When installing the bean it gets given a * JMX ObjectName in the MBean server it runs on. The default is * * jboss.j2ee:service=EJB3,name=<Fully qualified name of @Service bean>,type=service * You can override this default ObjectName by specifying the objectName attribute of the @Service annotation. * * @author Gregory Chomatas (gchomatas@betaconcept.com) * @author Savvas Triantafyllou (striantafyllou@betaconcept.com) * */ @Singleton(name="SecurityService") @Local({SecurityService.class}) public class SecurityServiceBean implements SecurityService { private final Logger logger = LoggerFactory.getLogger(getClass()); private ConcurrentHashMap<String, AuthenticationTokenEntry> authenticationTokenEntries = new ConcurrentHashMap<String, AuthenticationTokenEntry>(); private final Timer authenticationTokenRemovalTimer = new Timer(); @Resource(name="astroboa.engine.context", mappedName="java:jboss/astroboa.engine.context") private ApplicationContext springManagedRepositoryServicesContext; private CacheService cacheService; @PostConstruct public void start() throws Exception { cacheService = (CacheService) springManagedRepositoryServicesContext.getBean("cacheService"); } public void registerAndActivateClientContextForAuthenticationToken(String authenticationToken){ if (StringUtils.isBlank(authenticationToken)){ throw new CmsException("Authentication token is null"); } if (authenticationTokenEntries.containsKey(authenticationToken)){ AuthenticationTokenEntry authenticationTokenEntry = authenticationTokenEntries.get(authenticationToken); if (authenticationTokenEntry == null){ throw new TokenExpirationException("No Client Context found for authentication token "+authenticationToken); } //Place repository context to current thread AstroboaClientContext clientContext = authenticationTokenEntry.getClientContext(); AstroboaClientContextHolder.registerClientContext(clientContext, true); if (logger.isDebugEnabled()){ logger.debug("Successfully activate in {} {}", Thread.currentThread().getName(), clientContext); } resetIdleTime(authenticationToken, authenticationTokenEntry); } else{ throw new TokenExpirationException("No Client Context found for authentication token "+authenticationToken); } } private void resetIdleTime(String authenticationToken, AuthenticationTokenEntry authenticationTokenEntry) { //Reset idle time if (authenticationTokenEntry.getAuthenticationTokenTimeout() > 0){ RemoveAuthenticationTokenTask newTask = new RemoveAuthenticationTokenTask(authenticationToken, this); authenticationTokenRemovalTimer.schedule(newTask, authenticationTokenEntry.getAuthenticationTokenTimeout()); authenticationTokenEntry.resetIdleTime(newTask); } else{ authenticationTokenEntry.resetIdleTime(null); } } public void addClientContexToValidContextMap(AstroboaClientContext clientContext, boolean authenticactionTokenNeverExpires) { if (clientContext== null ){ throw new CmsException("Found no active astroboa client context associated with current thread "+Thread.currentThread().toString()); } String authenticationToken = clientContext.getAuthenticationToken(); if (StringUtils.isBlank(authenticationToken)){ throw new CmsException("Found no authentication token associated with current thread "+Thread.currentThread().toString()); } AuthenticationTokenEntry newAuthenticationTokenEntry = new AuthenticationTokenEntry(clientContext); AuthenticationTokenEntry authenticationTokenEntry = authenticationTokenEntries.putIfAbsent(authenticationToken, newAuthenticationTokenEntry); if (authenticationTokenEntry == null){ authenticationTokenEntry = newAuthenticationTokenEntry; } if (logger.isDebugEnabled()){ logger.debug("AuthenticationToken Map {} entry for authenticationToken {}", (authenticationTokenEntries.containsKey(authenticationToken) ? "contains " : " does not contain " ), authenticationToken); } if (! authenticactionTokenNeverExpires){ //Default value int authenticationTokenTimeout = 30 * 60 * 1000; //Create task to invalidate authentication token if (clientContext.getRepositoryContext() != null && clientContext.getRepositoryContext().getSecurityContext() != null){ authenticationTokenTimeout = clientContext.getRepositoryContext().getSecurityContext().getAuthenticationTokenTimeout() * 60 * 1000; } authenticationTokenEntry.setAuthenticationTokenTimeout(authenticationTokenTimeout); if (authenticationTokenTimeout > 0){ RemoveAuthenticationTokenTask removeAuthenticationTokenTask = new RemoveAuthenticationTokenTask(authenticationToken, this); authenticationTokenEntry.resetIdleTime(removeAuthenticationTokenTask); authenticationTokenRemovalTimer.schedule(removeAuthenticationTokenTask, authenticationTokenTimeout); } else{ authenticationTokenEntry.resetIdleTime(null); } } else{ authenticationTokenEntry.setAuthenticationTokenTimeout(-1); } if (logger.isDebugEnabled()){ logger.debug("Add {} to context map(size:{}) for {}. AuthenticationToken timeout {} ms (-1 stands for non expireable)", new Object[]{clientContext, authenticationTokenEntries.size(), Thread.currentThread().getName(), authenticationTokenEntry.getAuthenticationTokenTimeout()}); } } public void purgeAuthenticationToken(String authenticationToken) { if (authenticationToken != null){ //Remove all related content from cache if (cacheService != null){ cacheService.clearCacheForAuthenticationToken(authenticationToken); } else{ logger.warn("Could not find cache service. Authentication Token related cached items will be expired once their region expires"); } //Remove authentication token from map authenticationTokenEntries.remove(authenticationToken); logger.debug("Authentication Token {} has been successfully removed from cache and map (size: {})", authenticationToken,authenticationTokenEntries.size()); } } public void resetAuthenticationTokenTimeout(String authenticationToken) { if (StringUtils.isBlank(authenticationToken)){ logger.warn("Authentication token is null. Cannot reset token timeout "); return; } AuthenticationTokenEntry authenticationTokenEntry = authenticationTokenEntries.get(authenticationToken); if (authenticationTokenEntry != null){ resetIdleTime(authenticationToken, authenticationTokenEntry); if (logger.isDebugEnabled()){ logger.debug("Successfully reset authentication timeout in {} {}", Thread.currentThread().getName(), authenticationTokenEntry.getClientContext()); } } else{ logger.warn("No Client Context found for authentication token {}. Cannot reset token timeout ", authenticationToken); } } @Override public boolean tokenHasExpired(String authenticationToken) { return authenticationToken == null || ( authenticationTokenEntries != null && ! authenticationTokenEntries.containsKey(authenticationToken)); } }