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