diff --git a/kerberos/docs/sakai-jaas.conf b/kerberos/docs/sakai-jaas.conf index 1d47667..d6ed2f1 100644 --- a/kerberos/docs/sakai-jaas.conf +++ b/kerberos/docs/sakai-jaas.conf @@ -3,6 +3,18 @@ */ KerberosAuthentication { - com.sun.security.auth.module.Krb5LoginModule required; + com.sun.security.auth.module.Krb5LoginModule required + storeKey="true" + useTicketCache="false"; }; +ServiceKerberosAuthentication { + com.sun.security.auth.module.Krb5LoginModule required + // debug="true" + doNotPrompt="true" + principal="sakai/bit.oucs.ox.ac.uk" + useKeyTab="true" + storeKey="true" // Store the key inside the subject + isInitiator="false" // JDK 6 Only + useTicketCache="false"; +}; \ No newline at end of file diff --git a/kerberos/pom.xml b/kerberos/pom.xml index 2d2d55c..94ae254 100644 --- a/kerberos/pom.xml +++ b/kerberos/pom.xml @@ -18,6 +18,7 @@ jar + true @@ -55,11 +56,6 @@ sakai-util ${sakai.version} - - commons-codec - commons-codec - 1.3 - diff --git a/kerberos/src/java/org/sakaiproject/component/kerberos/user/JassAuthenticate.java b/kerberos/src/java/org/sakaiproject/component/kerberos/user/JassAuthenticate.java new file mode 100644 index 0000000..432c603 --- /dev/null +++ b/kerberos/src/java/org/sakaiproject/component/kerberos/user/JassAuthenticate.java @@ -0,0 +1,175 @@ +package org.sakaiproject.component.kerberos.user; +/********************************************************************************** + * $URL$ + * $Id$ + *********************************************************************************** + * + * Copyright (c) 2005 The Sakai Foundation. + * + * Licensed under the Educational Community License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.opensource.org/licenses/ecl1.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **********************************************************************************/ + + +import java.security.PrivilegedAction; + +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.ietf.jgss.GSSContext; +import org.ietf.jgss.GSSCredential; +import org.ietf.jgss.GSSException; +import org.ietf.jgss.GSSManager; +import org.ietf.jgss.GSSName; +import org.ietf.jgss.Oid; + +/* + * JaasTest -- attempts to authenticate a user and reports success or an error message + * Argument: LoginContext [optional, default is "JaasAuthentication"] + * (must exist in "login configuration file" specified in ${java.home}/lib/security/java.security) + * + * @author Matthew Buckett + * + */ +public class JassAuthenticate { + + private final static Log log = LogFactory.getLog(JassAuthenticate.class); + + private GSSContext clientContext; + private GSSContext serverContext; + + private byte[] acceptTokens = new byte[0]; + private byte[] initTokens = new byte[0]; + + private String serverGSS; + private int exchangeLimit = 50; + + private String servicePrincipal; + private String userPrincipal; + + private boolean verifyServiceTicket = false; + + /** + * Get ready for JAAS authenitcation, but don't verify a service ticket. + */ + public JassAuthenticate() { + verifyServiceTicket = false; + } + + /** + * Get ready for JAAS authentication and attempt to do service ticket verification. + */ + public JassAuthenticate(String serverGSS, String servicePrincipal, String userPrincipal) { + this.serverGSS = serverGSS; + this.servicePrincipal = servicePrincipal; + this.userPrincipal = userPrincipal; + verifyServiceTicket = true; + } + + private class InitiatorAction implements PrivilegedAction { + public Void run() { + try { + initTokens = clientContext.initSecContext(acceptTokens, 0, acceptTokens.length); + } catch (GSSException e) { + throw new RuntimeException("Failed to initiate.", e); + } + return null; + } + } + + private class AcceptorAction implements PrivilegedAction { + public Void run() { + try { + acceptTokens = serverContext.acceptSecContext(initTokens, 0, initTokens.length); + } catch (GSSException e) { + throw new RuntimeException("Failed to accept.", e); + } + return null; + } + } + + public boolean attemptAuthentication(String username, String password) { + LoginContext userLoginContext = null; + LoginContext serverLoginContext = null; + + try { + // This may well fail so run catch exceptions here. + try { + userLoginContext = new LoginContext(userPrincipal, new UsernamePasswordCallback(username, password)); + userLoginContext.login(); + } catch (LoginException le) { + if (log.isDebugEnabled()) { + log.debug("Failed to authenticate "+ username, le); + } + return false; + } + if(!verifyServiceTicket) { + log.debug("Authenticated ok and not attempting service ticket verification"); + return true; + } + // Shouldn't ever fail + serverLoginContext = new LoginContext(servicePrincipal, new NullCallbackHandler()); + serverLoginContext.login(); + + + GSSManager manager = GSSManager.getInstance(); + Oid kerberos = new Oid("1.2.840.113554.1.2.2"); + + GSSName serverName = manager.createName( + serverGSS, GSSName.NT_HOSTBASED_SERVICE); + + clientContext = manager.createContext( + serverName, kerberos, null, + GSSContext.DEFAULT_LIFETIME); + + serverContext = manager.createContext((GSSCredential)null); + + int exchanges = 0; + while (!clientContext.isEstablished() && !serverContext.isEstablished() && !(initTokens == null && acceptTokens == null)) { + Subject.doAs(userLoginContext.getSubject(), new InitiatorAction()); + Subject.doAs(serverLoginContext.getSubject(), new AcceptorAction()); + if (++exchanges > exchangeLimit) { + throw new RuntimeException("Too many tickets exchanged ("+ exchangeLimit+ ")."); + } + } + log.debug("Authenticated ok and verified service ticket"); + return true; + } catch (GSSException gsse) { + log.warn("Failed to verify ticket.", gsse); + } catch (LoginException le) { + log.warn("Failed to login with keytab.", le); + } finally { + try { + if (clientContext != null) + clientContext.dispose(); + if (serverContext != null) + serverContext.dispose(); + + if (userLoginContext != null) + userLoginContext.logout(); + if (serverLoginContext!= null) + serverLoginContext.logout(); + } catch (Exception e) { + log.error("Failed to tidy up after attempting authentication.", e); + } + } + return false; + } + + public boolean isVerifyServiceTicket() { + return verifyServiceTicket; + } +} diff --git a/kerberos/src/java/org/sakaiproject/component/kerberos/user/KerberosUserDirectoryProvider.java b/kerberos/src/java/org/sakaiproject/component/kerberos/user/KerberosUserDirectoryProvider.java index 0e9cdac..7d3e654 100644 --- a/kerberos/src/java/org/sakaiproject/component/kerberos/user/KerberosUserDirectoryProvider.java +++ b/kerberos/src/java/org/sakaiproject/component/kerberos/user/KerberosUserDirectoryProvider.java @@ -23,9 +23,7 @@ package org.sakaiproject.component.kerberos.user; import java.io.File; import java.io.IOException; -import java.security.MessageDigest; import java.util.Collection; -import java.util.Hashtable; import java.util.Iterator; import javax.security.auth.callback.Callback; @@ -45,8 +43,6 @@ import org.sakaiproject.user.api.UserDirectoryProvider; import org.sakaiproject.user.api.UserEdit; import org.sakaiproject.util.StringUtil; -import org.apache.commons.codec.binary.Base64; - /** *

* KerberosUserDirectoryProvider is a UserDirectoryProvider that authenticates usernames using Kerberos. @@ -69,7 +65,7 @@ public class KerberosUserDirectoryProvider implements UserDirectoryProvider *********************************************************************************************************************************************************************************************************************************************************/ /** Configuration: Domain */ - protected String m_domain = "domain.tld"; + protected String m_domain = null; /** * Configuration: Domain Name (for E-Mail Addresses) @@ -95,6 +91,33 @@ public class KerberosUserDirectoryProvider implements UserDirectoryProvider { m_logincontext = logincontext; } + + /** Configuration: ServiceLoginContext */ + protected String m_servicelogincontext = "ServiceKerberosAuthentication"; + + /** + * Configuration: Service Authentication Name + * + * @param serviceLoginContext + * The context for the service to be used from the login.config file - default "ServiceKerberosAuthentication" + */ + public void setServiceLoginContext(String serviceLoginContext) + { + m_servicelogincontext = serviceLoginContext; + } + + /** Configuration: */ + protected String m_serviceprincipal; + + /** + * Configuration: GSS-API Service Principal + * + * @param serviceprincipal + * The name of the service principal for GSS-API. Needs to be set. + */ + public void setServicePrincipal(String serviceprincipal) { + this.m_serviceprincipal = serviceprincipal; + } /** Configuration: RequireLocalAccount */ protected boolean m_requirelocalaccount = true; @@ -124,26 +147,6 @@ public class KerberosUserDirectoryProvider implements UserDirectoryProvider m_knownusermsg = knownusermsg; } - /** Configuration: Cachettl */ - protected int m_cachettl = 5 * 60 * 1000; - - /** - * Configuration: Cache TTL - * - * @param cachettl - * Time (in milliseconds) to cache authenticated usernames - default is 300000 ms (5 minutes) - */ - public void setCachettl(int cachettl) - { - m_cachettl = cachettl; - } - - /** - * Hash table for auth caching - */ - - private Hashtable users = new Hashtable(); - /********************************************************************************************************************************************************************************************************************************************************** * Init and Destroy *********************************************************************************************************************************************************************************************************************************************************/ @@ -153,73 +156,74 @@ public class KerberosUserDirectoryProvider implements UserDirectoryProvider */ public void init() { - try + // Full paths only from the file + String kerberoskrb5conf = ServerConfigurationService.getString("provider.kerberos.krb5.conf", null); + String kerberosauthloginconfig = ServerConfigurationService.getString("provider.kerberos.auth.login.config", "sakai-jaas.conf"); + boolean kerberosshowconfig = ServerConfigurationService.getBoolean("provider.kerberos.showconfig", false); + String sakaihomepath = System.getProperty("sakai.home"); + + // if locations are configured in sakai.properties, use them in place of the current system locations + // if the location specified exists and is readable, use full absolute path + // otherwise, try file path relative to sakai.home + // if files are readable use the, otherwise print warning and use system defaults + if (kerberoskrb5conf != null) { - - // Full paths only from the file - String kerberoskrb5conf = ServerConfigurationService.getString("provider.kerberos.krb5.conf", null); - String kerberosauthloginconfig = ServerConfigurationService.getString("provider.kerberos.auth.login.config", null); - boolean kerberosshowconfig = ServerConfigurationService.getBoolean("provider.kerberos.showconfig", false); - String sakaihomepath = System.getProperty("sakai.home"); - - // if locations are configured in sakai.properties, use them in place of the current system locations - // if the location specified exists and is readable, use full absolute path - // otherwise, try file path relative to sakai.home - // if files are readable use the, otherwise print warning and use system defaults - if (kerberoskrb5conf != null) + if (new File(kerberoskrb5conf).canRead()) { - if (new File(kerberoskrb5conf).canRead()) - { - System.setProperty("java.security.krb5.conf", kerberoskrb5conf); - } - else if (new File(sakaihomepath + kerberoskrb5conf).canRead()) - { - System.setProperty("java.security.krb5.conf", sakaihomepath + kerberoskrb5conf); - } - else - { - M_log.warn(this + ".init(): Cannot set krb5conf location"); - kerberoskrb5conf = null; - } + System.setProperty("java.security.krb5.conf", kerberoskrb5conf); } - - if (kerberosauthloginconfig != null) + else if (new File(sakaihomepath, kerberoskrb5conf).canRead()) { - - if (new File(kerberosauthloginconfig).canRead()) - { - System.setProperty("java.security.auth.login.config", kerberosauthloginconfig); - } - else if (new File(sakaihomepath + kerberosauthloginconfig).canRead()) - { - System.setProperty("java.security.auth.login.config", sakaihomepath + kerberosauthloginconfig); - } - else - { - M_log.warn(this + ".init(): Cannot set kerberosauthloginconfig location"); - kerberosauthloginconfig = null; - } + System.setProperty("java.security.krb5.conf", sakaihomepath + kerberoskrb5conf); + } + else + { + M_log.info(this + ".init(): Using default rules for krb5.conf location."); + kerberoskrb5conf = null; } + } - M_log.info(this + ".init()" + " Domain=" + m_domain + " LoginContext=" + m_logincontext + " RequireLocalAccount=" - + m_requirelocalaccount + " KnownUserMsg=" + m_knownusermsg + " CacheTTL=" + m_cachettl); + if (kerberosauthloginconfig != null) + { - // show the whole config if set - // system locations will read NULL if not set (system defaults will be used) - if (kerberosshowconfig) + if (new File(kerberosauthloginconfig).canRead()) + { + System.setProperty("java.security.auth.login.config", kerberosauthloginconfig); + } + else if (new File(sakaihomepath, kerberosauthloginconfig).canRead()) { - M_log.info(this + ".init()" + " SakaiHome=" + sakaihomepath + " SakaiPropertyKrb5Conf=" + kerberoskrb5conf - + " SakaiPropertyAuthLoginConfig=" + kerberosauthloginconfig + " SystemPropertyKrb5Conf=" - + System.getProperty("java.security.krb5.conf") + " SystemPropertyAuthLoginConfig=" - + System.getProperty("java.security.auth.login.config")); + System.setProperty("java.security.auth.login.config", sakaihomepath + kerberosauthloginconfig); } + else + { + M_log.info(this + ".init(): Cannot set kerberosauthloginconfig location"); + kerberosauthloginconfig = null; + } + } + + if (m_serviceprincipal == null) + { + throw new IllegalStateException("Service principal can't be null."); + } + + M_log.info(this + ".init()" + " Domain=" + m_domain + " LoginContext=" + m_logincontext + " ServiceLoginContext="+ m_servicelogincontext+ + " GSS-API Principal="+ m_serviceprincipal+ " RequireLocalAccount=" + m_requirelocalaccount + " KnownUserMsg=" + m_knownusermsg ); + // show the whole config if set + // system locations will read NULL if not set (system defaults will be used) + if (kerberosshowconfig) + { + M_log.info(this + ".init()" + " SakaiHome=" + sakaihomepath + " SakaiPropertyKrb5Conf=" + kerberoskrb5conf + + " SakaiPropertyAuthLoginConfig=" + kerberosauthloginconfig + " SystemPropertyKrb5Conf=" + + System.getProperty("java.security.krb5.conf") + " SystemPropertyAuthLoginConfig=" + + System.getProperty("java.security.auth.login.config")); } - catch (Throwable t) + if (!m_requirelocalaccount && m_domain == null) { - M_log.warn(this + ".init(): ", t); + throw new IllegalStateException("If you don't require local accounts you must set the domain for email addresses."); } + } // init /** @@ -280,6 +284,7 @@ public class KerberosUserDirectoryProvider implements UserDirectoryProvider */ public boolean findUserByEmail(UserEdit edit, String email) { + if (m_requirelocalaccount) return false; // lets not get messed up with spaces or cases String test = email.toLowerCase().trim(); @@ -308,66 +313,15 @@ public class KerberosUserDirectoryProvider implements UserDirectoryProvider */ public boolean authenticateUser(String userId, UserEdit edit, String password) { - // The in-memory caching mechanism is implemented here - // try to get user from in-memory hashtable try { - UserData existingUser = (UserData) users.get(userId); - - boolean authUser = false; - String hpassword = encodeSHA(password); - - // Check for user in in-memory hashtable. To be a "valid, previously authenticated" user, - // 3 conditions must be met: - // - // 1) an entry for the userId must exist in the cache - // 2) the last usccessful authentication was < cachettl milliseconds ago - // 3) the one-way hash of the entered password must be equivalent to what is stored in the cache - // - // If these conditions are not, the authentication is performed via JAAS and, if sucessful, a new entry is created - - if (existingUser == null || (System.currentTimeMillis() - existingUser.getTimeStamp()) > m_cachettl - || !(existingUser.getHpw().equals(hpassword))) - { - if (M_log.isDebugEnabled()) M_log.debug("authenticateUser(): user " + userId + " not in table, querying Kerberos"); - - boolean authKerb = authenticateKerberos(userId, password); - - // if authentication succeeds, create entry for authenticated user in cache; - // otherwise, remove any entries for this user from cache - - if (authKerb) - { - if (M_log.isDebugEnabled()) - M_log.debug("authenticateUser(): putting authenticated user (" + userId + ") in table for caching"); - - UserData u = new UserData(); // create entry for authenticated user in cache - u.setId(userId); - u.setHpw(hpassword); - u.setTimeStamp(System.currentTimeMillis()); - users.put(userId, u); // put entry for authenticated user into cache - - } - else - { - users.remove(userId); - } - - authUser = authKerb; - - } - else - { - if (M_log.isDebugEnabled()) - M_log.debug("authenticateUser(): found authenticated user (" + existingUser.getId() + ") in table"); - authUser = true; - } - - return authUser; + JassAuthenticate jass = new JassAuthenticate(m_serviceprincipal, m_servicelogincontext, m_logincontext); + boolean authKerb = jass.attemptAuthentication(userId, password); + return authKerb; } catch (Exception e) { - if (M_log.isDebugEnabled()) M_log.debug("authenticateUser(): exception: " + e); + M_log.warn("authenticateUser(): exception: ", e); return false; } } // authenticateUser @@ -377,63 +331,6 @@ public class KerberosUserDirectoryProvider implements UserDirectoryProvider *********************************************************************************************************************************************************************************************************************************************************/ /** - * Authenticate the user id and pw with Kerberos. - * - * @param user - * The user id. - * @param password - * the user supplied password. - * @return true if successful, false if not. - */ - protected boolean authenticateKerberos(String user, String pw) - { - // assure some length to the password - if ((pw == null) || (pw.length() == 0)) return false; - - // Obtain a LoginContext, needed for authentication. Tell it - // to use the LoginModule implementation specified by the - // appropriate entry in the JAAS login configuration - // file and to also use the specified CallbackHandler. - LoginContext lc = null; - try - { - SakaiCallbackHandler t = new SakaiCallbackHandler(); - t.setId(user); - t.setPw(pw); - lc = new LoginContext(m_logincontext, t); - } - catch (LoginException le) - { - if (M_log.isDebugEnabled()) M_log.debug("authenticateKerberos(): " + le.toString()); - return false; - } - catch (SecurityException se) - { - if (M_log.isDebugEnabled()) M_log.debug("authenticateKerberos(): " + se.toString()); - return false; - } - - try - { - // attempt authentication - lc.login(); - lc.logout(); - - if (M_log.isDebugEnabled()) M_log.debug("authenticateKerberos(" + user + ", pw): Kerberos auth success"); - - return true; - } - catch (LoginException le) - { - if (M_log.isDebugEnabled()) - M_log.debug("authenticateKerberos(" + user + ", pw): Kerberos auth failed: " + le.toString()); - - return false; - } - - } // authenticateKerberos - - /** * Check if the user id is known to kerberos. * * @param user @@ -602,101 +499,5 @@ public class KerberosUserDirectoryProvider implements UserDirectoryProvider return false; } - /** - *

- * Helper class for storing user data in an in-memory cache - *

- */ - class UserData - { - - String id; - - String hpw; - - long timeStamp; - - /** - * @return Returns the id. - */ - public String getId() - { - return id; - } - - /** - * @param id - * The id to set. - */ - public void setId(String id) - { - this.id = id; - } - - /** - * @param hpw - * hashed pw to put in. - */ - public void setHpw(String hpw) - { - this.hpw = hpw; - } - - /** - * @return Returns the hashed password. - */ - - public String getHpw() - { - return hpw; - } - - /** - * @return Returns the timeStamp. - */ - public long getTimeStamp() - { - return timeStamp; - } - - /** - * @param timeStamp - * The timeStamp to set. - */ - public void setTimeStamp(long timeStamp) - { - this.timeStamp = timeStamp; - } - - } // UserData class - - /** - *

- * Hash string for storage in a cache using SHA - *

- * - * @param UTF-8 - * string - * @return encoded hash of string - */ - - private synchronized String encodeSHA(String plaintext) - { - - try - { - MessageDigest md = MessageDigest.getInstance("SHA"); - md.update(plaintext.getBytes("UTF-8")); - byte raw[] = md.digest(); - String hash = new String(Base64.encodeBase64(raw)); - return hash; - } - catch (Exception e) - { - M_log.warn("encodeSHA(): exception: " + e); - return null; - } - } // encodeSHA - } // KerberosUserDirectoryProvider diff --git a/kerberos/src/java/org/sakaiproject/component/kerberos/user/NullCallbackHandler.java b/kerberos/src/java/org/sakaiproject/component/kerberos/user/NullCallbackHandler.java new file mode 100644 index 0000000..6ee53e1 --- /dev/null +++ b/kerberos/src/java/org/sakaiproject/component/kerberos/user/NullCallbackHandler.java @@ -0,0 +1,25 @@ +package org.sakaiproject.component.kerberos.user; +import java.io.IOException; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; + + +/** + * Callback handler that doesn't support anything. This is used when the login is + * done using a keychain. + * @author buckett + * + */ +public class NullCallbackHandler implements CallbackHandler { + + public void handle(Callback[] callbacks) throws IOException, + UnsupportedCallbackException { + for (Callback callback: callbacks) { + throw new UnsupportedCallbackException(callback, "Can't handle any callbacks"); + } + + } + +} diff --git a/kerberos/src/java/org/sakaiproject/component/kerberos/user/UsernamePasswordCallback.java b/kerberos/src/java/org/sakaiproject/component/kerberos/user/UsernamePasswordCallback.java new file mode 100644 index 0000000..eee19f8 --- /dev/null +++ b/kerberos/src/java/org/sakaiproject/component/kerberos/user/UsernamePasswordCallback.java @@ -0,0 +1,40 @@ +package org.sakaiproject.component.kerberos.user; +import java.io.IOException; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; + +/** + * Simple callback handler that supplies a username and password. + * @author buckett + */ +public class UsernamePasswordCallback implements CallbackHandler { + + private final String username; + private final String password; + + public UsernamePasswordCallback(String username, String password) { + this.username = username; + this.password = password; + } + + public void handle(Callback[] callbacks) throws IOException, + UnsupportedCallbackException { + for (Callback callback: callbacks) { + if (callback instanceof NameCallback) { + NameCallback nameCallback = (NameCallback)callback; + nameCallback.setName(username); + } else if (callback instanceof PasswordCallback) { + PasswordCallback passwordCallback = (PasswordCallback)callback; + passwordCallback.setPassword(password.toCharArray()); + } else { + throw new UnsupportedCallbackException(callback, "Only username and password supported."); + } + } + + } + +} diff --git a/kerberos/src/test/org/sakaiproject/component/kerberos/user/SimpleJassAuthenticateTest.java b/kerberos/src/test/org/sakaiproject/component/kerberos/user/SimpleJassAuthenticateTest.java new file mode 100644 index 0000000..6d68a2f --- /dev/null +++ b/kerberos/src/test/org/sakaiproject/component/kerberos/user/SimpleJassAuthenticateTest.java @@ -0,0 +1,22 @@ +package org.sakaiproject.component.kerberos.user; + +import junit.framework.TestCase; + +public class SimpleJassAuthenticateTest extends TestCase { + + private JassAuthenticate jass; + + public void setUp() throws Exception { + super.setUp(); + jass = new JassAuthenticate("sakai@bit.oucs.ox.ac.uk", "servicePrincipal", "userPrincipal"); + } + + public void testGood() { + assertTrue(jass.attemptAuthentication("username", "password")); + } + + public void testBad() { + assertFalse(jass.attemptAuthentication("username", "wrong password")); + } + +} diff --git a/kerberos/src/test/org/sakaiproject/component/kerberos/user/ThreadedJaasAuthenticateTest.java b/kerberos/src/test/org/sakaiproject/component/kerberos/user/ThreadedJaasAuthenticateTest.java new file mode 100644 index 0000000..0bb2123 --- /dev/null +++ b/kerberos/src/test/org/sakaiproject/component/kerberos/user/ThreadedJaasAuthenticateTest.java @@ -0,0 +1,114 @@ +package org.sakaiproject.component.kerberos.user; + +import java.io.IOException; +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.Properties; +import java.util.Random; + +import org.omg.CORBA.portable.ApplicationException; +import org.omg.CORBA_2_3.portable.OutputStream; + +import com.sun.corba.se.impl.presentation.rmi.ExceptionHandler; + +import junit.framework.TestCase; + +public class ThreadedJaasAuthenticateTest extends TestCase { + + private int loopLimit = 1000; + private int threadCount = 10; + + private String goodUser; + private String goodPass; + + private String badUser; + private String badPass; + private String multipleUser; + private String multiplePass; + + public ThreadedJaasAuthenticateTest() { + Properties props = new Properties(); + try { + props.load(getClass().getResourceAsStream("/users.properties")); + } catch (IOException e) { + throw new IllegalStateException("Can't load users file.", e); + } + goodUser = props.getProperty("good.user"); + goodPass = props.getProperty("good.pass"); + badUser = props.getProperty("bad.user"); + badPass = props.getProperty("bad.pass"); + + multipleUser = props.getProperty("multiple.user"); + multiplePass = props.getProperty("multiple.pass"); + } + + + protected void setUp() throws Exception { + super.setUp(); + } + + public void testUserThread() throws InterruptedException { + Thread[] goodThreads = new Thread[threadCount]; + for (int i = 0; i < threadCount ; i++) { + String name = "Thread-"+ i+ "-good"; + goodThreads[i] = new Thread(new Authenticate(multipleUser+i, multiplePass, true), name); + goodThreads[i].start(); + System.out.println("Started "+ name); + } + Thread[] badThreads = new Thread[threadCount]; + for (int i = 0; i < threadCount ; i++) { + String name = "Thread-"+ i+ "-bad"; + badThreads[i] = new Thread(new Authenticate(badUser, badPass, false), name); + badThreads[i].start(); + System.out.println("Started "+ name); + } + + for (Thread thread: goodThreads) { + thread.join(); + } + for (Thread thread: badThreads) { + thread.join(); + } + } + + public void testThreads() throws InterruptedException { + Thread[] threads = new Thread[threadCount]; + Random rnd = new Random(); + for (int i = 0; i < threadCount ; i++) { + String name; + if (rnd.nextBoolean()) { + name = "Thread-"+ i+ "-good"; + threads[i] = new Thread(new Authenticate(goodUser, goodPass, true), name); + } else { + name = "Thread-"+ i+ "-bad"; + threads[i] = new Thread(new Authenticate(badUser, badPass, false), name); + } + threads[i].start(); + System.out.println("Started "+ name); + } + for (Thread thread: threads) { + thread.join(); + } + } + + private class Authenticate implements Runnable { + + String username; + String password; + boolean good; + + private Authenticate(String username, String password, boolean good) { + this.username = username; + this.password = password; + this.good = good; + } + + public void run() { + for(int i = 0; i< loopLimit; i++) { + JassAuthenticate jass = new JassAuthenticate("sakai@bit.oucs.ox.ac.uk", "servicePrincipal", "userPrincipal"); + assertEquals(good,jass.attemptAuthentication(username, password)); + } + } + + } + +} diff --git a/kerberos/src/test/sakai-jaas.conf b/kerberos/src/test/sakai-jaas.conf new file mode 100644 index 0000000..8a36c17 --- /dev/null +++ b/kerberos/src/test/sakai-jaas.conf @@ -0,0 +1,22 @@ +/* + * JAAS Login Configuration for Sakai + */ + +userPrincipal { + com.sun.security.auth.module.Krb5LoginModule required + storeKey="true" + useTicketCache="false"; +}; + +servicePrincipal { + com.sun.security.auth.module.Krb5LoginModule required + doNotPrompt="true" + principal="sakai/bit.oucs.ox.ac.uk" + useKeyTab="true" + keyTab="/Users/buckett/krb5-test.keytab" + storeKey="true" // Store the key inside the subject + // refreshKrb5Config="true" + isInitiator="false" // JDK 6 Only + useTicketCache="false"; +}; +