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