commit 38d6902abcd0b0df66b7f3fa21bc0dbaf3c9a184 from: leo date: Tue Feb 7 21:08:14 2006 UTC integrated Basic Auth and Certificate Auth commit - 5c3be72c5f44a87bd91169e87ddf128994522bde commit + 38d6902abcd0b0df66b7f3fa21bc0dbaf3c9a184 blob - ba49b47a3801f32259b868d3c0ba6ea0c217e3a8 blob + c5667afd129b86653c2e164d411632fba01fe241 --- src/org/snipsnap/container/DefaultSessionService.java +++ src/org/snipsnap/container/DefaultSessionService.java @@ -26,14 +26,18 @@ package org.snipsnap.container; import org.radeox.util.logging.Logger; +import snipsnap.api.storage.UserStorage; +import org.snipsnap.snip.HomePage; +import org.snipsnap.user.AuthenticationService; +import org.snipsnap.user.Digest; +import org.snipsnap.user.UserManager; +import org.snipsnap.util.Base64; +import org.snipsnap.util.X509NameTokenizer; import snipsnap.api.app.Application; import snipsnap.api.config.Configuration; -import snipsnap.api.snip.Snip; import snipsnap.api.snip.SnipSpace; -import org.snipsnap.snip.storage.UserStorage; -import org.snipsnap.user.AuthenticationService; -import org.snipsnap.user.Digest; import snipsnap.api.user.User; +import snipsnap.api.container.Components; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; @@ -43,6 +47,7 @@ import java.io.BufferedReader; import java.io.StringReader; import java.net.MalformedURLException; import java.net.URL; +import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -51,7 +56,9 @@ public class DefaultSessionService implements SessionS private final static String COOKIE_NAME = "SnipSnapUser"; private final static String ATT_USER = "user"; private final static int SECONDS_PER_YEAR = 60 * 60 * 24 * 365; + private final static int HTTP_UNAUTHORIZED = 401; + private Map authHash = new HashMap(); private Map robots = new HashMap(); private Map robotIds = new HashMap(); @@ -67,7 +74,7 @@ public class DefaultSessionService implements SessionS snipsnap.api.snip.Snip robots = space.load(snipsnap.api.config.Configuration.SNIPSNAP_CONFIG_ROBOTS); if (robots != null) { BufferedReader crawler = new BufferedReader(new StringReader(robots.getContent())); - String line = null; + String line; int ln = 0; while ((line = crawler.readLine()) != null) { ln++; @@ -121,28 +128,82 @@ public class DefaultSessionService implements SessionS public User getUser(HttpServletRequest request, HttpServletResponse response) { HttpSession session = request.getSession(); User user = (User) session.getAttribute(ATT_USER); - String appOid = (String)Application.get().getObject(Application.OID); + String appOid = (String) Application.get().getObject(Application.OID); if (null != user && !appOid.equals(user.getApplication())) { - user = null; + user = null; } + // TODO: refactor to several session service modules + // TODO: Idea: Basic, Digest or Certificate access and possible guest access (Cookie) if (null == user) { - Cookie cookie = getCookie(request, COOKIE_NAME); - if (cookie != null) { - String auth = cookie.getValue(); - if (!authHash.containsKey(auth)) { - updateAuthHash(); + if ("Cookie".equals(Application.get().getConfiguration().getAuth())) { + Cookie cookie = getCookie(request, COOKIE_NAME); + if (cookie != null) { + String auth = cookie.getValue(); + if (!authHash.containsKey(auth)) { + updateAuthHash(); + } + + user = (User) authHash.get(auth); + if (user != null && appOid.equals(user.getApplication())) { + user = authService.authenticate(user.getLogin(), user.getPasswd(), AuthenticationService.ENCRYPTED); + if (null != user) { + setCookie(request, response, user); + } + } else { + Logger.warn("SessionService: invalid hash: " + auth); + user = null; + } } + } else if ("Basic".equals(Application.get().getConfiguration().getAuth())) { + // make sure the user is authorized + String auth = request.getHeader("Authorization"); + String login = "", password = ""; - user = (User) authHash.get(auth); - if (user != null && appOid.equals(user.getApplication())) { - user = authService.authenticate(user.getLogin(), user.getPasswd(), AuthenticationService.ENCRYPTED); - if(null != user) { - setCookie(request, response, user); + if (auth != null) { + auth = new String(Base64.decode(auth.substring(auth.indexOf(' ') + 1))); + login = auth.substring(0, auth.indexOf(':')); + password = auth.substring(auth.indexOf(':') + 1); + } + + user = authService.authenticate(login, password); + if (user == null) { + response.setHeader("WWW-Authenticate", "Basic realm=\"SnipSnap\""); + response.setStatus(HTTP_UNAUTHORIZED); + return null; + } + } else if ("Certificate".equals(Application.get().getConfiguration().getAuth())) { + // Part for authenticating users with X509Certificates. If the user have a trusted client certificate + // he can get access to the server. Since the certificate is trusted already, by java/jsse, we don't + // have to verify it here. + // If the CA puts the users uid in the DN we can use that as login. + + // Check if we have a user in the certificate authentication + X509Certificate[] certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate"); + if (certs != null) { + X509Certificate clientCert = certs[0]; + if (clientCert != null) { + // Get the Distinguised Name for the user. + java.security.Principal userDN = clientCert.getSubjectDN(); + String dn = userDN.toString(); + // Get uid, which is the username we will use + String uid = getPartFromDN(dn, "UID"); + String email = getPartFromDN(dn, "emailAddress"); + // Create users home page if it does not exist + UserManager um = (UserManager) Components.getComponent(UserManager.class); + user = authService.authenticate(uid); + // create a user and home page for new logins + if(null == user) { + // set password to "*", if we switch back to Cookie auth service + // this is no problem as the users password is expected to be encrypted + // switching to Basic auth poses a security risk as it compares unencrypted + // passwords. + user = um.create(uid, "*", email); + Application.get().setUser(user, session); + HomePage.create(uid); + user = authService.authenticate(uid); + } } - } else { - Logger.warn("SessionService: invalid hash: " + auth); - user = null; } } @@ -221,9 +282,10 @@ public class DefaultSessionService implements SessionS /** * Helper method for getUser to extract user from request/cookie/session + * * @param request * @param name - * @return + * @return the cookie */ public Cookie getCookie(HttpServletRequest request, String name) { Cookie cookies[] = request.getCookies(); @@ -234,4 +296,32 @@ public class DefaultSessionService implements SessionS } return null; } + + /** + * Gets a specified part of a DN. Specifically the first occurrence it the DN contains several + * instances of a part (i.e. cn=x, cn=y returns x). + * + * @param dn String containing DN, The DN string has the format "C=SE, O=xx, OU=yy, CN=zz". + * @param dnpart String specifying which part of the DN to get, should be "CN" or "OU" etc. + * @return String containing dnpart or null if dnpart is not present + */ + private String getPartFromDN(String dn, String dnpart) { + String part = null; + if ((dn != null) && (dnpart != null)) { + String o; + dnpart += "="; // we search for 'CN=' etc. + X509NameTokenizer xt = new X509NameTokenizer(dn); + while (xt.hasMoreTokens()) { + o = xt.nextToken(); + if ((o.length() > dnpart.length()) && + o.substring(0, dnpart.length()).equalsIgnoreCase(dnpart)) { + part = o.substring(dnpart.length()); + break; + } + } + } + return part; + } //getPartFromDN + + } blob - /dev/null blob + ac73430ca7c1c228ba4172b321b1ad831dce8e6a (mode 644) --- /dev/null +++ src/org/snipsnap/util/X509NameTokenizer.java @@ -0,0 +1,92 @@ +package org.snipsnap.util; + +/** + * class for breaking up an X500 Name into it's component tokens, ala + * java.util.StringTokenizer. We need this class as some of the + * lightweight Java environment don't support classes like + * StringTokenizer. + * Class originally from the free JCE-provider bouncycastle.org, under BSD-like license. + */ +public class X509NameTokenizer +{ + private String value; + private int index; + private char seperator; + private StringBuffer buf = new StringBuffer(); + + public X509NameTokenizer( + String oid) + { + this(oid, ','); + } + + public X509NameTokenizer( + String oid, + char seperator) + { + this.value = oid; + this.index = -1; + this.seperator = seperator; + } + + public boolean hasMoreTokens() + { + return (index != value.length()); + } + + public String nextToken() + { + if (index == value.length()) + { + return null; + } + + int end = index + 1; + boolean quoted = false; + boolean escaped = false; + + buf.setLength(0); + + while (end != value.length()) + { + char c = value.charAt(end); + + if (c == '"') + { + if (!escaped) + { + quoted = !quoted; + } + else + { + buf.append(c); + } + escaped = false; + } + else + { + if (escaped || quoted) + { + buf.append(c); + escaped = false; + } + else if (c == '\\') + { + escaped = true; + } + else if (c == seperator) + { + break; + } + else + { + buf.append(c); + } + } + end++; + } + + index = end; + return buf.toString().trim(); + } +}