001 /*
002 GRANITE DATA SERVICES
003 Copyright (C) 2011 GRANITE DATA SERVICES S.A.S.
004
005 This file is part of Granite Data Services.
006
007 Granite Data Services is free software; you can redistribute it and/or modify
008 it under the terms of the GNU Library General Public License as published by
009 the Free Software Foundation; either version 2 of the License, or (at your
010 option) any later version.
011
012 Granite Data Services is distributed in the hope that it will be useful, but
013 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
015 for more details.
016
017 You should have received a copy of the GNU Library General Public License
018 along with this library; if not, see <http://www.gnu.org/licenses/>.
019 */
020
021 package org.granite.messaging.service.security;
022
023 import java.io.UnsupportedEncodingException;
024
025 import org.granite.clustering.GraniteDistributedData;
026 import org.granite.clustering.GraniteDistributedDataFactory;
027 import org.granite.logging.Logger;
028 import org.granite.messaging.amf.process.AMF3MessageProcessor;
029 import org.granite.util.Base64;
030
031 import flex.messaging.messages.Message;
032
033 /**
034 * Abstract implementation of the {@link SecurityService} interface. This class mainly contains
035 * utility methods helping with actual implementations.
036 *
037 * @author Franck WOLFF
038 */
039 public abstract class AbstractSecurityService implements SecurityService {
040
041 private static final Logger log = Logger.getLogger(AbstractSecurityService.class);
042
043 public static final String AUTH_TYPE = "granite-security";
044
045 /**
046 * Try to login by using remote credentials (see Flex method RemoteObject.setRemoteCredentials()).
047 * This method must be called at the beginning of {@link SecurityService#authorize(AbstractSecurityContext)}.
048 *
049 * @param context the current security context.
050 * @throws SecurityServiceException if login fails.
051 */
052 protected void startAuthorization(AbstractSecurityContext context) throws SecurityServiceException {
053 // Get credentials set with RemoteObject.setRemoteCredentials() and login.
054 Object credentials = context.getMessage().getHeaders().get(Message.REMOTE_CREDENTIALS_HEADER);
055 if (credentials != null && !("".equals(credentials)))
056 login(credentials);
057 }
058
059 /**
060 * Invoke a service method (EJB3, Spring, Seam, etc...) after a successful authorization.
061 * This method must be called at the end of {@link SecurityService#authorize(AbstractSecurityContext)}.
062 *
063 * @param context the current security context.
064 * @throws Exception if anything goes wrong with service invocation.
065 */
066 protected Object endAuthorization(AbstractSecurityContext context) throws Exception {
067 return context.invoke();
068 }
069
070 /**
071 * Decode credentails encoded in base 64 (in the form of "username:password"), as they have been
072 * sent by a RemoteObject.
073 *
074 * @param credentials base 64 encoded credentials.
075 * @return an array containing two decoded Strings, username and password.
076 * @throws IllegalArgumentException if credentials isn't a String.
077 * @throws SecurityServiceException if credentials are invalid (bad encoding or missing ':').
078 */
079 protected String[] decodeBase64Credentials(Object credentials) {
080 if (!(credentials instanceof String))
081 throw new IllegalArgumentException("Credentials should be a non null String: " +
082 (credentials != null ? credentials.getClass().getName() : null));
083
084 byte[] bytes = Base64.decode((String)credentials);
085 String decoded = "";
086 try {
087 decoded = new String(bytes, "ISO-8859-1");
088 }
089 catch (UnsupportedEncodingException e) {
090 throw SecurityServiceException.newInvalidCredentialsException("ISO-8859-1 encoding not supported ???");
091 }
092
093 int colon = decoded.indexOf(':');
094 if (colon == -1)
095 throw SecurityServiceException.newInvalidCredentialsException("No colon");
096
097 return new String[] {decoded.substring(0, colon), decoded.substring(colon + 1)};
098 }
099
100 /**
101 * Handle a security exception. This method is called in
102 * {@link AMF3MessageProcessor#processCommandMessage(flex.messaging.messages.CommandMessage)}
103 * whenever a SecurityService occurs and does nothing by default.
104 *
105 * @param e the security exception.
106 */
107 public void handleSecurityException(SecurityServiceException e) {
108 }
109
110 /**
111 * Try to save current credentials in distributed data, typically a user session attribute. This method
112 * must be called at the end of a successful {@link SecurityService#login(Object)} operation and is useful
113 * in clustered environments with session replication in order to transparently re-authenticate the
114 * user when failing over.
115 *
116 * @param credentials the credentials to be saved in distributed data.
117 */
118 protected void endLogin(Object credentials) {
119 try {
120 GraniteDistributedData gdd = GraniteDistributedDataFactory.getInstance();
121 if (gdd != null)
122 gdd.setCredentials(credentials);
123 }
124 catch (Exception e) {
125 log.error(e, "Could not save credentials in distributed data");
126 }
127 }
128
129 /**
130 * Try to re-authenticate the current user with credentials previously saved in distributed data.
131 * This method must be called in the {@link SecurityService#authorize(AbstractSecurityContext)}
132 * method when the current user principal is null.
133 *
134 * @return <tt>true</tt> if relogin was successful, <tt>false</tt> otherwise.
135 *
136 * @see #endLogin(Object)
137 */
138 protected boolean tryRelogin() {
139 try {
140 GraniteDistributedData gdd = GraniteDistributedDataFactory.getInstance();
141 if (gdd != null) {
142 Object credentials = gdd.getCredentials();
143 if (credentials != null) {
144 try {
145 login(credentials);
146 return true;
147 }
148 catch (SecurityServiceException e) {
149 }
150 }
151 }
152 }
153 catch (Exception e) {
154 log.error(e, "Could not relogin with credentials found in distributed data");
155 }
156 return false;
157 }
158
159 /**
160 * Try to remove credentials previously saved in distributed data. This method must be called in the
161 * {@link SecurityService#logout()} method.
162 *
163 * @see #endLogin(Object)
164 */
165 protected void endLogout() {
166 try {
167 GraniteDistributedData gdd = GraniteDistributedDataFactory.getInstance();
168 if (gdd != null)
169 gdd.removeCredentials();
170 }
171 catch (Exception e) {
172 log.error(e, "Could not remove credentials from distributed data");
173 }
174 }
175 }