1 package edu.internet2.middleware.shibboleth.hs;
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.security.Security;
8 import javax.crypto.SecretKey;
9 import javax.crypto.SecretKeyFactory;
10 import javax.crypto.spec.DESedeKeySpec;
11 import javax.servlet.RequestDispatcher;
12 import javax.servlet.ServletException;
13 import javax.servlet.http.HttpServlet;
14 import javax.servlet.http.HttpServletRequest;
15 import javax.servlet.http.HttpServletResponse;
16 import org.apache.log4j.Logger;
17 import org.apache.log4j.PropertyConfigurator;
18 import org.bouncycastle.jce.provider.BouncyCastleProvider;
19 import org.xml.sax.SAXException;
21 import edu.internet2.middleware.shibboleth.AABindingInfo;
22 import edu.internet2.middleware.shibboleth.Policies;
23 import edu.internet2.middleware.shibboleth.SAMLAuthenticationAssertionFactory;
24 import edu.internet2.middleware.shibboleth.SAMLException;
25 import edu.internet2.middleware.shibboleth.common.AttributeQueryHandle;
26 import edu.internet2.middleware.shibboleth.common.Base64;
27 import edu.internet2.middleware.shibboleth.common.HandleException;
31 * A servlet implementation of the Shibboleth Handle Service. Accepts
32 * Shibboleth Attribute Query Handle Requests via HTTP GET and generates
33 * SAML authN assertions containing an opaque user handle. These assertions are
34 * embedded in an HTML that auto-POSTs to the referring SHIRE.
36 * @author Walter Hoehn wassa@columbia.edu
37 * @author Barbara Jenson blk@cmu.edu
41 public class HandleService extends HttpServlet {
43 private static Logger log = Logger.getLogger(HandleService.class.getName());
44 private SAMLAuthenticationAssertionFactory assertionFactory;
45 private String hsConfigFileLocation;
46 private String log4jConfigFileLocation;
47 private SecretKey key;
50 * @see GenericServlet#init()
53 public void init() throws ServletException {
65 * Initializes symmetric key for use in AQH creation
68 private void initSecretKey() throws ServletException {
72 //Currently hardcoded to use Bouncy Castle
73 //Decide to change this or not based on overall shibboleth policy
74 Security.addProvider(new BouncyCastleProvider());
75 SecretKeyFactory keyFactory =
76 SecretKeyFactory.getInstance("DESede");
77 DESedeKeySpec keySpec =
79 Base64.decode(HandleServiceConfig.getSecretKey()));
80 key = keyFactory.generateSecret(keySpec);
81 } catch (Exception t) {
82 log.fatal("Error reading Secret Key from configuration.", t);
83 throw new ServletException("Error reading Key from configuration.");
89 * Retrieves location of HS configuration files from the servlet configuration.
92 private void loadInitParams() {
94 hsConfigFileLocation =
95 getServletConfig().getInitParameter("HSConfigFileLocation");
96 if (hsConfigFileLocation == null) {
97 hsConfigFileLocation = "/WEB-INF/conf/shibboleth.xml";
99 log4jConfigFileLocation =
100 getServletConfig().getInitParameter("log4jConfigFileLocation");
101 if (log4jConfigFileLocation == null) {
102 log4jConfigFileLocation = "/WEB-INF/conf/log4j.properties";
108 * Loads HS configuration.
111 private void initConfig() throws ServletException {
114 getServletContext().getResourceAsStream(hsConfigFileLocation);
115 HsConfigDigester digester = new HsConfigDigester();
118 } catch (SAXException se) {
119 log.fatal("Error parsing HS configuration file.", se);
120 throw new ServletException(
121 "Error parsing HS configuration file.",
123 } catch (IOException ioe) {
124 log.fatal("Error reading HS configuration file.", ioe);
125 throw new ServletException(
126 "Error reading HS configuration file.",
136 private void initLogger() {
138 PropertyConfigurator.configure(
139 getServletContext().getRealPath("/") + log4jConfigFileLocation);
144 * Places configuration parameters in the <code>ServletContext</code> so that they may
145 * be retreived by view components.
148 private void initViewConfig() {
149 getServletContext().setAttribute(
151 HandleServiceConfig.getSupportContact());
152 getServletContext().setAttribute(
154 HandleServiceConfig.getLogoLocation());
155 getServletContext().setAttribute(
157 HandleServiceConfig.getHelpText());
158 getServletContext().setAttribute(
159 "hs_detailedHelpURL",
160 HandleServiceConfig.getDetailedHelpURL());
161 getServletContext().setAttribute(
163 HandleServiceConfig.getLocation());
167 * Initializes SAML AuthN Factory
170 private void initAuthNFactory() throws ServletException {
172 AABindingInfo[] binfo = new AABindingInfo[1];
175 AABindingInfo.SAML_SOAP_HTTPS,
176 HandleServiceConfig.getAaURL());
177 String[] policies = { Policies.POLICY_URI_CLUBSHIB };
179 SAMLAuthenticationAssertionFactory.getInstance(
181 HandleServiceConfig.getIssuer(),
182 HandleServiceConfig.getDomain(),
187 } catch (SAMLException se) {
188 log.fatal("Error initializing SAML library: ", se);
189 throw new ServletException("Error initializing SAML library: ", se);
194 * @see HttpServlet#doGet(HttpServletRequest, HttpServletResponse)
197 public void doGet(HttpServletRequest req, HttpServletResponse resp)
198 throws ServletException, IOException {
201 validateRequestParameters(req);
202 req.setAttribute("shire", req.getParameter("shire"));
203 req.setAttribute("target", req.getParameter("target"));
204 log.info("Generating assertion...");
205 long startTime = System.currentTimeMillis();
208 req.getParameter("shire"),
213 "Assertion Generated: "
215 + (System.currentTimeMillis() - startTime)
217 log.debug("Assertion: " + new String(Base64.decode(assertion)));
218 handleForm(req, resp, assertion);
219 } catch (HandleServiceException e) {
220 handleError(req, resp, e);
226 * Deals with HS runtime exceptions. Logs errors locally and then
227 * formats them for output to user.
229 * @param e The Exception to be handled
232 private void handleError(
233 HttpServletRequest req,
234 HttpServletResponse res,
237 log.warn("Handle Service Failure: " + e);
239 req.setAttribute("errorText", e.toString());
240 RequestDispatcher rd = req.getRequestDispatcher("/hserror.jsp");
243 rd.forward(req, res);
244 } catch (IOException ioe) {
246 "IO operation interrupted when displaying Handle Service error page: "
248 } catch (ServletException se) {
250 "Problem trying to display Handle Service error page: " + se);
255 * Method for auto-POSTing a Base64 encoded SAML assertion.
257 * @param assertion Base64 encoded SAML authN assertion
260 private void handleForm(
261 HttpServletRequest req,
262 HttpServletResponse res,
264 throws HandleServiceException {
267 //Hardcoded to ASCII to ensure Base64 encoding compatibility
268 req.setAttribute("assertion", new String(assertion, "ASCII"));
269 RequestDispatcher rd = req.getRequestDispatcher("/hs.jsp");
270 log.info("POSTing assertion to SHIRE.");
271 rd.forward(req, res);
272 } catch (IOException ioe) {
273 throw new HandleServiceException(
274 "IO interruption while displaying Handle Service UI." + ioe);
275 } catch (ServletException se) {
276 throw new HandleServiceException(
277 "Problem displaying Handle Service UI." + se);
282 * Generates a new <code>AttributeQueryHandle</code> and includes it in a
283 * <code>SAMLAuthenticationAssertion</code>.
286 private byte[] generateAssertion(
288 String clientAddress,
291 throws HandleServiceException {
294 AttributeQueryHandle aqh =
295 new AttributeQueryHandle(
298 Long.parseLong(HandleServiceConfig.getValidityPeriod()),
299 HandleServiceConfig.getLocation());
301 log.info("Acquired Handle: " + aqh.getHandleID());
303 return assertionFactory
313 } catch (SAMLException se) {
314 throw new HandleServiceException(
315 "Error creating SAML assertion: " + se);
316 } catch (IOException ioe) {
317 throw new HandleServiceException(
318 "Error creating SAML assertion: " + ioe);
319 } catch (HandleException he) {
320 throw new HandleServiceException(
321 "Error creating User Handle: " + he);
326 * Ensures that <code>HttpServletRequest</code> contains all of the parameters necessary
327 * for generation of an <code>AttributeQueryHandle</code>.
330 private void validateRequestParameters(HttpServletRequest req)
331 throws HandleServiceException {
333 if ((req.getParameter("shire") == null)
334 || (req.getParameter("shire").equals(""))) {
335 throw new HandleServiceException("Invalid data from SHIRE: No acceptance URL received.");
337 if ((req.getParameter("target") == null)
338 || (req.getParameter("target").equals(""))) {
339 throw new HandleServiceException("Invalid data from SHIRE: No target URL received.");
341 if ((req.getRemoteUser() == null)
342 || (req.getRemoteUser().equals(""))) {
343 throw new HandleServiceException("No authentication received from webserver.");
345 if ((req.getAuthType() == null) || (req.getAuthType().equals(""))) {
346 throw new HandleServiceException("Unable to ascertain authentication type.");
348 if ((req.getRemoteAddr() == null)
349 || (req.getRemoteAddr().equals(""))) {
350 throw new HandleServiceException("Unable to ascertain client address.");