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.security.Principal;
026    import java.util.Map;
027    
028    import javax.servlet.http.HttpServletRequest;
029    import javax.servlet.http.HttpServletRequestWrapper;
030    import javax.servlet.http.HttpSession;
031    
032    import org.apache.catalina.Context;
033    import org.apache.catalina.Engine;
034    import org.apache.catalina.Host;
035    import org.apache.catalina.Realm;
036    import org.apache.catalina.Server;
037    import org.apache.catalina.ServerFactory;
038    import org.apache.catalina.Service;
039    import org.apache.catalina.Session;
040    import org.apache.catalina.authenticator.Constants;
041    import org.apache.catalina.connector.Request;
042    import org.apache.catalina.connector.RequestFacade;
043    import org.granite.context.GraniteContext;
044    import org.granite.messaging.webapp.HttpGraniteContext;
045    
046    /**
047     * @author Franck WOLFF
048     */
049    public class TomcatSecurityService extends AbstractSecurityService {
050    
051        private final Field requestField;
052        private Engine engine = null;
053    
054        public TomcatSecurityService() {
055            super();
056            try {
057                // We need to access the org.apache.catalina.connector.Request field from
058                // a org.apache.catalina.connector.RequestFacade. Unfortunately there is
059                // no public getter for this field (and I don't want to create a Valve)...
060                requestField = RequestFacade.class.getDeclaredField("request");
061                requestField.setAccessible(true);
062            } catch (Exception e) {
063                throw new RuntimeException("Could not get 'request' field in Tomcat RequestFacade", e);
064            }
065        }
066    
067        protected Field getRequestField() {
068            return requestField;
069        }
070    
071        protected Engine getEngine() {
072            return engine;
073        }
074    
075            public void configure(Map<String, String> params) {
076            String serviceId = params.get("service");
077    
078            Server server = ServerFactory.getServer();
079            if (server == null)
080                throw new NullPointerException("Could not get Tomcat server");
081    
082            Service service = null;
083            if (serviceId != null)
084                service = server.findService(serviceId);
085            else {
086                Service[] services = server.findServices();
087                if (services != null && services.length > 0)
088                    service = services[0];
089            }
090            if (service == null)
091                throw new NullPointerException("Could not find Tomcat service for: " + (serviceId != null ? serviceId : "(default)"));
092    
093            engine = (Engine)service.getContainer();
094            if (engine == null)
095                throw new NullPointerException("Could not find Tomcat container for: " + (serviceId != null ? serviceId : "(default)"));
096        }
097    
098        public void login(Object credentials) throws SecurityServiceException {
099            String[] decoded = decodeBase64Credentials(credentials);
100    
101            HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
102            HttpServletRequest httpRequest = context.getRequest();
103            Realm realm = getRealm(httpRequest);
104    
105            Principal principal = realm.authenticate(decoded[0], decoded[1]);
106            if (principal == null)
107                throw SecurityServiceException.newInvalidCredentialsException("Wrong username or password");
108    
109            Request request = getRequest(httpRequest);
110            request.setAuthType(AUTH_TYPE);
111            request.setUserPrincipal(principal);
112    
113            Session session = request.getSessionInternal(true);
114            session.setAuthType(AUTH_TYPE);
115            session.setPrincipal(principal);
116            session.setNote(Constants.SESS_USERNAME_NOTE, decoded[0]);
117            session.setNote(Constants.SESS_PASSWORD_NOTE, decoded[1]);
118            
119            endLogin(credentials);
120        }
121    
122        public Object authorize(AbstractSecurityContext context) throws Exception {
123    
124            startAuthorization(context);
125            
126            HttpGraniteContext graniteContext = (HttpGraniteContext)GraniteContext.getCurrentInstance();
127            HttpServletRequest httpRequest = graniteContext.getRequest();
128            Request request = getRequest(httpRequest);
129            Session session = request.getSessionInternal(false);
130            
131            Principal principal = null;
132            if (session != null) {
133                request.setAuthType(session.getAuthType());
134                    principal = session.getPrincipal();
135                    if (principal == null && tryRelogin())
136                            principal = session.getPrincipal();
137            }
138    
139            request.setUserPrincipal(principal);
140    
141            if (context.getDestination().isSecured()) {
142                if (principal == null) {
143                    if (httpRequest.getRequestedSessionId() != null) {
144                        HttpSession httpSession = httpRequest.getSession(false);
145                        if (httpSession == null || httpRequest.getRequestedSessionId().equals(httpSession.getId()))
146                            throw SecurityServiceException.newSessionExpiredException("Session expired");
147                    }
148                    throw SecurityServiceException.newNotLoggedInException("User not logged in");
149                }
150    
151                Realm realm = getRealm(httpRequest);
152                boolean accessDenied = true;
153                for (String role : context.getDestination().getRoles()) {
154                    if (realm.hasRole(principal, 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    }