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