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.lang.reflect.Field;
024    import java.lang.reflect.InvocationTargetException;
025    import java.lang.reflect.Method;
026    import java.security.Principal;
027    import java.util.Map;
028    
029    import javax.servlet.http.HttpServletRequest;
030    import javax.servlet.http.HttpServletRequestWrapper;
031    import javax.servlet.http.HttpSession;
032    
033    import org.apache.catalina.Engine;
034    import org.apache.catalina.Realm;
035    import org.apache.catalina.Server;
036    import org.apache.catalina.ServerFactory;
037    import org.apache.catalina.Service;
038    import org.apache.catalina.Session;
039    import org.apache.catalina.authenticator.Constants;
040    import org.apache.catalina.connector.RequestFacade;
041    import org.apache.catalina.connector.Request;
042    import org.granite.context.GraniteContext;
043    import org.granite.logging.Logger;
044    import org.granite.messaging.webapp.HttpGraniteContext;
045    
046    
047    /**
048     * @author Franck WOLFF
049     */
050    public class GlassFishV3SecurityService extends AbstractSecurityService {
051            
052            private static final Logger log = Logger.getLogger(GlassFishV3SecurityService.class);
053        
054        private static Method authenticate = null;
055        static {
056            // GlassFish V3.0
057            try {
058                    authenticate = Realm.class.getMethod("authenticate", String.class, String.class);
059                    log.info("Detected GlassFish v3.0 authentication");
060            }
061            catch (NoSuchMethodException e) {
062            }
063            catch (NoSuchMethodError e) {
064            }
065            // GlassFish V3.1+
066            if (authenticate == null) try {
067                    authenticate = Realm.class.getMethod("authenticate", String.class, char[].class);
068                    log.info("Detected GlassFish v3.1+ authentication");
069            }
070            catch (NoSuchMethodException e) {
071            }
072            catch (NoSuchMethodError e) {
073            }
074            if (authenticate == null)
075                    throw new ExceptionInInitializerError("Could not find any supported Realm.authenticate method");
076    
077        }
078        
079        private static Principal authenticate(Realm realm, String username, String password) {
080            try {
081                    if (authenticate.getParameterTypes()[1].equals(String.class))
082                            return (Principal)authenticate.invoke(realm, username, password);
083                    return (Principal)authenticate.invoke(realm, username, password.toCharArray());
084            }
085            catch (Exception e) {
086                    throw new RuntimeException(e);
087            }
088        }
089    
090        private final Field requestField;
091        private Engine engine = null;
092    
093        public GlassFishV3SecurityService() {
094            super();
095            try {
096                // We need to access the org.apache.catalina.connector.Request field from
097                // a org.apache.catalina.connector.RequestFacade. Unfortunately there is
098                // no public getter for this field (and I don't want to create a Valve)...
099                requestField = RequestFacade.class.getDeclaredField("request");
100                requestField.setAccessible(true);
101            } catch (Exception e) {
102                throw new RuntimeException("Could not get 'request' field in GlassFish V3 RequestFacade", e);
103            }
104        }
105    
106        protected Field getRequestField() {
107            return requestField;
108        }
109    
110        protected Engine getEngine() {
111            return engine;
112        }
113    
114        public void configure(Map<String, String> params) {
115            String serviceId = params.get("service");
116    
117            Server server = ServerFactory.getServer();
118            if (server == null)
119                throw new NullPointerException("Could not get GlassFish V3 server");
120    
121            Service service = null;
122            if (serviceId != null)
123                service = server.findService(serviceId);
124            else {
125                Service[] services = server.findServices();
126                if (services != null && services.length > 0)
127                    service = services[0];
128            }
129            if (service == null)
130                throw new NullPointerException("Could not find GlassFish V3 service for: " + (serviceId != null ? serviceId : "(default)"));
131    
132            engine = (Engine)service.getContainer();
133            if (engine == null)
134                throw new NullPointerException("Could not find GlassFish V3 container for: " + (serviceId != null ? serviceId : "(default)"));
135        }
136    
137        public void login(Object credentials) throws SecurityServiceException {
138            String[] decoded = decodeBase64Credentials(credentials);
139    
140            HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
141            HttpServletRequest httpRequest = context.getRequest();
142    
143            Request request = getRequest(httpRequest);
144            Realm realm = request.getContext().getRealm();
145    
146            Principal principal = authenticate(realm, decoded[0], decoded[1]);
147            if (principal == null)
148                throw SecurityServiceException.newInvalidCredentialsException("Wrong username or password");
149            
150            request.setAuthType(AUTH_TYPE);
151            request.setUserPrincipal(principal);
152    
153            Session session = request.getSessionInternal();
154            session.setAuthType(AUTH_TYPE);
155            session.setPrincipal(principal);
156            session.setNote(Constants.SESS_USERNAME_NOTE, decoded[0]);
157            session.setNote(Constants.SESS_PASSWORD_NOTE, decoded[1]);
158            
159            endLogin(credentials);
160        }
161    
162        public Object authorize(AbstractSecurityContext context) throws Exception {
163    
164            startAuthorization(context);
165    
166            HttpGraniteContext graniteContext = (HttpGraniteContext)GraniteContext.getCurrentInstance();
167            HttpServletRequest httpRequest = graniteContext.getRequest();
168            Request request = getRequest(httpRequest);
169            Session session = request.getSessionInternal(false);
170            
171            Principal principal = null;
172            if (session != null) {
173                    request.setAuthType(session.getAuthType());
174                    principal = session.getPrincipal();
175                    if (principal == null && tryRelogin())
176                            principal = session.getPrincipal();
177            }
178            request.setUserPrincipal(principal);
179    
180            if (context.getDestination().isSecured()) {
181                if (principal == null) {
182                    if (httpRequest.getRequestedSessionId() != null) {
183                        HttpSession httpSession = httpRequest.getSession(false);
184                        if (httpSession == null || httpRequest.getRequestedSessionId().equals(httpSession.getId()))
185                            throw SecurityServiceException.newSessionExpiredException("Session expired");
186                    }
187                    throw SecurityServiceException.newNotLoggedInException("User not logged in");
188                }
189                
190                boolean accessDenied = true;
191                for (String role : context.getDestination().getRoles()) {
192                    if (httpRequest.isUserInRole(role)) {
193                        accessDenied = false;
194                        break;
195                    }
196                }
197                if (accessDenied)
198                    throw SecurityServiceException.newAccessDeniedException("User not in required role");
199            }
200    
201            try {
202                return endAuthorization(context);
203            } catch (InvocationTargetException e) {
204                for (Throwable t = e; t != null; t = t.getCause()) {
205                    // Don't create a dependency to javax.ejb in SecurityService...
206                    if (t instanceof SecurityException ||
207                        "javax.ejb.EJBAccessException".equals(t.getClass().getName()))
208                        throw SecurityServiceException.newAccessDeniedException(t.getMessage());
209                }
210                throw e;
211            }
212        }
213    
214        public void logout() throws SecurityServiceException {
215            HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
216    
217            Session session = getSession(context.getRequest(), false);
218            if (session != null && session.getPrincipal() != null) {
219                session.setAuthType(null);
220                session.setPrincipal(null);
221                session.removeNote(Constants.SESS_USERNAME_NOTE);
222                session.removeNote(Constants.SESS_PASSWORD_NOTE);
223                
224                endLogout();
225                
226                session.expire();
227            }
228        }
229    
230        protected Principal getPrincipal(HttpServletRequest httpRequest) {
231            Request request = getRequest(httpRequest);
232            Session session = request.getSessionInternal(false);
233            return (session != null ? session.getPrincipal() : null);
234        }
235    
236        protected Session getSession(HttpServletRequest httpRequest, boolean create) {
237            Request request = getRequest(httpRequest);
238            return request.getSessionInternal(create);
239        }
240    
241        protected Request getRequest(HttpServletRequest request) {
242            while (request instanceof HttpServletRequestWrapper)
243                request = (HttpServletRequest)((HttpServletRequestWrapper)request).getRequest();
244            try {
245                return (Request)requestField.get(request);
246            } catch (Exception e) {
247                throw new RuntimeException("Could not get GlassFish V3 request", e);
248            }
249        }
250    
251        protected Realm getRealm(HttpServletRequest request) {
252            Request creq = getRequest(request);
253            return creq.getContext().getRealm();
254        }
255    }