/** * GRANITE DATA SERVICES * Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S. * * This file is part of the Granite Data Services Platform. * * Granite Data Services 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 2.1 of the License, or (at your option) any later version. * * Granite Data Services 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 this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA, or see <http://www.gnu.org/licenses/>. */ package org.granite.messaging.service.security; import java.io.UnsupportedEncodingException; import java.security.Principal; import java.util.Date; import javax.servlet.http.HttpSession; import org.granite.clustering.DistributedData; import org.granite.config.GraniteConfig; import org.granite.context.GraniteContext; import org.granite.logging.Logger; import org.granite.messaging.amf.process.AMF3MessageProcessor; import org.granite.messaging.webapp.HttpGraniteContext; import org.granite.messaging.webapp.ServletGraniteContext; import org.granite.util.Base64; import flex.messaging.messages.Message; /** * Abstract implementation of the {@link SecurityService} interface. This class mainly contains * utility methods helping with actual implementations. * * @author Franck WOLFF */ public abstract class AbstractSecurityService implements SecurityService { private static final Logger log = Logger.getLogger(AbstractSecurityService.class); public static final String AUTH_TYPE = "granite-security"; public void prelogin(HttpSession session, Object request, String servletName) { } /** * A default implementation of the basic login method, passing null as the extra charset * parameter. Mainly here for compatibility purpose. * * @param credentials the login:password pair (must be a base64/ISO-8859-1 encoded string). */ public Principal login(Object credentials) throws SecurityServiceException { return login(credentials, null); } /** * Try to login by using remote credentials (see Flex method RemoteObject.setRemoteCredentials()). * This method must be called at the beginning of {@link SecurityService#authorize(AbstractSecurityContext)}. * * @param context the current security context. * @throws SecurityServiceException if login fails. */ protected void startAuthorization(AbstractSecurityContext context) throws SecurityServiceException { // Get credentials set with RemoteObject.setRemoteCredentials() and login. Object credentials = context.getMessage().getHeader(Message.REMOTE_CREDENTIALS_HEADER); if (credentials != null && !("".equals(credentials))) login(credentials, (String)context.getMessage().getHeader(Message.REMOTE_CREDENTIALS_CHARSET_HEADER)); // Check session expiration if (GraniteContext.getCurrentInstance() instanceof ServletGraniteContext) { HttpSession session = ((ServletGraniteContext)GraniteContext.getCurrentInstance()).getSession(false); if (session == null) return; long serverTime = new Date().getTime(); try { Long lastAccessedTime = (Long)session.getAttribute(GraniteContext.SESSION_LAST_ACCESSED_TIME_KEY); if (lastAccessedTime != null && lastAccessedTime + session.getMaxInactiveInterval()*1000L + 1000L < serverTime) { log.info("No user-initiated action since last access, force session invalidation %s", session.getId()); session.invalidate(); } } catch (IllegalStateException e) { } } } /** * Invoke a service method (EJB3, Spring, Seam, etc...) after a successful authorization. * This method must be called at the end of {@link SecurityService#authorize(AbstractSecurityContext)}. * * @param context the current security context. * @throws Exception if anything goes wrong with service invocation. */ protected Object endAuthorization(AbstractSecurityContext context) throws Exception { return context.invoke(); } /** * A security service can optionally indicate that it's able to authorize requests that are not HTTP requests * (websockets). In this case the method {@link SecurityService#authorize(AbstractSecurityContext)} will be * invoked in a {@link ServletGraniteContext} and not in a {@link HttpGraniteContext} * @return true is a {@link HttpGraniteContext} is mandated */ public boolean acceptsContext() { return GraniteContext.getCurrentInstance() instanceof ServletGraniteContext; } /** * Decode credentials encoded in base 64 (in the form of "username:password"), as they have been * sent by a RemoteObject. * * @param credentials base 64 encoded credentials. * @return an array containing two decoded Strings, username and password. * @throws IllegalArgumentException if credentials isn't a String. * @throws SecurityServiceException if credentials are invalid (bad encoding or missing ':'). */ protected String[] decodeBase64Credentials(Object credentials, String charset) { if (!(credentials instanceof String)) throw new IllegalArgumentException("Credentials should be a non null String: " + (credentials != null ? credentials.getClass().getName() : null)); if (charset == null) charset = "ISO-8859-1"; byte[] bytes = Base64.decode((String)credentials); String decoded; try { decoded = new String(bytes, charset); } catch (UnsupportedEncodingException e) { throw SecurityServiceException.newInvalidCredentialsException("ISO-8859-1 encoding not supported ???"); } int colon = decoded.indexOf(':'); if (colon == -1) throw SecurityServiceException.newInvalidCredentialsException("No colon"); return new String[] {decoded.substring(0, colon), decoded.substring(colon + 1)}; } /** * Handle a security exception. This method is called in * {@link AMF3MessageProcessor#processCommandMessage(flex.messaging.messages.CommandMessage)} * whenever a SecurityService occurs and does nothing by default. * * @param e the security exception. */ public void handleSecurityException(SecurityServiceException e) { } /** * Try to save current credentials in distributed data, typically a user session attribute. This method * must be called at the end of a successful {@link SecurityService#login(Object)} operation and is useful * in clustered environments with session replication in order to transparently re-authenticate the * user when failing over. * * @param credentials the credentials to be saved in distributed data. */ protected void endLogin(Object credentials, String charset) { try { DistributedData gdd = ((GraniteConfig)GraniteContext.getCurrentInstance().getGraniteConfig()).getDistributedDataFactory().getInstance(); if (gdd != null) { gdd.setCredentials(credentials); gdd.setCredentialsCharset(charset); } } catch (Exception e) { log.error(e, "Could not save credentials in distributed data"); } } /** * Try to re-authenticate the current user with credentials previously saved in distributed data. * This method must be called in the {@link SecurityService#authorize(AbstractSecurityContext)} * method when the current user principal is null. * * @return <tt>true</tt> if relogin was successful, <tt>false</tt> otherwise. * * @see #endLogin(Object, String) */ protected boolean tryRelogin() { try { DistributedData gdd = ((GraniteConfig)GraniteContext.getCurrentInstance().getGraniteConfig()).getDistributedDataFactory().getInstance(); if (gdd != null) { Object credentials = gdd.getCredentials(); if (credentials != null) { String charset = gdd.getCredentialsCharset(); try { login(credentials, charset); return true; } catch (SecurityServiceException e) { } } } } catch (Exception e) { log.error(e, "Could not relogin with credentials found in distributed data"); } return false; } /** * Try to remove credentials previously saved in distributed data. This method must be called in the * {@link SecurityService#logout()} method. * * @see #endLogin(Object, String) */ protected void endLogout() { try { DistributedData gdd = ((GraniteConfig)GraniteContext.getCurrentInstance().getGraniteConfig()).getDistributedDataFactory().getInstance(); if (gdd != null) { gdd.removeCredentials(); gdd.removeCredentialsCharset(); } } catch (Exception e) { log.error(e, "Could not remove credentials from distributed data"); } } }