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 * A default implementation of the basic login method, passing null as the extra charset
047 * parameter. Mainly here for compatibility purpose.
048 *
049 * @param credentials the login:password pair (must be a base64/ISO-8859-1 encoded string).
050 */
051 public void login(Object credentials) throws SecurityServiceException {
052 login(credentials, null);
053 }
054
055 /**
056 * Try to login by using remote credentials (see Flex method RemoteObject.setRemoteCredentials()).
057 * This method must be called at the beginning of {@link SecurityService#authorize(AbstractSecurityContext)}.
058 *
059 * @param context the current security context.
060 * @throws SecurityServiceException if login fails.
061 */
062 protected void startAuthorization(AbstractSecurityContext context) throws SecurityServiceException {
063 // Get credentials set with RemoteObject.setRemoteCredentials() and login.
064 Object credentials = context.getMessage().getHeader(Message.REMOTE_CREDENTIALS_HEADER);
065 if (credentials != null && !("".equals(credentials)))
066 login(credentials, (String)context.getMessage().getHeader(Message.REMOTE_CREDENTIALS_CHARSET_HEADER));
067 }
068
069 /**
070 * Invoke a service method (EJB3, Spring, Seam, etc...) after a successful authorization.
071 * This method must be called at the end of {@link SecurityService#authorize(AbstractSecurityContext)}.
072 *
073 * @param context the current security context.
074 * @throws Exception if anything goes wrong with service invocation.
075 */
076 protected Object endAuthorization(AbstractSecurityContext context) throws Exception {
077 return context.invoke();
078 }
079
080 /**
081 * Decode credentails encoded in base 64 (in the form of "username:password"), as they have been
082 * sent by a RemoteObject.
083 *
084 * @param credentials base 64 encoded credentials.
085 * @return an array containing two decoded Strings, username and password.
086 * @throws IllegalArgumentException if credentials isn't a String.
087 * @throws SecurityServiceException if credentials are invalid (bad encoding or missing ':').
088 */
089 protected String[] decodeBase64Credentials(Object credentials, String charset) {
090 if (!(credentials instanceof String))
091 throw new IllegalArgumentException("Credentials should be a non null String: " +
092 (credentials != null ? credentials.getClass().getName() : null));
093
094 if (charset == null)
095 charset = "ISO-8859-1";
096
097 byte[] bytes = Base64.decode((String)credentials);
098 String decoded;
099 try {
100 decoded = new String(bytes, charset);
101 }
102 catch (UnsupportedEncodingException e) {
103 throw SecurityServiceException.newInvalidCredentialsException("ISO-8859-1 encoding not supported ???");
104 }
105
106 int colon = decoded.indexOf(':');
107 if (colon == -1)
108 throw SecurityServiceException.newInvalidCredentialsException("No colon");
109
110 return new String[] {decoded.substring(0, colon), decoded.substring(colon + 1)};
111 }
112
113 /**
114 * Handle a security exception. This method is called in
115 * {@link AMF3MessageProcessor#processCommandMessage(flex.messaging.messages.CommandMessage)}
116 * whenever a SecurityService occurs and does nothing by default.
117 *
118 * @param e the security exception.
119 */
120 public void handleSecurityException(SecurityServiceException e) {
121 }
122
123 /**
124 * Try to save current credentials in distributed data, typically a user session attribute. This method
125 * must be called at the end of a successful {@link SecurityService#login(Object)} operation and is useful
126 * in clustered environments with session replication in order to transparently re-authenticate the
127 * user when failing over.
128 *
129 * @param credentials the credentials to be saved in distributed data.
130 */
131 protected void endLogin(Object credentials, String charset) {
132 try {
133 GraniteDistributedData gdd = GraniteDistributedDataFactory.getInstance();
134 if (gdd != null) {
135 gdd.setCredentials(credentials);
136 gdd.setCredentialsCharset(charset);
137 }
138 }
139 catch (Exception e) {
140 log.error(e, "Could not save credentials in distributed data");
141 }
142 }
143
144 /**
145 * Try to re-authenticate the current user with credentials previously saved in distributed data.
146 * This method must be called in the {@link SecurityService#authorize(AbstractSecurityContext)}
147 * method when the current user principal is null.
148 *
149 * @return <tt>true</tt> if relogin was successful, <tt>false</tt> otherwise.
150 *
151 * @see #endLogin(Object, String)
152 */
153 protected boolean tryRelogin() {
154 try {
155 GraniteDistributedData gdd = GraniteDistributedDataFactory.getInstance();
156 if (gdd != null) {
157 Object credentials = gdd.getCredentials();
158 if (credentials != null) {
159 String charset = gdd.getCredentialsCharset();
160 try {
161 login(credentials, charset);
162 return true;
163 }
164 catch (SecurityServiceException e) {
165 }
166 }
167 }
168 }
169 catch (Exception e) {
170 log.error(e, "Could not relogin with credentials found in distributed data");
171 }
172 return false;
173 }
174
175 /**
176 * Try to remove credentials previously saved in distributed data. This method must be called in the
177 * {@link SecurityService#logout()} method.
178 *
179 * @see #endLogin(Object, String)
180 */
181 protected void endLogout() {
182 try {
183 GraniteDistributedData gdd = GraniteDistributedDataFactory.getInstance();
184 if (gdd != null) {
185 gdd.removeCredentials();
186 gdd.removeCredentialsCharset();
187 }
188 }
189 catch (Exception e) {
190 log.error(e, "Could not remove credentials from distributed data");
191 }
192 }
193 }