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