001    /**
002     *   GRANITE DATA SERVICES
003     *   Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S.
004     *
005     *   This file is part of the Granite Data Services Platform.
006     *
007     *   Granite Data Services is free software; you can redistribute it and/or
008     *   modify it under the terms of the GNU Lesser General Public
009     *   License as published by the Free Software Foundation; either
010     *   version 2.1 of the License, or (at your option) any later version.
011     *
012     *   Granite Data Services is distributed in the hope that it will be useful,
013     *   but WITHOUT ANY WARRANTY; without even the implied warranty of
014     *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
015     *   General Public License for more details.
016     *
017     *   You should have received a copy of the GNU Lesser General Public
018     *   License along with this library; if not, write to the Free Software
019     *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
020     *   USA, or see <http://www.gnu.org/licenses/>.
021     */
022    package org.granite.messaging.service.security;
023    
024    import java.lang.reflect.Field;
025    import java.lang.reflect.InvocationTargetException;
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.Context;
034    import org.apache.catalina.Engine;
035    import org.apache.catalina.Host;
036    import org.apache.catalina.Realm;
037    import org.apache.catalina.Server;
038    import org.apache.catalina.ServerFactory;
039    import org.apache.catalina.Service;
040    import org.apache.catalina.Session;
041    import org.apache.catalina.authenticator.Constants;
042    import org.apache.catalina.connector.Request;
043    import org.apache.catalina.connector.RequestFacade;
044    import org.granite.context.GraniteContext;
045    import org.granite.messaging.webapp.HttpGraniteContext;
046    
047    /**
048     * @author Franck WOLFF
049     */
050    public class TomcatSecurityService extends AbstractSecurityService {
051    
052        private final Field requestField;
053        private Engine engine = null;
054    
055        public TomcatSecurityService() {
056            super();
057            try {
058                // We need to access the org.apache.catalina.connector.Request field from
059                // a org.apache.catalina.connector.RequestFacade. Unfortunately there is
060                // no public getter for this field (and I don't want to create a Valve)...
061                requestField = RequestFacade.class.getDeclaredField("request");
062                requestField.setAccessible(true);
063            } catch (Exception e) {
064                throw new RuntimeException("Could not get 'request' field in Tomcat RequestFacade", e);
065            }
066        }
067    
068        protected Field getRequestField() {
069            return requestField;
070        }
071    
072        protected Engine getEngine() {
073            return engine;
074        }
075    
076            public void configure(Map<String, String> params) {
077            String serviceId = params.get("service");
078    
079            Server server = ServerFactory.getServer();
080            if (server == null)
081                throw new NullPointerException("Could not get Tomcat server");
082    
083            Service service = null;
084            if (serviceId != null)
085                service = server.findService(serviceId);
086            else {
087                Service[] services = server.findServices();
088                if (services != null && services.length > 0)
089                    service = services[0];
090            }
091            if (service == null)
092                throw new NullPointerException("Could not find Tomcat service for: " + (serviceId != null ? serviceId : "(default)"));
093    
094            engine = (Engine)service.getContainer();
095            if (engine == null)
096                throw new NullPointerException("Could not find Tomcat container for: " + (serviceId != null ? serviceId : "(default)"));
097        }
098    
099        public void login(Object credentials, String charset) throws SecurityServiceException {
100            String[] decoded = decodeBase64Credentials(credentials, charset);
101    
102            HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
103            HttpServletRequest httpRequest = context.getRequest();
104            Realm realm = getRealm(httpRequest);
105    
106            Principal principal = realm.authenticate(decoded[0], decoded[1]);
107            if (principal == null)
108                throw SecurityServiceException.newInvalidCredentialsException("Wrong username or password");
109    
110            Request request = getRequest(httpRequest);
111            request.setAuthType(AUTH_TYPE);
112            request.setUserPrincipal(principal);
113    
114            Session session = request.getSessionInternal(true);
115            session.setAuthType(AUTH_TYPE);
116            session.setPrincipal(principal);
117            session.setNote(Constants.SESS_USERNAME_NOTE, decoded[0]);
118            session.setNote(Constants.SESS_PASSWORD_NOTE, decoded[1]);
119            
120            endLogin(credentials, charset);
121        }
122    
123        public Object authorize(AbstractSecurityContext context) throws Exception {
124    
125            startAuthorization(context);
126            
127            HttpGraniteContext graniteContext = (HttpGraniteContext)GraniteContext.getCurrentInstance();
128            HttpServletRequest httpRequest = graniteContext.getRequest();
129            Request request = getRequest(httpRequest);
130            Session session = request.getSessionInternal(false);
131            
132            Principal principal = null;
133            if (session != null) {
134                request.setAuthType(session.getAuthType());
135                    principal = session.getPrincipal();
136                    if (principal == null && tryRelogin())
137                            principal = session.getPrincipal();
138            }
139    
140            request.setUserPrincipal(principal);
141    
142            if (context.getDestination().isSecured()) {
143                if (principal == null) {
144                    if (httpRequest.getRequestedSessionId() != null) {
145                        HttpSession httpSession = httpRequest.getSession(false);
146                        if (httpSession == null || httpRequest.getRequestedSessionId().equals(httpSession.getId()))
147                            throw SecurityServiceException.newSessionExpiredException("Session expired");
148                    }
149                    throw SecurityServiceException.newNotLoggedInException("User not logged in");
150                }
151    
152                boolean accessDenied = true;
153                for (String role : context.getDestination().getRoles()) {
154                    if (request.isUserInRole(role)) {
155                        accessDenied = false;
156                        break;
157                    }
158                }
159                if (accessDenied)
160                    throw SecurityServiceException.newAccessDeniedException("User not in required role");
161            }
162    
163            try {
164                return endAuthorization(context);
165            } catch (InvocationTargetException e) {
166                for (Throwable t = e; t != null; t = t.getCause()) {
167                    // Don't create a dependency to javax.ejb in SecurityService...
168                    if (t instanceof SecurityException ||
169                        "javax.ejb.EJBAccessException".equals(t.getClass().getName()))
170                        throw SecurityServiceException.newAccessDeniedException(t.getMessage());
171                }
172                throw e;
173            }
174        }
175    
176        public void logout() throws SecurityServiceException {
177            HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
178    
179            Session session = getSession(context.getRequest(), false);
180            if (session != null && session.getPrincipal() != null) {
181                session.setAuthType(null);
182                session.setPrincipal(null);
183                session.removeNote(Constants.SESS_USERNAME_NOTE);
184                session.removeNote(Constants.SESS_PASSWORD_NOTE);
185                
186                endLogout();
187                
188                session.expire();
189            }
190        }
191    
192        protected Principal getPrincipal(HttpServletRequest httpRequest) {
193            Request request = getRequest(httpRequest);
194            Session session = request.getSessionInternal(false);
195            return (session != null ? session.getPrincipal() : null);
196        }
197    
198        protected Session getSession(HttpServletRequest httpRequest, boolean create) {
199            Request request = getRequest(httpRequest);
200            return request.getSessionInternal(create);
201        }
202    
203        protected Request getRequest(HttpServletRequest request) {
204            while (request instanceof HttpServletRequestWrapper)
205                request = (HttpServletRequest)((HttpServletRequestWrapper)request).getRequest();
206            try {
207                return (Request)requestField.get(request);
208            } catch (Exception e) {
209                throw new RuntimeException("Could not get tomcat request", e);
210            }
211        }
212    
213        protected Context getContext(HttpServletRequest request) {
214            String serverName = request.getServerName();
215            String contextPath = request.getContextPath();
216    
217            Host host = (Host)engine.findChild(serverName);
218            if (host == null) {
219                // if it cannot find host, then use the default host.
220                host = (Host)engine.findChild(engine.getDefaultHost());
221                if (host == null)
222                    throw new NullPointerException("Could not find Tomcat host for: " + serverName + " or: " + engine.getDefaultHost());
223            }
224    
225            Context context = (Context)host.findChild(contextPath);
226            if (context == null)
227                throw new NullPointerException("Could not find Tomcat context for: " + contextPath);
228            return context;
229        }
230    
231        protected Realm getRealm(HttpServletRequest request) {
232            Context context = getContext(request);
233            Realm realm = context.getRealm();
234            if (realm == null)
235                throw new NullPointerException("Could not find Tomcat realm for: " + context.getPath());
236    
237            return realm;
238        }
239    }