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