[ 2102617 ] Sands Fish - X509Authentication fails to assign appropriate special groups

http://sourceforge.net/tracker/index.php?func=detail&aid=2102617&group_id=19984&atid=319984

git-svn-id: http://scm.dspace.org/svn/repo/branches/dspace-1_5_x@3114 9c30dcfa-912a-0410-8fc2-9e0234be79fd
This commit is contained in:
Mark Diggory
2008-09-09 20:37:55 +00:00
parent 1fe7487263
commit 3c3526419e

View File

@@ -66,39 +66,53 @@ import org.dspace.core.LogManager;
import org.dspace.eperson.EPerson; import org.dspace.eperson.EPerson;
/** /**
* Implicit authentication method that gets credentials from the X.509 * Implicit authentication method that gets credentials from the X.509 client
* client certificate supplied by the HTTPS client when connecting to * certificate supplied by the HTTPS client when connecting to this server. The
* this server. The email address in that certificate is taken as the * email address in that certificate is taken as the authenticated user name
* authenticated user name with no further checking, so be sure * with no further checking, so be sure your HTTP server (e.g. Tomcat) is
* your HTTP server (e.g. Tomcat) is configured correctly to accept only * configured correctly to accept only client certificates it can validate.
* client certificates it can validate.
* <p> * <p>
* See the <code>AuthenticationMethod</code> interface for more details. * See the <code>AuthenticationMethod</code> interface for more details.
* <p> * <p>
* <b>Configuration:</b><pre> * <b>Configuration:</b>
* authentication.x509.keystore.path = <em>path to Java keystore file</em> *
* authentication.x509.keystore.password = <em>password to access the keystore</em> * <pre>
* authentication.x509.ca.cert = <em>path to certificate file for CA whose client certs to accept.</em> * authentication.x509.keystore.path =
* authentication.x509.autoregister = <em>"true" if E-Person is created automatically for unknown new users.</em> * <em>
* path to Java keystore file
* </em>
* authentication.x509.keystore.password =
* <em>
* password to access the keystore
* </em>
* authentication.x509.ca.cert =
* <em>
* path to certificate file for CA whose client certs to accept.
* </em>
* authentication.x509.autoregister =
* <em>
* &quot;true&quot; if E-Person is created automatically for unknown new users.
* </em>
* </pre> * </pre>
*
* Only one of the "<code>keystore.path</code>" or "<code>ca.cert</code>" * Only one of the "<code>keystore.path</code>" or "<code>ca.cert</code>"
* options is required. If you supply a keystore, then all of the "trusted" * options is required. If you supply a keystore, then all of the "trusted"
* certificates in the keystore represent CAs whose client certificates will * certificates in the keystore represent CAs whose client certificates will be
* be accepted. The <code>ca.cert</code> option only allows a single CA to be named. * accepted. The <code>ca.cert</code> option only allows a single CA to be
* named.
* <p> * <p>
* You can configure <em>both</em> a keystore and a CA cert, and both will * You can configure <em>both</em> a keystore and a CA cert, and both will be
* be used. * used.
* <p> * <p>
* The <code>autoregister</code> configuration parameter determines what * The <code>autoregister</code> configuration parameter determines what the
* the <code>canSelfRegister()</code> method returns. It also allows an * <code>canSelfRegister()</code> method returns. It also allows an EPerson
* EPerson record to be created automatically when the presented * record to be created automatically when the presented certificate is
* certificate is acceptable but there is no corresponding EPerson. * acceptable but there is no corresponding EPerson.
* *
* @author Larry Stone * @author Larry Stone
* @version $Revision$ * @version $Revision$
*/ */
public class X509Authentication public class X509Authentication implements AuthenticationMethod
implements AuthenticationMethod
{ {
/** log4j category */ /** log4j category */
@@ -111,27 +125,31 @@ public class X509Authentication
private static KeyStore caCertKeyStore = null; private static KeyStore caCertKeyStore = null;
private static String loginPageTitle = null; private static String loginPageTitle = null;
private static String loginPageURL = null; private static String loginPageURL = null;
/** /**
* Initialization: * Initialization: Set caPublicKey and/or keystore. This loads the
* Set caPublicKey and/or keystore. This loads the information * information needed to check if a client cert presented is valid and
* needed to check if a client cert presented is valid and acceptable. * acceptable.
*/ */
static static
{ {
/* /*
* allow identification of alternative entry points * allow identification of alternative entry points for certificate
* for certificate authentication when * authentication when selected by the user rather than implicitly.
* selected by the user rather than implicitly.
*/ */
loginPageTitle = ConfigurationManager.getProperty("authentication.x509.chooser.title.key"); loginPageTitle = ConfigurationManager
loginPageURL = ConfigurationManager.getProperty("authentication.x509.chooser.uri"); .getProperty("authentication.x509.chooser.title.key");
loginPageURL = ConfigurationManager
String keystorePath = ConfigurationManager.getProperty("authentication.x509.keystore.path"); .getProperty("authentication.x509.chooser.uri");
String keystorePassword = ConfigurationManager.getProperty("authentication.x509.keystore.password");
String caCertPath = ConfigurationManager.getProperty("authentication.x509.ca.cert"); String keystorePath = ConfigurationManager
.getProperty("authentication.x509.keystore.path");
String keystorePassword = ConfigurationManager
.getProperty("authentication.x509.keystore.password");
String caCertPath = ConfigurationManager
.getProperty("authentication.x509.ca.cert");
// backward-compatible kludge // backward-compatible kludge
if (caCertPath == null) if (caCertPath == null)
@@ -143,7 +161,8 @@ public class X509Authentication
FileInputStream fis = null; FileInputStream fis = null;
if (keystorePassword == null) if (keystorePassword == null)
keystorePassword = ""; keystorePassword = "";
try { try
{
KeyStore ks = KeyStore.getInstance("JKS"); KeyStore ks = KeyStore.getInstance("JKS");
fis = new FileInputStream(keystorePath); fis = new FileInputStream(keystorePath);
ks.load(fis, keystorePassword.toCharArray()); ks.load(fis, keystorePassword.toCharArray());
@@ -151,18 +170,26 @@ public class X509Authentication
} }
catch (IOException e) catch (IOException e)
{ {
log.error("X509Authentication: Failed to load CA keystore, file="+ log
keystorePath+", error="+e.toString()); .error("X509Authentication: Failed to load CA keystore, file="
+ keystorePath + ", error=" + e.toString());
} }
catch (GeneralSecurityException e) catch (GeneralSecurityException e)
{ {
log.error("X509Authentication: Failed to extract CA keystore, file="+ log
keystorePath+", error="+e.toString()); .error("X509Authentication: Failed to extract CA keystore, file="
+ keystorePath + ", error=" + e.toString());
} }
finally finally
{ {
if (fis != null) if (fis != null)
try { fis.close(); } catch (IOException ioe) { } try
{
fis.close();
}
catch (IOException ioe)
{
}
} }
} }
@@ -176,38 +203,51 @@ public class X509Authentication
fis = new FileInputStream(caCertPath); fis = new FileInputStream(caCertPath);
is = new BufferedInputStream(fis); is = new BufferedInputStream(fis);
X509Certificate cert = (X509Certificate) CertificateFactory X509Certificate cert = (X509Certificate) CertificateFactory
.getInstance("X.509").generateCertificate(is); .getInstance("X.509").generateCertificate(is);
if (cert != null) if (cert != null)
caPublicKey = cert.getPublicKey(); caPublicKey = cert.getPublicKey();
} }
catch (IOException e) catch (IOException e)
{ {
log.error("X509Authentication: Failed to load CA cert, file="+ log.error("X509Authentication: Failed to load CA cert, file="
caCertPath+", error="+e.toString()); + caCertPath + ", error=" + e.toString());
} }
catch (CertificateException e) catch (CertificateException e)
{ {
log.error("X509Authentication: Failed to extract CA cert, file="+ log
caCertPath+", error="+e.toString()); .error("X509Authentication: Failed to extract CA cert, file="
+ caCertPath + ", error=" + e.toString());
} }
finally finally
{ {
if (is != null) if (is != null)
try { is.close(); } catch (IOException ioe) { } try
{
is.close();
}
catch (IOException ioe)
{
}
if (fis != null) if (fis != null)
try { fis.close(); } catch (IOException ioe) { } try
{
fis.close();
}
catch (IOException ioe)
{
}
} }
} }
} }
/** /**
* Return the email address from <code>certificate</code>, or null * Return the email address from <code>certificate</code>, or null if an
* if an email address cannot be found in the certificate. * email address cannot be found in the certificate.
* <p> * <p>
* Note that the certificate parsing has only been tested with certificates * Note that the certificate parsing has only been tested with certificates
* granted by the MIT Certification Authority, and may not work elsewhere. * granted by the MIT Certification Authority, and may not work elsewhere.
* *
* @param certificate - * @param certificate -
* An X509 certificate object * An X509 certificate object
* @return - The email address found in certificate, or null if an email * @return - The email address found in certificate, or null if an email
@@ -249,7 +289,7 @@ public class X509Authentication
/** /**
* Verify CERTIFICATE against KEY. Return true if and only if CERTIFICATE is * Verify CERTIFICATE against KEY. Return true if and only if CERTIFICATE is
* valid and can be verified against KEY. * valid and can be verified against KEY.
* *
* @param certificate - * @param certificate -
* An X509 certificate object * An X509 certificate object
* @param key - * @param key -
@@ -257,8 +297,7 @@ public class X509Authentication
* @return - True if CERTIFICATE is valid and can be verified against KEY, * @return - True if CERTIFICATE is valid and can be verified against KEY,
* false otherwise. * false otherwise.
*/ */
private static boolean isValid(Context context, private static boolean isValid(Context context, X509Certificate certificate)
X509Certificate certificate)
{ {
if (certificate == null) if (certificate == null)
return false; return false;
@@ -271,7 +310,8 @@ public class X509Authentication
catch (CertificateException e) catch (CertificateException e)
{ {
log.info(LogManager.getHeader(context, "authentication", log.info(LogManager.getHeader(context, "authentication",
"X.509 Certificate is EXPIRED or PREMATURE: "+e.toString())); "X.509 Certificate is EXPIRED or PREMATURE: "
+ e.toString()));
return false; return false;
} }
@@ -286,7 +326,8 @@ public class X509Authentication
catch (GeneralSecurityException e) catch (GeneralSecurityException e)
{ {
log.info(LogManager.getHeader(context, "authentication", log.info(LogManager.getHeader(context, "authentication",
"X.509 Certificate FAILED SIGNATURE check: "+e.toString())); "X.509 Certificate FAILED SIGNATURE check: "
+ e.toString()));
} }
} }
@@ -299,11 +340,12 @@ public class X509Authentication
while (ke.hasMoreElements()) while (ke.hasMoreElements())
{ {
String alias = (String)ke.nextElement(); String alias = (String) ke.nextElement();
if (caCertKeyStore.isCertificateEntry(alias)) if (caCertKeyStore.isCertificateEntry(alias))
{ {
Certificate ca = caCertKeyStore.getCertificate(alias); Certificate ca = caCertKeyStore.getCertificate(alias);
try { try
{
certificate.verify(ca.getPublicKey()); certificate.verify(ca.getPublicKey());
return true; return true;
} }
@@ -312,13 +354,16 @@ public class X509Authentication
} }
} }
} }
log.info(LogManager.getHeader(context, "authentication", log
"Keystore method FAILED SIGNATURE check on client cert.")); .info(LogManager
.getHeader(context, "authentication",
"Keystore method FAILED SIGNATURE check on client cert."));
} }
catch (GeneralSecurityException e) catch (GeneralSecurityException e)
{ {
log.info(LogManager.getHeader(context, "authentication", log.info(LogManager.getHeader(context, "authentication",
"X.509 Certificate FAILED SIGNATURE check: "+e.toString())); "X.509 Certificate FAILED SIGNATURE check: "
+ e.toString()));
} }
} }
@@ -326,26 +371,23 @@ public class X509Authentication
} }
/** /**
* Predicate, can new user automatically create EPerson. * Predicate, can new user automatically create EPerson. Checks
* Checks configuration value. You'll probably want this to * configuration value. You'll probably want this to be true to take
* be true to take advantage of a Web certificate infrastructure * advantage of a Web certificate infrastructure with many more users than
* with many more users than are already known by DSpace. * are already known by DSpace.
*/ */
public boolean canSelfRegister(Context context, public boolean canSelfRegister(Context context, HttpServletRequest request,
HttpServletRequest request, String username) throws SQLException
String username)
throws SQLException
{ {
return ConfigurationManager return ConfigurationManager
.getBooleanProperty("authentication.x509.autoregister"); .getBooleanProperty("authentication.x509.autoregister");
} }
/** /**
* Nothing extra to initialize. * Nothing extra to initialize.
*/ */
public void initEPerson(Context context, HttpServletRequest request, public void initEPerson(Context context, HttpServletRequest request,
EPerson eperson) EPerson eperson) throws SQLException
throws SQLException
{ {
} }
@@ -353,9 +395,7 @@ public class X509Authentication
* We don't use EPerson password so there is no reason to change it. * We don't use EPerson password so there is no reason to change it.
*/ */
public boolean allowSetPassword(Context context, public boolean allowSetPassword(Context context,
HttpServletRequest request, HttpServletRequest request, String username) throws SQLException
String username)
throws SQLException
{ {
return false; return false;
} }
@@ -377,37 +417,33 @@ public class X509Authentication
} }
/** /**
* X509 certificate authentication. The client certificate * X509 certificate authentication. The client certificate is obtained from
* is obtained from the <code>ServletRequest</code> object. * the <code>ServletRequest</code> object.
* <ul> * <ul>
* <li>If the certificate is valid, and corresponds to an existing EPerson, * <li>If the certificate is valid, and corresponds to an existing EPerson,
* and the user is allowed to login, return success.</li> * and the user is allowed to login, return success.</li>
* <li>If the user is matched but is not allowed to login, it fails.</li> * <li>If the user is matched but is not allowed to login, it fails.</li>
* <li>If the certificate is valid, but there is no corresponding EPerson, * <li>If the certificate is valid, but there is no corresponding EPerson,
* the <code>"authentication.x509.autoregister"</code> * the <code>"authentication.x509.autoregister"</code> configuration
* configuration parameter is checked (via <code>canSelfRegister()</code>) * parameter is checked (via <code>canSelfRegister()</code>)
* <ul> * <ul>
* <li>If it's true, a new EPerson record is created for the certificate, and * <li>If it's true, a new EPerson record is created for the certificate,
* the result is success.</li> * and the result is success.</li>
* <li>If it's false, return that the user was unknown.</li> * <li>If it's false, return that the user was unknown.</li>
* </ul> * </ul>
* </li> * </li>
* </ul> * </ul>
* *
* @return One of: SUCCESS, BAD_CREDENTIALS, NO_SUCH_USER, BAD_ARGS * @return One of: SUCCESS, BAD_CREDENTIALS, NO_SUCH_USER, BAD_ARGS
*/ */
public int authenticate(Context context, public int authenticate(Context context, String username, String password,
String username, String realm, HttpServletRequest request) throws SQLException
String password,
String realm,
HttpServletRequest request)
throws SQLException
{ {
// Obtain the certificate from the request, if any // Obtain the certificate from the request, if any
X509Certificate[] certs = null; X509Certificate[] certs = null;
if (request != null) if (request != null)
certs = (X509Certificate[]) request certs = (X509Certificate[]) request
.getAttribute("javax.servlet.request.X509Certificate"); .getAttribute("javax.servlet.request.X509Certificate");
if ((certs == null) || (certs.length == 0)) if ((certs == null) || (certs.length == 0))
return BAD_ARGS; return BAD_ARGS;
@@ -418,8 +454,10 @@ public class X509Authentication
{ {
if (!isValid(context, certs[0])) if (!isValid(context, certs[0]))
{ {
log.warn(LogManager.getHeader(context, "authenticate", log
"type=x509certificate, status=BAD_CREDENTIALS (not valid)")); .warn(LogManager
.getHeader(context, "authenticate",
"type=x509certificate, status=BAD_CREDENTIALS (not valid)"));
return BAD_CREDENTIALS; return BAD_CREDENTIALS;
} }
@@ -431,19 +469,20 @@ public class X509Authentication
if (eperson == null) if (eperson == null)
{ {
// Cert is valid, but no record. // Cert is valid, but no record.
if (email != null && canSelfRegister(context, request, null)) if (email != null
&& canSelfRegister(context, request, null))
{ {
// Register the new user automatically // Register the new user automatically
log.info(LogManager.getHeader(context, log.info(LogManager.getHeader(context, "autoregister",
"autoregister", "from=x.509, email=" + email)); "from=x.509, email=" + email));
// TEMPORARILY turn off authorisation // TEMPORARILY turn off authorisation
context.setIgnoreAuthorization(true); context.setIgnoreAuthorization(true);
eperson = EPerson.create(context); eperson = EPerson.create(context);
eperson.setEmail(email); eperson.setEmail(email);
eperson.setCanLogIn(true); eperson.setCanLogIn(true);
AuthenticationManager.initEPerson(context, AuthenticationManager.initEPerson(context, request,
request, eperson); eperson);
eperson.update(); eperson.update();
context.commit(); context.commit();
context.setIgnoreAuthorization(false); context.setIgnoreAuthorization(false);
@@ -453,8 +492,10 @@ public class X509Authentication
else else
{ {
// No auto-registration for valid certs // No auto-registration for valid certs
log.warn(LogManager.getHeader(context, "authenticate", log
"type=cert_but_no_record, cannot auto-register")); .warn(LogManager
.getHeader(context, "authenticate",
"type=cert_but_no_record, cannot auto-register"));
return NO_SUCH_USER; return NO_SUCH_USER;
} }
} }
@@ -463,7 +504,8 @@ public class X509Authentication
else if (!eperson.canLogIn()) else if (!eperson.canLogIn())
{ {
log.warn(LogManager.getHeader(context, "authenticate", log.warn(LogManager.getHeader(context, "authenticate",
"type=x509certificate, email="+email+", canLogIn=false, rejecting.")); "type=x509certificate, email=" + email
+ ", canLogIn=false, rejecting."));
return BAD_ARGS; return BAD_ARGS;
} }
@@ -487,32 +529,32 @@ public class X509Authentication
/** /**
* Returns URL of password-login servlet. * Returns URL of password-login servlet.
* *
* @param context * @param context
* DSpace context, will be modified (EPerson set) upon success. * DSpace context, will be modified (EPerson set) upon success.
* *
* @param request * @param request
* The HTTP request that started this operation, or null if not applicable. * The HTTP request that started this operation, or null if not
* * applicable.
*
* @param response * @param response
* The HTTP response from the servlet method. * The HTTP response from the servlet method.
* *
* @return fully-qualified URL * @return fully-qualified URL
*/ */
public String loginPageURL(Context context, public String loginPageURL(Context context, HttpServletRequest request,
HttpServletRequest request, HttpServletResponse response)
HttpServletResponse response)
{ {
return loginPageURL; return loginPageURL;
} }
/** /**
* Returns message key for title of the "login" page, to use * Returns message key for title of the "login" page, to use in a menu
* in a menu showing the choice of multiple login methods. * showing the choice of multiple login methods.
* *
* @param context * @param context
* DSpace context, will be modified (EPerson set) upon success. * DSpace context, will be modified (EPerson set) upon success.
* *
* @return Message key to look up in i18n message catalog. * @return Message key to look up in i18n message catalog.
*/ */
public String loginPageTitle(Context context) public String loginPageTitle(Context context)