4ae18fa8e59bc86c0f64c7475e9f0317fee03b46
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / hs / HandleService.java
1 package edu.internet2.middleware.shibboleth.hs;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.security.Security;
6 import java.util.Date;
7
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;
20
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;
28
29 /**
30  * 
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.
35  * 
36  * @author Walter Hoehn wassa@columbia.edu
37  * @author Barbara Jenson blk@cmu.edu
38  *
39  */
40
41 public class HandleService extends HttpServlet {
42
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;
48
49         /**
50          * @see GenericServlet#init()
51          */
52
53         public void init() throws ServletException {
54
55                 super.init();
56                 loadInitParams();
57                 initLogger();
58                 initConfig();
59                 initViewConfig();
60                 initSecretKey();
61                 initAuthNFactory();
62         }
63
64         /**
65          * Initializes symmetric key for use in AQH creation
66          */
67
68         private void initSecretKey() throws ServletException {
69
70                 try {
71
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 =
78                                 new DESedeKeySpec(
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.");
84                 }
85
86         }
87
88         /**
89          * Retrieves location of HS configuration files from the servlet configuration.
90          */
91
92         private void loadInitParams() {
93
94                 hsConfigFileLocation =
95                         getServletConfig().getInitParameter("HSConfigFileLocation");
96                 if (hsConfigFileLocation == null) {
97                         hsConfigFileLocation = "/WEB-INF/conf/shibboleth.xml";
98                 }
99                 log4jConfigFileLocation =
100                         getServletConfig().getInitParameter("log4jConfigFileLocation");
101                 if (log4jConfigFileLocation == null) {
102                         log4jConfigFileLocation = "/WEB-INF/conf/log4j.properties";
103                 }
104
105         }
106
107         /**
108          * Loads HS configuration.  Populates a <code>HandleServiceConfig</code> object based
109          * on administrator supplied configuration.
110          */
111
112         private void initConfig() throws ServletException {
113
114                 InputStream is =
115                         getServletContext().getResourceAsStream(hsConfigFileLocation);
116                 HsConfigDigester digester = new HsConfigDigester();
117                 try {
118                         digester.parse(is);
119                 } catch (SAXException se) {
120                         log.fatal("Error parsing HS configuration file.", se);
121                         throw new ServletException(
122                                 "Error parsing HS configuration file.",
123                                 se);
124                 } catch (IOException ioe) {
125                         log.fatal("Error reading HS configuration file.", ioe);
126                         throw new ServletException(
127                                 "Error reading HS configuration file.",
128                                 ioe);
129                 }
130
131         }
132
133         /**
134          * Starts up Log4J.
135          */
136
137         private void initLogger() {
138
139                 PropertyConfigurator.configure(
140                         getServletContext().getRealPath("/") + log4jConfigFileLocation);
141
142         }
143
144         /**
145          * Places configuration parameters in the <code>ServletContext</code> so that they may 
146          * be retreived by view components.
147          */
148
149         private void initViewConfig() {
150                 getServletContext().setAttribute(
151                         "hs_supportContact",
152                         HandleServiceConfig.getSupportContact());
153                 getServletContext().setAttribute(
154                         "hs_logoLocation",
155                         HandleServiceConfig.getLogoLocation());
156                 getServletContext().setAttribute(
157                         "hs_helpText",
158                         HandleServiceConfig.getHelpText());
159                 getServletContext().setAttribute(
160                         "hs_detailedHelpURL",
161                         HandleServiceConfig.getDetailedHelpURL());
162                 getServletContext().setAttribute(
163                         "hs_location",
164                         HandleServiceConfig.getHsURL());
165         }
166
167         /**
168          * Initializes SAML AuthN Factory
169          */
170
171         private void initAuthNFactory() throws ServletException {
172                 try {
173                         AABindingInfo[] binfo = new AABindingInfo[1];
174                         binfo[0] =
175                                 new AABindingInfo(
176                                         AABindingInfo.SAML_SOAP_HTTPS,
177                                         HandleServiceConfig.getAaURL());
178                         String[] policies = { Policies.POLICY_URI_CLUBSHIB };
179                         assertionFactory =
180                                 SAMLAuthenticationAssertionFactory.getInstance(
181                                         policies,
182                                         HandleServiceConfig.getIssuer(),
183                                         HandleServiceConfig.getDomain(),
184                                         binfo,
185                                         null,
186                                         null);
187
188                 } catch (SAMLException se) {
189                         log.fatal("Error initializing SAML library: ", se);
190                         throw new ServletException("Error initializing SAML library: ", se);
191                 }
192         }
193
194         /**
195          * @see HttpServlet#doGet(HttpServletRequest, HttpServletResponse)
196          */
197
198         public void doGet(HttpServletRequest req, HttpServletResponse resp)
199                 throws ServletException, IOException {
200
201                 try {
202                         validateRequestParameters(req);
203                         req.setAttribute("shire", req.getParameter("shire"));
204                         req.setAttribute("target", req.getParameter("target"));
205                         log.info("Generating assertion...");
206                         long startTime = System.currentTimeMillis();
207                         byte[] assertion =
208                                 generateAssertion(
209                                         req.getParameter("shire"),
210                                         req.getRemoteAddr(),
211                                         req.getRemoteUser(),
212                                         req.getAuthType());
213                         log.info(
214                                 "Assertion Generated: "
215                                         + "elapsed time "
216                                         + (System.currentTimeMillis() - startTime)
217                                         + " milliseconds.");
218                         log.debug("Assertion: " + new String(Base64.decode(assertion)));
219                         handleForm(req, resp, assertion);
220                 } catch (HandleServiceException e) {
221                         handleError(req, resp, e);
222                 }
223
224         }
225
226         /**
227          * Deals with HS runtime exceptions.  Logs errors locally and then 
228          * formats them for output to user.
229          * 
230          * @param e The Exception to be handled
231          */
232
233         private void handleError(
234                 HttpServletRequest req,
235                 HttpServletResponse res,
236                 Exception e) throws ServletException {
237
238                 log.warn("Handle Service Failure: " + e);
239
240                 req.setAttribute("errorText", e.toString());
241                 RequestDispatcher rd = req.getRequestDispatcher("/hserror.jsp");
242
243                 try {
244                         rd.forward(req, res);
245                 } catch (IOException ioe) {
246                         log.info(
247                                 "IO operation interrupted when displaying Handle Service error page: "
248                                         + ioe);
249                 } catch (ServletException se) {
250                         log.error(
251                                 "Problem trying to display Handle Service error page: " + se);
252                                 throw se;
253                 }
254         }
255
256         /**
257          * Method for auto-POSTing a Base64 encoded SAML assertion.
258          * 
259          * @param assertion Base64 encoded SAML authN assertion
260          */
261
262         private void handleForm(
263                 HttpServletRequest req,
264                 HttpServletResponse res,
265                 byte[] assertion)
266                 throws HandleServiceException {
267
268                 try {
269                         //Hardcoded to ASCII to ensure Base64 encoding compatibility
270                         req.setAttribute("assertion", new String(assertion, "ASCII"));
271                         RequestDispatcher rd = req.getRequestDispatcher("/hs.jsp");
272                         log.info("POSTing assertion to SHIRE.");
273                         rd.forward(req, res);
274                 } catch (IOException ioe) {
275                         throw new HandleServiceException(
276                                 "IO interruption while displaying Handle Service UI." + ioe);
277                 } catch (ServletException se) {
278                         throw new HandleServiceException(
279                                 "Problem displaying Handle Service UI." + se);
280                 }
281         }
282
283         /**
284          * Generates a new <code>AttributeQueryHandle</code> and includes it in a 
285          * <code>SAMLAuthenticationAssertion</code>.
286          */
287
288         private byte[] generateAssertion(
289                 String shireURL,
290                 String clientAddress,
291                 String remoteUser,
292                 String authType)
293                 throws HandleServiceException {
294                 try {
295
296                         AttributeQueryHandle aqh =
297                                 new AttributeQueryHandle(
298                                         remoteUser,
299                                         key,
300                                         Long.parseLong(HandleServiceConfig.getValidityPeriod()),
301                                         HandleServiceConfig.getHsURL());
302
303                         log.info("Acquired Handle: " + aqh.getHandleID());
304
305                         return assertionFactory
306                                 .getAssertion(
307                                         new String(aqh.serialize(), "ASCII"),
308                                         shireURL,
309                                         clientAddress,
310                                         authType,
311                                         new Date(),
312                                         null)
313                                 .toBase64();
314
315                 } catch (SAMLException se) {
316                         throw new HandleServiceException(
317                                 "Error creating SAML assertion: " + se);
318                 } catch (IOException ioe) {
319                         throw new HandleServiceException(
320                                 "Error creating SAML assertion: " + ioe);
321                 } catch (HandleException he) {
322                         throw new HandleServiceException(
323                                 "Error creating User Handle: " + he);
324                 }
325         }
326
327         /**
328          * Ensures that <code>HttpServletRequest</code> contains all of the parameters necessary
329          * for generation of an <code>AttributeQueryHandle</code>.
330          */
331
332         private void validateRequestParameters(HttpServletRequest req)
333                 throws HandleServiceException {
334
335                 if ((req.getParameter("shire") == null)
336                         || (req.getParameter("shire").equals(""))) {
337                         throw new HandleServiceException("Invalid data from SHIRE: No acceptance URL received.");
338                 }
339                 if ((req.getParameter("target") == null)
340                         || (req.getParameter("target").equals(""))) {
341                         throw new HandleServiceException("Invalid data from SHIRE: No target URL received.");
342                 }
343                 if ((req.getRemoteUser() == null)
344                         || (req.getRemoteUser().equals(""))) {
345                         throw new HandleServiceException("No authentication received from webserver.");
346                 }
347                 if ((req.getAuthType() == null) || (req.getAuthType().equals(""))) {
348                         throw new HandleServiceException("Unable to ascertain authentication type.");
349                 }
350                 if ((req.getRemoteAddr() == null)
351                         || (req.getRemoteAddr().equals(""))) {
352                         throw new HandleServiceException("Unable to ascertain client address.");
353                 }
354         }
355
356 }