dfa8ca9685234ac3b0fc8c4d0b511eabdacd9e50
[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                         //Decided 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.
109          */
110
111         private void initConfig() throws ServletException {
112
113                 InputStream is =
114                         getServletContext().getResourceAsStream(hsConfigFileLocation);
115                 HsConfigDigester digester = new HsConfigDigester();
116                 try {
117                         digester.parse(is);
118                 } catch (SAXException se) {
119                         log.fatal("Error parsing HS configuration file.", se);
120                         throw new ServletException(
121                                 "Error parsing HS configuration file.",
122                                 se);
123                 } catch (IOException ioe) {
124                         log.fatal("Error reading HS configuration file.", ioe);
125                         throw new ServletException(
126                                 "Error reading HS configuration file.",
127                                 ioe);
128                 }
129
130         }
131
132         /**
133          * Starts up Log4J.
134          */
135
136         private void initLogger() {
137
138                 PropertyConfigurator.configure(
139                         getServletContext().getRealPath("/") + log4jConfigFileLocation);
140
141         }
142
143         /**
144          * Places configuration parameters in the <code>ServletContext</code> so that they may 
145          * be retreived by view components.
146          */
147
148         private void initViewConfig() {
149                 getServletContext().setAttribute(
150                         "hs_supportContact",
151                         HandleServiceConfig.getSupportContact());
152                 getServletContext().setAttribute(
153                         "hs_logoLocation",
154                         HandleServiceConfig.getLogoLocation());
155                 getServletContext().setAttribute(
156                         "hs_helpText",
157                         HandleServiceConfig.getHelpText());
158                 getServletContext().setAttribute(
159                         "hs_detailedHelpURL",
160                         HandleServiceConfig.getDetailedHelpURL());
161                 getServletContext().setAttribute(
162                         "hs_location",
163                         HandleServiceConfig.getLocation());
164         }
165
166         /**
167          * Initializes SAML AuthN Factory
168          */
169
170         private void initAuthNFactory() throws ServletException {
171                 try {
172                         AABindingInfo[] binfo = new AABindingInfo[1];
173                         binfo[0] =
174                                 new AABindingInfo(
175                                         AABindingInfo.SAML_SOAP_HTTPS,
176                                         HandleServiceConfig.getAaURL());
177                         String[] policies = { Policies.POLICY_URI_CLUBSHIB };
178                         assertionFactory =
179                                 SAMLAuthenticationAssertionFactory.getInstance(
180                                         policies,
181                                         HandleServiceConfig.getIssuer(),
182                                         HandleServiceConfig.getDomain(),
183                                         binfo,
184                                         null,
185                                         null);
186
187                 } catch (SAMLException se) {
188                         log.fatal("Error initializing SAML library: ", se);
189                         throw new ServletException("Error initializing SAML library: ", se);
190                 }
191         }
192
193         /**
194          * @see HttpServlet#doGet(HttpServletRequest, HttpServletResponse)
195          */
196
197         public void doGet(HttpServletRequest req, HttpServletResponse resp)
198                 throws ServletException, IOException {
199
200                 try {
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();
206                         byte[] assertion =
207                                 generateAssertion(
208                                         req.getParameter("shire"),
209                                         req.getRemoteAddr(),
210                                         req.getRemoteUser(),
211                                         req.getAuthType());
212                         log.info(
213                                 "Assertion Generated: "
214                                         + "elapsed time "
215                                         + (System.currentTimeMillis() - startTime)
216                                         + " milliseconds.");
217                         log.debug("Assertion: " + new String(Base64.decode(assertion)));
218                         handleForm(req, resp, assertion);
219                 } catch (HandleServiceException e) {
220                         handleError(req, resp, e);
221                 }
222
223         }
224
225         /**
226          * Deals with HS runtime exceptions.  Logs errors locally and then 
227          * formats them for output to user.
228          * 
229          * @param e The Exception to be handled
230          */
231
232         private void handleError(
233                 HttpServletRequest req,
234                 HttpServletResponse res,
235                 Exception e) {
236
237                 log.warn("Handle Service Failure: " + e);
238
239                 req.setAttribute("errorText", e.toString());
240                 RequestDispatcher rd = req.getRequestDispatcher("/hserror.jsp");
241
242                 try {
243                         rd.forward(req, res);
244                 } catch (IOException ioe) {
245                         log.info(
246                                 "IO operation interrupted when displaying Handle Service error page: "
247                                         + ioe);
248                 } catch (ServletException se) {
249                         log.error(
250                                 "Problem trying to display Handle Service error page: " + se);
251                 }
252         }
253
254         /**
255          * Method for auto-POSTing a Base64 encoded SAML assertion.
256          * 
257          * @param assertion Base64 encoded SAML authN assertion
258          */
259
260         private void handleForm(
261                 HttpServletRequest req,
262                 HttpServletResponse res,
263                 byte[] assertion)
264                 throws HandleServiceException {
265
266                 try {
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);
278                 }
279         }
280
281         /**
282          * Generates a new <code>AttributeQueryHandle</code> and includes it in a 
283          * <code>SAMLAuthenticationAssertion</code>.
284          */
285
286         private byte[] generateAssertion(
287                 String shireURL,
288                 String clientAddress,
289                 String remoteUser,
290                 String authType)
291                 throws HandleServiceException {
292                 try {
293
294                         AttributeQueryHandle aqh =
295                                 new AttributeQueryHandle(
296                                         remoteUser,
297                                         key,
298                                         Long.parseLong(HandleServiceConfig.getValidityPeriod()),
299                                         HandleServiceConfig.getLocation());
300
301                         log.info("Acquired Handle: " + aqh.getHandleID());
302
303                         return assertionFactory
304                                 .getAssertion(
305                                         aqh.serialize(),
306                                         shireURL,
307                                         clientAddress,
308                                         authType,
309                                         new Date(),
310                                         null)
311                                 .toBase64();
312
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);
322                 }
323         }
324
325         /**
326          * Ensures that <code>HttpServletRequest</code> contains all of the parameters necessary
327          * for generation of an <code>AttributeQueryHandle</code>.
328          */
329
330         private void validateRequestParameters(HttpServletRequest req)
331                 throws HandleServiceException {
332
333                 if ((req.getParameter("shire") == null)
334                         || (req.getParameter("shire").equals(""))) {
335                         throw new HandleServiceException("Invalid data from SHIRE: No acceptance URL received.");
336                 }
337                 if ((req.getParameter("target") == null)
338                         || (req.getParameter("target").equals(""))) {
339                         throw new HandleServiceException("Invalid data from SHIRE: No target URL received.");
340                 }
341                 if ((req.getRemoteUser() == null)
342                         || (req.getRemoteUser().equals(""))) {
343                         throw new HandleServiceException("No authentication received from webserver.");
344                 }
345                 if ((req.getAuthType() == null) || (req.getAuthType().equals(""))) {
346                         throw new HandleServiceException("Unable to ascertain authentication type.");
347                 }
348                 if ((req.getRemoteAddr() == null)
349                         || (req.getRemoteAddr().equals(""))) {
350                         throw new HandleServiceException("Unable to ascertain client address.");
351                 }
352         }
353
354 }