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    }