1 7 package org.jboss.web.tomcat.tc5.sso; 8 9 import java.io.Serializable ; 10 import java.security.Principal ; 11 import java.util.HashSet ; 12 import java.util.LinkedList ; 13 import java.util.Set ; 14 15 import javax.management.MBeanServer ; 16 import javax.management.ObjectName ; 17 import javax.naming.InitialContext ; 18 import javax.naming.NamingException ; 19 import javax.transaction.UserTransaction ; 20 21 import org.apache.catalina.LifecycleException; 22 import org.apache.catalina.LifecycleListener; 23 import org.apache.catalina.Session; 24 import org.apache.catalina.util.LifecycleSupport; 25 import org.jboss.cache.Fqn; 26 import org.jboss.cache.TreeCache; 27 import org.jboss.cache.TreeCacheListener; 28 import org.jboss.logging.Logger; 29 import org.jboss.mx.util.MBeanServerLocator; 30 import org.jboss.web.tomcat.tc5.Tomcat5; 31 import org.jgroups.View; 32 33 40 public final class TreeCacheSSOClusterManager 41 implements SSOClusterManager, TreeCacheListener 42 { 43 45 49 private static final String CREDENTIALS = "credentials"; 50 51 55 private static final String SSO = "SSO"; 56 57 61 private static final String SESSIONS = "sessions"; 62 63 66 private static final String KEY = "key"; 67 68 71 public static final String DEFAULT_GLOBAL_CACHE_NAME = 72 Tomcat5.DEFAULT_CACHE_NAME; 73 74 77 private static final String [] GET_SIGNATURE = 78 {Fqn.class.getName(), Object .class.getName()}; 79 80 83 private static final String [] PUT_SIGNATURE = 84 {Fqn.class.getName(), Object .class.getName(), Object .class.getName()}; 85 86 89 private static final String [] REMOVE_SIGNATURE = {Fqn.class.getName()}; 90 91 93 96 private LinkedList beingLocallyAdded = new LinkedList (); 97 98 101 private LinkedList beingLocallyRemoved = new LinkedList (); 102 103 107 private LinkedList beingRemotelyRemoved = new LinkedList (); 108 109 112 private ObjectName cacheObjectName = null; 113 114 117 private String cacheName = null; 118 119 123 private CredentialUpdater credentialUpdater = null; 124 125 128 private InitialContext initialContext = null; 129 130 133 private LifecycleSupport lifecycle = new LifecycleSupport(this); 134 135 138 private Logger log = Logger.getLogger(getClass().getName());; 139 140 143 private boolean registeredAsListener = false; 144 145 148 private MBeanServer server = null; 149 150 153 private ClusteredSingleSignOn ssoValve = null; 154 155 158 private boolean started = false; 159 160 163 private boolean treeCacheAvailable = false; 164 165 168 private boolean missingCacheErrorLogged = false; 169 170 172 173 176 public TreeCacheSSOClusterManager() 177 { 178 server = MBeanServerLocator.locate(); 180 } 181 182 183 185 public String getCacheName() 186 { 187 return cacheName; 188 } 189 190 public void setCacheName(String objectName) 191 throws Exception 192 { 193 if (objectName == null) 194 { 195 setCacheObjectName(null); 196 } 197 else if (objectName.equals(cacheName) == false) 198 { 199 setCacheObjectName(new ObjectName (objectName)); 200 } 201 } 202 203 public ObjectName getCacheObjectName() 204 { 205 return cacheObjectName; 206 } 207 208 public void setCacheObjectName(ObjectName objectName) 209 throws Exception 210 { 211 if ((objectName != null && objectName.equals(cacheObjectName)) 213 || (cacheObjectName != null && cacheObjectName.equals(objectName)) 214 || (objectName == null && cacheObjectName == null)) 215 { 216 return; 217 } 218 219 removeAsTreeCacheListener(cacheObjectName); 220 this.cacheObjectName = objectName; 221 this.cacheName = (objectName == null 222 ? null 223 : objectName.getCanonicalName()); 224 225 if (false == isTreeCacheAvailable(true)) 226 { 227 if (started) 228 { 229 logMissingCacheError(); 230 } 231 else 232 { 233 log.info("Cannot find TreeCache using " + cacheName + " -- tree" + 235 "CacheName must be set to point to a running TreeCache " + 236 "before ClusteredSingleSignOn can handle requests"); 237 } 238 } 239 } 240 241 243 249 public void addSession(String ssoId, Session session) 250 { 251 if (ssoId == null || session == null) 252 { 253 return; 254 } 255 256 if (false == isTreeCacheAvailable(false)) 257 { 258 logMissingCacheError(); 259 return; 260 } 261 262 if (log.isTraceEnabled()) 263 { 264 log.trace("addSession(): adding Session " + session.getId() + 265 " to cached session set for SSO " + ssoId); 266 } 267 268 Fqn fqn = getSessionsFqn(ssoId); 269 UserTransaction tx = null; 270 try 271 { 272 tx = getNewTransaction(); 273 tx.begin(); 274 Set sessions = getSessionSet(fqn, true); 275 sessions.add(session.getId()); 276 putInTreeCache(fqn, sessions); 277 tx.commit(); 278 } 279 catch (Exception e) 280 { 281 if (tx != null) 282 { 283 try 284 { 285 tx.rollback(); 286 } 287 catch (Exception x) 288 { 289 } 290 } 291 String sessId = (session == null ? "NULL" : session.getId()); 292 log.error("caught exception adding session " + sessId + 293 " to SSO id " + ssoId, e); 294 } 295 } 296 297 298 304 public ClusteredSingleSignOn getSingleSignOnValve() 305 { 306 return ssoValve; 307 } 308 309 310 318 public void setSingleSignOnValve(ClusteredSingleSignOn valve) 319 { 320 ssoValve = valve; 321 } 322 323 324 330 public void logout(String ssoId) 331 { 332 if (false == isTreeCacheAvailable(false)) 333 { 334 logMissingCacheError(); 335 return; 336 } 337 338 { 341 if (beingLocallyRemoved.contains(ssoId)) 342 { 343 return; 344 } 345 beingLocallyRemoved.add(ssoId); 348 } 349 350 if (log.isTraceEnabled()) 351 { 352 log.trace("Registering logout of SSO " + ssoId + 353 " in clustered cache"); 354 } 355 356 Fqn fqn = getSingleSignOnFqn(ssoId); 357 358 try 360 { 361 removeFromTreeCache(fqn); 364 } 366 catch (Exception e) 367 { 368 378 log.error("Exception attempting to remove node " + 379 fqn.toString() + " from TreeCache", e); 380 } 381 finally 382 { 383 { 385 beingLocallyRemoved.remove(ssoId); 386 } 387 } 388 } 389 390 391 400 public SingleSignOnEntry lookup(String ssoId) 401 { 402 if (false == isTreeCacheAvailable(false)) 403 { 404 logMissingCacheError(); 405 return null; 406 } 407 408 SingleSignOnEntry entry = null; 409 Fqn fqn = getCredentialsFqn(ssoId); 411 try 413 { 414 SSOCredentials data = (SSOCredentials) getFromTreeCache(fqn); 417 if (data != null) 418 { 419 entry = new SingleSignOnEntry(null, 420 data.getAuthType(), 421 data.getUsername(), 422 data.getPassword()); 423 } 424 } 426 catch (Exception e) 427 { 428 438 log.error("caught exception looking up SSOCredentials for SSO id " + 439 ssoId, e); 440 } 441 return entry; 442 } 443 444 445 454 public void register(String ssoId, String authType, 455 String username, String password) 456 { 457 if (false == isTreeCacheAvailable(false)) 458 { 459 logMissingCacheError(); 460 return; 461 } 462 463 if (log.isTraceEnabled()) 464 { 465 log.trace("Registering SSO " + ssoId + " in clustered cache"); 466 } 467 468 storeSSOData(ssoId, authType, username, password); 469 } 470 471 472 478 public void removeSession(String ssoId, Session session) 479 { 480 if (false == isTreeCacheAvailable(false)) 481 { 482 logMissingCacheError(); 483 return; 484 } 485 486 { 490 if (beingRemotelyRemoved.contains(ssoId)) 491 { 492 return; 493 } 494 } 495 496 if (log.isTraceEnabled()) 497 { 498 log.trace("removeSession(): removing Session " + session.getId() + 499 " from cached session set for SSO " + ssoId); 500 } 501 502 Fqn fqn = getSessionsFqn(ssoId); 503 UserTransaction tx = null; 504 boolean removing = false; 505 try 506 { 507 tx = getNewTransaction(); 508 509 tx.begin(); 510 Set sessions = getSessionSet(fqn, false); 511 if (sessions != null) 512 { 513 sessions.remove(session.getId()); 514 if (sessions.size() == 0) 515 { 516 { 520 beingLocallyRemoved.add(ssoId); 521 } 522 removing = true; 523 removeFromTreeCache(getSingleSignOnFqn(ssoId)); 525 } 526 else 527 { 528 putInTreeCache(fqn, sessions); 529 } 530 } 531 tx.commit(); 532 } 533 catch (Exception e) 534 { 535 if (tx != null) 536 { 537 try 538 { 539 tx.rollback(); 540 } 541 catch (Exception x) 542 { 543 } 544 } 545 String sessId = (session == null ? "NULL" : session.getId()); 546 log.error("caught exception removing session " + sessId + 547 " from SSO id " + ssoId, e); 548 } 549 finally 550 { 551 if (removing) 552 { 553 { 555 beingLocallyRemoved.remove(ssoId); 556 } 557 } 558 } 559 } 560 561 562 572 public void updateCredentials(String ssoId, String authType, 573 String username, String password) 574 { 575 if (false == isTreeCacheAvailable(false)) 576 { 577 logMissingCacheError(); 578 return; 579 } 580 581 if (log.isTraceEnabled()) 582 { 583 log.trace("Updating credentials for SSO " + ssoId + 584 " in clustered cache"); 585 } 586 587 storeSSOData(ssoId, authType, username, password); 588 } 589 590 591 593 596 public void nodeCreated(Fqn fqn) 597 { 598 ; } 600 601 604 public void nodeLoaded(Fqn fqn) 605 { 606 ; } 608 609 610 613 public void nodeVisited(Fqn fqn) 614 { 615 ; } 617 618 619 622 public void cacheStarted(TreeCache cache) 623 { 624 ; } 626 627 628 631 public void cacheStopped(TreeCache cache) 632 { 633 ; } 635 636 637 646 public void nodeRemoved(Fqn fqn) 647 { 648 String ssoId = getIdFromFqn(fqn); 649 650 { 653 if (beingLocallyRemoved.contains(ssoId)) 654 { 655 return; 656 } 657 } 658 659 { 661 beingRemotelyRemoved.add(ssoId); 662 } 663 664 try 665 { 666 if (log.isTraceEnabled()) 667 { 668 log.trace("received a node removed message for SSO " + ssoId); 669 } 670 671 ssoValve.deregister(ssoId); 672 } 673 finally 674 { 675 { 677 beingRemotelyRemoved.remove(ssoId); 678 } 679 } 680 681 } 682 683 684 699 public void nodeModified(Fqn fqn) 700 { 701 if (CREDENTIALS.equals(getTypeFromFqn(fqn)) == false) 703 { 704 return; 705 } 706 707 String ssoId = getIdFromFqn(fqn); 708 709 { 712 if (beingLocallyAdded.contains(ssoId)) 713 { 714 return; 715 } 716 } 717 718 SingleSignOnEntry sso = ssoValve.localLookup(ssoId); 719 if (sso == null || sso.getCanReauthenticate()) 720 { 721 return; 723 } 724 725 if (log.isTraceEnabled()) 726 { 727 log.trace("received a credentials modified message for SSO " + ssoId); 728 } 729 730 credentialUpdater.enqueue(sso, ssoId); 732 } 733 734 735 738 public void viewChange(View new_view) 739 { 740 ; } 742 743 744 749 public void nodeEvicted(Fqn fqn) 750 { 751 ; } 754 755 756 758 759 764 public void addLifecycleListener(LifecycleListener listener) 765 { 766 lifecycle.addLifecycleListener(listener); 767 } 768 769 770 774 public LifecycleListener[] findLifecycleListeners() 775 { 776 return lifecycle.findLifecycleListeners(); 777 } 778 779 780 785 public void removeLifecycleListener(LifecycleListener listener) 786 { 787 lifecycle.removeLifecycleListener(listener); 788 } 789 790 799 public void start() throws LifecycleException 800 { 801 if (started) 803 { 804 throw new LifecycleException 805 ("TreeCacheSSOClusterManager already Started"); 806 } 807 808 credentialUpdater = new CredentialUpdater(); 810 811 started = true; 812 813 lifecycle.fireLifecycleEvent(START_EVENT, null); 815 } 816 817 818 827 public void stop() throws LifecycleException 828 { 829 if (!started) 831 { 832 throw new LifecycleException 833 ("TreeCacheSSOClusterManager not Started"); 834 } 835 836 credentialUpdater.stop(); 837 838 started = false; 839 840 lifecycle.fireLifecycleEvent(STOP_EVENT, null); 842 } 843 844 845 847 private Object getFromTreeCache(Fqn fqn) throws Exception 848 { 849 Object [] args = new Object []{fqn, KEY}; 850 return server.invoke(getCacheObjectName(), "get", args, GET_SIGNATURE); 851 } 852 853 private Fqn getCredentialsFqn(String ssoid) 854 { 855 Object [] objs = new Object []{SSO, ssoid, CREDENTIALS}; 856 return new Fqn(objs); 857 } 858 859 private Fqn getSessionsFqn(String ssoid) 860 { 861 Object [] objs = new Object []{SSO, ssoid, SESSIONS}; 862 return new Fqn(objs); 863 } 864 865 private Fqn getSingleSignOnFqn(String ssoid) 866 { 867 Object [] objs = new Object []{SSO, ssoid}; 868 return new Fqn(objs); 869 } 870 871 877 private String getIdFromFqn(Fqn fqn) 878 { 879 return (String ) fqn.get(1); 880 } 881 882 private InitialContext getInitialContext() throws NamingException 883 { 884 if (initialContext == null) 885 { 886 initialContext = new InitialContext (); 887 } 888 return initialContext; 889 } 890 891 private Set getSessionSet(Fqn fqn, boolean create) 892 throws Exception 893 { 894 Set sessions = (Set ) getFromTreeCache(fqn); 895 if (create && sessions == null) 896 { 897 sessions = new HashSet (); 898 } 899 return sessions; 900 } 901 902 910 private String getTypeFromFqn(Fqn fqn) 911 { 912 return (String ) fqn.get(fqn.size() - 1); 913 } 914 915 private UserTransaction getNewTransaction() throws NamingException 916 { 917 try 918 { 919 UserTransaction t = 920 (UserTransaction ) getInitialContext().lookup("UserTransaction"); 921 return t; 922 } 923 catch (NamingException n) 924 { 925 initialContext = null; 928 throw n; 929 } 930 } 931 932 941 private synchronized boolean isTreeCacheAvailable(boolean forceCheck) 942 { 943 if (forceCheck || treeCacheAvailable == false) 944 { 945 boolean available = (cacheObjectName != null); 946 if (available) 947 { 948 Set s = server.queryMBeans(cacheObjectName, null); 949 available = s.size() > 0; 950 if (available) 951 { 952 try 953 { 954 registerAsTreeCacheListener(cacheObjectName); 955 setMissingCacheErrorLogged(false); 956 } 957 catch (Exception e) 958 { 959 log.error("Caught exception registering as listener to " + 960 cacheObjectName, e); 961 available = false; 962 } 963 } 964 } 965 treeCacheAvailable = available; 966 } 967 return treeCacheAvailable; 968 } 969 970 private void putInTreeCache(Fqn fqn, Object data) throws Exception 971 { 972 Object [] args = new Object []{fqn, KEY, data}; 973 server.invoke(getCacheObjectName(), "put", args, PUT_SIGNATURE); 974 } 975 976 982 private void registerAsTreeCacheListener(ObjectName listenTo) 983 throws Exception 984 { 985 server.invoke(listenTo, "addTreeCacheListener", 986 new Object []{this}, 987 new String []{TreeCacheListener.class.getName()}); 988 registeredAsListener = true; 989 } 990 991 992 998 private void removeAsTreeCacheListener(ObjectName removeFrom) 999 throws Exception 1000 { 1001 if (registeredAsListener && removeFrom != null) 1002 { 1003 server.invoke(removeFrom, "removeTreeCacheListener", 1004 new Object []{this}, 1005 new String []{TreeCacheListener.class.getName()}); 1006 } 1007 } 1008 1009 private void removeFromTreeCache(Fqn fqn) throws Exception 1010 { 1011 server.invoke(getCacheObjectName(), "remove", 1012 new Object []{fqn}, 1013 REMOVE_SIGNATURE); 1014 } 1015 1016 1032 private void storeSSOData(String ssoId, String authType, String username, 1033 String password) 1034 { 1035 SSOCredentials data = new SSOCredentials(authType, username, password); 1036 { 1040 beingLocallyAdded.add(ssoId); 1041 } 1042 try 1044 { 1045 putInTreeCache(getCredentialsFqn(ssoId), data); 1048 } 1050 catch (Exception e) 1051 { 1052 1062 log.error("Exception attempting to add TreeCache nodes for SSO " + 1063 ssoId, e); 1064 } 1065 finally 1066 { 1067 { 1069 beingLocallyAdded.remove(ssoId); 1070 } 1071 } 1072 } 1073 1074 private boolean isMissingCacheErrorLogged() 1075 { 1076 return missingCacheErrorLogged; 1077 } 1078 1079 private void setMissingCacheErrorLogged(boolean missingCacheErrorLogged) 1080 { 1081 this.missingCacheErrorLogged = missingCacheErrorLogged; 1082 } 1083 1084 private void logMissingCacheError() 1085 { 1086 StringBuffer msg = new StringBuffer ("Cannot find TreeCache using "); 1087 msg.append(getCacheName()); 1088 msg.append(" -- TreeCache must be started before ClusteredSingleSignOn "); 1089 msg.append("can handle requests"); 1090 1091 if (isMissingCacheErrorLogged()) 1092 { 1093 log.warn(msg); 1095 } 1096 else 1097 { 1098 log.error(msg); 1099 setMissingCacheErrorLogged(true); 1101 } 1102 } 1103 1104 1106 1109 private class CredentialUpdater 1110 implements Runnable 1111 { 1112 private HashSet awaitingUpdate = new HashSet (); 1113 private Thread updateThread; 1114 private boolean updateThreadSleeping = false; 1115 private boolean queueEmpty = true; 1116 private boolean stopped = false; 1117 1118 private CredentialUpdater() 1119 { 1120 updateThread = 1121 new Thread (this, "SSOClusterManager.CredentialUpdater"); 1122 updateThread.setDaemon(true); 1123 updateThread.start(); 1124 } 1125 1126 1128 public void run() 1129 { 1130 while (!stopped) 1131 { 1132 try 1134 { 1135 updateThreadSleeping = false; 1136 SSOWrapper[] ssos = null; 1138 synchronized (awaitingUpdate) 1139 { 1140 ssos = new SSOWrapper[awaitingUpdate.size()]; 1141 ssos = (SSOWrapper[]) awaitingUpdate.toArray(ssos); 1142 awaitingUpdate.clear(); 1143 queueEmpty = true; 1144 } 1145 1146 for (int i = 0; i < ssos.length; i++) 1148 { 1149 processUpdate(ssos[i]); 1150 } 1151 1152 if (queueEmpty) 1156 { 1157 try 1158 { 1159 updateThreadSleeping = true; 1175 updateThread.sleep(30000); 1176 } 1177 catch (InterruptedException e) 1178 { 1179 if (log.isTraceEnabled()) 1180 { 1181 log.trace("CredentialUpdater: interrupted"); 1182 } 1183 } 1185 } 1186 else if (log.isTraceEnabled()) 1187 { 1188 log.trace("CredentialUpdater: more updates added while " + 1189 "handling existing updates"); 1190 } 1191 } 1192 catch (Exception e) 1193 { 1194 log.error("CredentialUpdater thread caught an exception", e); 1195 } 1196 } 1197 } 1198 1199 1201 1208 private void enqueue(SingleSignOnEntry sso, String ssoId) 1209 { 1210 synchronized (awaitingUpdate) 1211 { 1212 awaitingUpdate.add(new SSOWrapper(sso, ssoId)); 1213 queueEmpty = false; 1214 } 1215 if (updateThreadSleeping) 1220 { 1221 updateThread.interrupt(); 1222 } 1223 } 1224 1225 private void processUpdate(SSOWrapper wrapper) 1226 { 1227 if (wrapper.sso.getCanReauthenticate()) 1228 { 1229 return; 1231 } 1232 1233 Fqn fqn = getCredentialsFqn(wrapper.id); 1234 try 1236 { 1237 SSOCredentials data = (SSOCredentials) getFromTreeCache(fqn); 1240 if (data != null) 1241 { 1242 String authType = data.getAuthType(); 1245 String username = data.getUsername(); 1246 String password = data.getPassword(); 1247 1249 if (log.isTraceEnabled()) 1250 { 1251 log.trace("CredentialUpdater: Updating credentials for SSO " + 1252 wrapper.sso); 1253 } 1254 1255 synchronized (wrapper.sso) 1256 { 1257 Principal p = wrapper.sso.getPrincipal(); 1259 wrapper.sso.updateCredentials(p, authType, username, password); 1260 } 1261 } 1262 1268 1269 } 1270 catch (Exception e) 1271 { 1272 1282 log.error("Exception attempting to get SSOCredentials from " + 1283 "TreeCache node " + fqn.toString(), e); 1284 } 1285 } 1286 1287 1290 private void stop() 1291 { 1292 stopped = true; 1293 } 1294 1295 } 1297 1298 1301 private class SSOWrapper 1302 { 1303 private SingleSignOnEntry sso = null; 1304 private String id = null; 1305 1306 private SSOWrapper(SingleSignOnEntry entry, String ssoId) 1307 { 1308 this.sso = entry; 1309 this.id = ssoId; 1310 } 1311 } 1312 1313 1315 1320 public static class SSOCredentials 1321 implements Serializable 1322 { 1323 static final long serialVersionUID = 5704877226920571663L; 1324 private String authType = null; 1325 private String password = null; 1326 private String username = null; 1327 1328 1336 private SSOCredentials(String authType, String username, String password) 1337 { 1338 this.authType = authType; 1339 this.username = username; 1340 this.password = password; 1341 } 1342 1343 1348 public String getUsername() 1349 { 1350 return username; 1351 } 1352 1353 1358 public String getAuthType() 1359 { 1360 return authType; 1361 } 1362 1363 1369 private String getPassword() 1370 { 1371 return password; 1372 } 1373 1374 } 1376} 1378 | Popular Tags |