View Javadoc

1   /*
2    * Copyright (c) 2007 Kathryn Huxtable
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * $Id: ShibShimFilter.java 1146 2007-04-14 10:28:25Z kathryn5 $
17   */
18  package org.kathrynhuxtable.middleware.shibshim.filter;
19  
20  import java.io.BufferedReader;
21  import java.io.FileNotFoundException;
22  import java.io.IOException;
23  import java.io.InputStreamReader;
24  import java.net.MalformedURLException;
25  import java.net.URL;
26  import java.util.HashMap;
27  import java.util.Map;
28  
29  import javax.servlet.Filter;
30  import javax.servlet.FilterChain;
31  import javax.servlet.FilterConfig;
32  import javax.servlet.ServletException;
33  import javax.servlet.ServletRequest;
34  import javax.servlet.ServletResponse;
35  
36  import org.apache.log4j.Logger;
37  import org.kathrynhuxtable.middleware.shibshim.util.properties.PropertiesBundle;
38  import org.kathrynhuxtable.middleware.shibshim.util.properties.PropertiesBundleException;
39  
40  /**
41   * The servlet filter for the Shibboleth Shim.
42   */
43  public final class ShibShimFilter implements Filter {
44      /**
45       * Logger for error/information/debug messages.
46       */
47      private static Logger    log             = Logger.getLogger(ShibShimFilter.class.getName());
48  
49      /**
50       * Default timeout value.
51       */
52      private static final int DEFAULT_TIMEOUT = 60;
53  
54      /**
55       * Number of milliseconds per second.
56       */
57      private static final int MILLI_PER_SEC   = 1000;
58  
59      /**
60       * The URL for the Shibboleth Shim server Assertion Consumer Service.
61       */
62      private String           acsUrl;
63  
64      /**
65       * The application name.
66       */
67      private String           application;
68  
69      /**
70       * The Map mapping incoming attribute names to the attributes stored in the
71       * UserAttribute object.
72       */
73      private Map              attributeMap    = new HashMap();
74  
75      /**
76       * The context-relative path for the local Shibboleth Shim filter Assertion
77       * Consumer Service.
78       */
79      private String           localAcsPath;
80  
81      /**
82       * The context-relative path for the login redirect URL.
83       */
84      private String           loginRedirectPath;
85  
86      /**
87       * The context-relative path for the logout redirect URL.
88       */
89      private String           logoutRedirectPath;
90  
91      /**
92       * The name of the Shibboleth attribute that will be mapped to the remote
93       * user such that request.getRemoteUser() will return it.
94       */
95      private String           remoteUserAttribute;
96  
97      /**
98       * The name to use to store the session attribute containing the
99       * UserAttributes object.
100      */
101     private String           sessionAttribute;
102 
103     /**
104      * Do we require a session, or can the user access the path without logging
105      * in? Set to <tt>false</tt> if login is not required. The default is
106      * <tt>true</tt>.
107      */
108     private boolean          sessionRequired;
109 
110     /**
111      * A URL designating the file holding the Shibboleth Shim server's signing
112      * certificate.
113      */
114     private String           shibShimServerCert;
115 
116     /**
117      * A URL designating the file holding the Shibboleth Shim server's encryption
118      * key.
119      */
120     private String           shibShimServerCryptKey;
121 
122     /**
123      * The number of seconds permitted before the session must be verified with
124      * the Shibboleth Shim server. Not currently used.
125      */
126     private long             timeout;
127 
128     /**
129      * @return the acsUrl
130      */
131     public String getAcsUrl() {
132         return acsUrl;
133     }
134 
135     /**
136      * @return the application
137      */
138     public String getApplication() {
139         return application;
140     }
141 
142     /**
143      * @param application
144      *            the application to set
145      */
146     public void setApplication(String application) {
147         this.application = application;
148     }
149 
150     /**
151      * @return the attributeMap
152      */
153     public Map getAttributeMap() {
154         return attributeMap;
155     }
156 
157     /**
158      * @return the localAcsPath
159      */
160     public String getLocalAcsPath() {
161         return localAcsPath;
162     }
163 
164     /**
165      * @return the loginRedirectPath
166      */
167     public String getLoginRedirectPath() {
168         return loginRedirectPath;
169     }
170 
171     /**
172      * @return the logoutRedirectPath
173      */
174     public String getLogoutRedirectPath() {
175         return logoutRedirectPath;
176     }
177 
178     /**
179      * @return the remoteUserAttribute
180      */
181     public String getRemoteUserAttribute() {
182         return remoteUserAttribute;
183     }
184 
185     /**
186      * @return the sessionAttribute
187      */
188     public String getSessionAttribute() {
189         return sessionAttribute;
190     }
191 
192     /**
193      * @return the sessionRequired
194      */
195     public boolean isSessionRequired() {
196         return sessionRequired;
197     }
198 
199     /**
200      * @return the shibShimServerCert
201      */
202     public String getShibShimServerCert() {
203         return shibShimServerCert;
204     }
205 
206     /**
207      * @return the shibShimServerCryptKey
208      */
209     public String getShibShimServerCryptKey() {
210         return shibShimServerCryptKey;
211     }
212 
213     /**
214      * @return the timeout
215      */
216     public long getTimeout() {
217         return timeout;
218     }
219 
220     /**
221      * Saves the config object. Initializes any private attributes from
222      * init-param values.
223      * 
224      * @param config
225      *            the configuration object for this filter.
226      * 
227      * @throws ServletException
228      *             if the properties aren't present or there is no acsURL
229      *             property.
230      */
231     public void init(FilterConfig config) throws ServletException {
232         String propertiesFile = config.getInitParameter("ShibShimFilterPropertiesFile");
233         if (propertiesFile == null || propertiesFile.length() <= 0) {
234             log.fatal("No Shibboleth Shim filter properties file specified in filter parameters.");
235             log.fatal("Use filter param ShibShimFilterPropertiesFile.");
236             throw new ServletException("Incorrect configuration data supplied.");
237         }
238 
239         // Load the properties bundle from the properties file.
240         // Get the values.
241         try {
242             PropertiesBundle.init(propertiesFile);
243 
244             // Get the required attributes
245             acsUrl = PropertiesBundle.getString("Assertion Consumer Service URL", "acsUrl");
246             attributeMap = PropertiesBundle.getMap("attribute map", "attributeMap.");
247             shibShimServerCert = readFile(PropertiesBundle.getString("server cert file", "certFile"));
248             shibShimServerCryptKey = readFile(PropertiesBundle.getString("server encryption key file", "cryptKeyFile"));
249             localAcsPath = PropertiesBundle.getString("local Assertion Consumer Service path", "localAcsPath");
250             sessionAttribute = PropertiesBundle.getString("name of session attribute", "sessionAttribute");
251 
252             // Get the optional attributes.
253             application = PropertiesBundle.getString("application", "application", null);
254             loginRedirectPath = PropertiesBundle.getString("login redirect path", "loginRedirectPath", null);
255             logoutRedirectPath = PropertiesBundle.getString("logout redirect path", "logoutRedirectPath", null);
256             remoteUserAttribute = PropertiesBundle.getString("remote user attribute", "remoteUserAttribute", null);
257             sessionRequired = "true".equals(PropertiesBundle.getString("session required", "sessionRequired", "true"));
258             timeout = PropertiesBundle.getInteger("session timeout", "timeout", DEFAULT_TIMEOUT) * MILLI_PER_SEC;
259         } catch (PropertiesBundleException e) {
260             throw new ServletException(e);
261         }
262 
263         log.debug("ShibShimFilter initialized");
264     }
265 
266     /**
267      * Overrides the destroy method from filter.
268      */
269     public void destroy() {
270         // Do nothing.
271     }
272 
273     /**
274      * <p>
275      * Filters requests to make sure user is logged in. If not, redirects to
276      * standard login page. If logged in, fills in some principal attributes
277      * that can be used by the filtered servlet.
278      * </p>
279      * 
280      * <p>
281      * Note: The Filter will throw a Servlet Exception only in one catastrophic
282      * case, where it is not running in a Web Server?!? It doesn't generate
283      * other exceptions, but will pass on exceptions generated by other Filters
284      * or the Servlet farther down the chain.
285      * </p>
286      * 
287      * @param req
288      *            the servlet request object.
289      * @param res
290      *            the servlet response object.
291      * 
292      * @param chain
293      *            the chain of servlets to run if we are logged in.
294      * 
295      * @throws IOException
296      * @throws ServletException
297      */
298     public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
299         try {
300             FilterHandler handler = new FilterHandler(req, res, chain, this);
301             handler.filterRequest();
302         } catch (ImmediateReturnException e) {
303             // Do nothing. We have already redirected to another page or printed
304             // an error page.
305         }
306     }
307 
308     /**
309      * Read a file and return its text as a string.
310      * 
311      * @param filename
312      *            the name of the file.
313      * @return the text of the file.
314      * @throws ServletException
315      *             if the file cannot be read.
316      */
317     private String readFile(String filename) throws ServletException {
318         String text = null;
319         try {
320             URL resource = null;
321             try {
322                 resource = new URL(filename);
323             } catch (MalformedURLException e) {
324                 resource = PropertiesBundle.class.getResource(filename);
325             }
326             if (resource == null) {
327                 log.fatal("File could not be found at the specified location: " + filename);
328                 throw new ServletException("File could not be found at the specified location: " + filename);
329             }
330             BufferedReader reader = new BufferedReader(new InputStreamReader(resource.openStream()));
331             StringBuffer textBuffer = new StringBuffer();
332             String buffer = null;
333             while ((buffer = reader.readLine()) != null) {
334                 textBuffer.append(buffer).append('\n');
335             }
336             reader.close();
337             text = new String(textBuffer);
338         } catch (FileNotFoundException e) {
339             throw new ServletException("File not found: " + filename, e);
340         } catch (IOException e) {
341             throw new ServletException("IOException reading file: " + filename, e);
342         }
343         return text;
344     }
345 }