1 package edu.internet2.middleware.shibboleth.wayf.plugins.provider;
2
3 import java.io.UnsupportedEncodingException;
4 import java.net.URLDecoder;
5 import java.net.URLEncoder;
6 import java.util.ArrayList;
7 import java.util.Collection;
8 import java.util.Iterator;
9 import java.util.List;
10 import java.util.Map;
11
12 import javax.servlet.http.Cookie;
13 import javax.servlet.http.HttpServletRequest;
14 import javax.servlet.http.HttpServletResponse;
15
16 import org.apache.log4j.Logger;
17 import org.opensaml.saml2.metadata.provider.MetadataProvider;
18 import org.opensaml.xml.util.Base64;
19 import org.w3c.dom.Element;
20
21 import edu.internet2.middleware.shibboleth.wayf.DiscoveryServiceHandler;
22 import edu.internet2.middleware.shibboleth.wayf.IdPSite;
23 import edu.internet2.middleware.shibboleth.wayf.WayfException;
24 import edu.internet2.middleware.shibboleth.wayf.plugins.Plugin;
25 import edu.internet2.middleware.shibboleth.wayf.plugins.PluginContext;
26 import edu.internet2.middleware.shibboleth.wayf.plugins.PluginMetadataParameter;
27 import edu.internet2.middleware.shibboleth.wayf.plugins.WayfRequestHandled;
28
29
30
31
32
33
34
35
36 public class SamlCookiePlugin implements Plugin {
37
38
39
40
41 private static final String PARAMETER_NAME = "cache";
42
43
44
45
46 private static final String PARAMETER_PERM = "perm";
47
48
49
50
51 private static final String PARAMETER_SESSION = "session";
52
53
54
55
56 private static Logger log = Logger.getLogger(SamlCookiePlugin.class.getName());
57
58
59
60
61 private static final String COOKIE_NAME = "_saml_idp";
62
63
64
65
66 private static final int DEFAULT_CACHE_EXPIRATION = 6048000;
67
68
69
70
71 private boolean alwaysFollow;
72
73
74
75
76 private boolean deleteCookie;
77
78
79
80
81 private String cacheDomain;
82
83
84
85
86 private int cacheExpiration;
87
88
89
90
91
92
93
94 public SamlCookiePlugin(Element element) {
95
96
97
98
99
100
101
102
103 log.info("New plugin");
104 String s;
105
106 s = element.getAttribute("alwaysFollow");
107 if (s != null && !s.equals("") ) {
108 alwaysFollow = Boolean.valueOf(s).booleanValue();
109 } else {
110 alwaysFollow = true;
111 }
112
113 s = element.getAttribute("deleteCookie");
114 if (s != null && !s.equals("")) {
115 deleteCookie = Boolean.valueOf(s).booleanValue();
116 } else {
117 deleteCookie = false;
118 }
119
120 s = element.getAttribute("cacheDomain");
121 if ((s != null) && !s.equals("")) {
122 cacheDomain = s;
123 } else {
124 cacheDomain = "";
125 }
126
127 s = element.getAttribute("cacheExpiration");
128 if ((s != null) && !s.equals("")) {
129
130 try {
131
132 cacheExpiration = Integer.parseInt(s);
133 } catch (NumberFormatException ex) {
134
135 log.error("Invalid CacheExpiration value - " + s);
136 cacheExpiration = DEFAULT_CACHE_EXPIRATION;
137 }
138 } else {
139 cacheExpiration = DEFAULT_CACHE_EXPIRATION;
140 }
141 }
142
143
144
145
146 private SamlCookiePlugin() {
147 alwaysFollow = false;
148 deleteCookie = false;
149 cacheExpiration = DEFAULT_CACHE_EXPIRATION;
150 }
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170 public PluginContext lookup(HttpServletRequest req,
171 HttpServletResponse res,
172 PluginMetadataParameter parameter,
173 Map<String, IdPSite> validIdps,
174 PluginContext context,
175 List <IdPSite> idpList) throws WayfRequestHandled {
176
177 if (context != null) {
178
179
180
181 return context;
182 }
183
184 if (deleteCookie) {
185 deleteCookie(req, res);
186
187
188
189 return new Context() ;
190 }
191 List <String> idps = getIdPCookie(req, res, cacheDomain).getIdPList();
192
193 for (String idpName : idps) {
194 IdPSite idp = validIdps.get(idpName);
195 if (idp != null) {
196 if (alwaysFollow) {
197 try {
198 DiscoveryServiceHandler.forwardRequest(req, res, idp);
199 } catch (WayfException e) {
200
201 ;
202 }
203 throw new WayfRequestHandled();
204 }
205
206
207
208 idpList.add(idp);
209 }
210 }
211
212 return null;
213 }
214
215
216
217
218
219
220
221 public PluginMetadataParameter refreshMetadata(MetadataProvider metadata) {
222
223
224
225 return null;
226 }
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248 public PluginContext search(HttpServletRequest req,
249 HttpServletResponse res,
250 PluginMetadataParameter parameter,
251 String pattern,
252 Map<String, IdPSite> validIdps,
253 PluginContext context,
254 Collection<IdPSite> searchResult,
255 List<IdPSite> idpList) throws WayfRequestHandled {
256
257
258
259 return lookup(req, res, parameter, validIdps, context, idpList);
260 }
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275 public void selected(HttpServletRequest req, HttpServletResponse res,
276 PluginMetadataParameter parameter, String idP) {
277
278 SamlIdPCookie cookie = getIdPCookie(req, res, cacheDomain);
279 String param = req.getParameter(PARAMETER_NAME);
280
281 if (null == param || param.equals("")) {
282 return;
283 } else if (param.equalsIgnoreCase(PARAMETER_SESSION)) {
284 cookie.addIdPName(idP, -1);
285 } else if (param.equalsIgnoreCase(PARAMETER_PERM)) {
286 cookie.addIdPName(idP, cacheExpiration);
287 }
288 }
289
290
291
292
293
294
295
296
297 private static class Context implements PluginContext {}
298
299
300
301
302 public final class SamlIdPCookie {
303
304
305
306
307
308 private final HttpServletRequest req;
309
310
311
312 private final HttpServletResponse res;
313
314
315
316 private final String domain;
317
318
319
320 private final List <String> idPList = new ArrayList<String>();
321
322
323
324
325
326
327
328
329
330
331 private SamlIdPCookie(String codedData,
332 HttpServletRequest request,
333 HttpServletResponse response,
334 String domainName) {
335
336 this.req = request;
337 this.res = response;
338 this.domain = domainName;
339
340 int start;
341 int end;
342
343 if (codedData == null || codedData.equals("")) {
344 log.info("Empty cookie");
345 return;
346 }
347
348
349
350
351 if (codedData.charAt(0) == '"' && codedData.charAt(codedData.length()-1) == '"') {
352 codedData = codedData.substring(1,codedData.length()-1);
353 }
354
355 try {
356 codedData = URLDecoder.decode(codedData, "UTF-8");
357 } catch (UnsupportedEncodingException e) {
358 log.error("could not decode cookie");
359 return;
360 }
361
362 start = 0;
363 end = codedData.indexOf(' ', start);
364 while (end > 0) {
365 String value = codedData.substring(start, end);
366 start = end + 1;
367 end = codedData.indexOf(' ', start);
368 if (!value.equals("")) {
369 idPList.add(new String(Base64.decode(value)));
370 }
371 }
372 if (start < codedData.length()) {
373 String value = codedData.substring(start);
374 if (!value.equals("")) {
375 idPList.add(new String(Base64.decode(value)));
376 }
377 }
378 }
379
380
381
382
383
384
385
386 private SamlIdPCookie(HttpServletRequest request, HttpServletResponse response, String domainName) {
387 this.req = request;
388 this.res = response;
389 this.domain = domainName;
390 }
391
392
393
394
395
396
397
398
399
400
401 private void addIdPName(String idPName, int expiration) {
402
403 idPList.remove(idPName);
404 idPList.add(0, idPName);
405
406 writeCookie(expiration);
407 }
408
409
410
411
412
413
414
415
416
417
418
419
420
421 public void deleteIdPName(String origin, int expiration) {
422 idPList.remove(origin);
423 writeCookie(expiration);
424 }
425
426
427
428
429
430
431 private void writeCookie(int expiration) {
432 Cookie cookie = getCookie(req);
433
434 if (idPList.size() == 0) {
435
436
437
438 cookie.setPath("/");
439 cookie.setMaxAge(0);
440 res.addCookie(cookie);
441 return;
442 }
443
444
445
446
447 StringBuffer buffer = new StringBuffer();
448 Iterator <String> it = idPList.iterator();
449
450 while (it.hasNext()) {
451 String next = it.next();
452 String what = new String(Base64.encodeBytes(next.getBytes()));
453 buffer.append(what).append(' ');
454 }
455
456 String value;
457 try {
458 value = URLEncoder.encode(buffer.toString(), "UTF-8");
459 } catch (UnsupportedEncodingException e) {
460 log.error("Could not encode cookie");
461 return;
462 }
463
464 if (cookie == null) {
465 cookie = new Cookie(COOKIE_NAME, value);
466 } else {
467 cookie.setValue(value);
468 }
469 cookie.setComment("Used to cache selection of a user's Shibboleth IdP");
470 cookie.setPath("/");
471
472
473 cookie.setMaxAge(expiration);
474
475 if (domain != null && domain != "") {
476 cookie.setDomain(domain);
477 }
478 res.addCookie(cookie);
479
480 }
481
482
483
484
485
486 public List <String> getIdPList() {
487 return idPList;
488 }
489 }
490
491
492
493
494
495
496 private static Cookie getCookie(HttpServletRequest req) {
497
498 Cookie[] cookies = req.getCookies();
499 if (cookies != null) {
500 for (int i = 0; i < cookies.length; i++) {
501 if (cookies[i].getName().equals(COOKIE_NAME)) {
502 return cookies[i];
503 }
504 }
505 }
506 return null;
507 }
508
509
510
511
512
513
514 private static void deleteCookie(HttpServletRequest req, HttpServletResponse res) {
515 Cookie cookie = getCookie(req);
516
517 if (cookie == null) {
518 return;
519 }
520
521 cookie.setPath("/");
522 cookie.setMaxAge(0);
523 res.addCookie(cookie);
524 }
525
526
527
528
529
530
531
532
533
534 private SamlIdPCookie getIdPCookie(HttpServletRequest req, HttpServletResponse res, String domain) {
535 Cookie cookie = getCookie(req);
536
537 if (cookie == null) {
538 return new SamlIdPCookie(req, res, domain);
539 } else {
540 return new SamlIdPCookie(cookie.getValue(), req, res, domain);
541 }
542 }
543 }
544