/* * $Id$ * * Copyright 2009-2011 Glencoe Software, Inc. All rights reserved. * Use is subject to license terms supplied in LICENSE.txt */ package ome.security.auth; import java.security.Permissions; import ome.conditions.ApiUsageException; import ome.conditions.ValidationException; import ome.logic.LdapImpl; import ome.model.meta.Experimenter; import ome.security.SecuritySystem; import org.springframework.util.Assert; /** * LDAP {@link PasswordProvider} which can create users on * {@link #checkPassword(String, String, boolean) request} to synchronize with an LDAP * directory. Assuming that a user exists in the configured LDAP store but not * in the database, then a new user will be created. Authentication, however, * always takes place against LDAP, and changing passwords is not allowed. * * Note: deleted LDAP users will not be removed from OMERO, but will not be able * to login. * * @author Josh Moore, josh at glencoesoftware.com * @see SecuritySystem * @see Permissions * @since 4.0 */ public class LdapPasswordProvider extends ConfigurablePasswordProvider { final protected LdapImpl ldapUtil; public LdapPasswordProvider(PasswordUtil util, LdapImpl ldap) { super(util); Assert.notNull(ldap); this.ldapUtil = ldap; } public LdapPasswordProvider(PasswordUtil util, LdapImpl ldap, boolean ignoreUnknown) { super(util, ignoreUnknown); Assert.notNull(ldap); this.ldapUtil = ldap; } /** * Only returns if the user is already in the database and has a DN value in * the password table. Note: after a call to * {@link #checkPassword(String, String, boolean)} with this same user value, this * method might begin to return {@code true} due to a call to * {@link LdapImpl#createUser(String, String)}. */ @Override public boolean hasPassword(String user) { if (ldapUtil.getSetting()) { Long id = util.userId(user); if (id != null) { String dn = ldapUtil.lookupLdapAuthExperimenter(id); if (dn != null) { return true; } } } return false; } @Override public Boolean checkPassword(String user, String password, boolean readOnly) { if (!ldapUtil.getSetting()) { return null; // EARLY EXIT! } // Note: LDAP simple authentication defaults to anonymous // binding if the password is blank: // // 5.1.2. Unauthenticated Authentication Mechanism of Simple Bind // // An LDAP client may use the unauthenticated authentication mechanism // of the simple Bind method to establish an anonymous authorization // state by sending a Bind request with a name value (a distinguished // name in LDAP string form [RFC4514] of non-zero length) and specifying // the simple authentication choice containing a password value of zero // length. // // Since an anonymous bind proves nothing about the validity of this // user, we disable all attempts to login with an empty password. // // The same check takes place in LdapImpl.isAuthContext method. // if (password == null || password.equals("")) { log.warn("Empty password for user: " + user); loginAttempt(user, false); return false; } Long id = util.userId(user); // Unknown user. First try to create. if (null == id) { try { if (readOnly == true) { throw new IllegalStateException("Cannot create user!"); } Experimenter experimenter = ldapUtil.createUser(user, password); // Use default logic if the user creation did not exist, // because there may be another non-database login mechanism // which should also be given a chance. if (experimenter != null) { loginAttempt(user, true); return true; } } catch (ApiUsageException e) { log.info(String.format( "Default choice on create user: %s (%s)", user, e)); } } // Known user, preventing special users by checking for a null dn // in which case we ignore any information from LDAP. // See ticket:6702 final String dn1 = (id == null) ? null : getOmeroDN(id); if (dn1 != null) { // If LDAP doesn't return a DN for a user that expects one // then assume that they've been locked out. ticket:6248 final String dn2 = getLdapDN(user); if (dn2 == null) { log.info(String.format( "User not found in LDAP: {username=%s, dn=%s}", user, dn1)); return loginAttempt(user, false); } else if (!dn1.equals(dn2)) { String msg = String.format("DNs don't match: '%s' and '%s'", dn1, dn2); log.warn(msg); loginAttempt(user, false); // Throwing an exception so that the permissions verifier // will state an "InternalException: Please contact your admin" // We will need to find another way to handle this. // Perhaps a hard-coded value in "password"."dn" throw new ValidationException(msg); } else { ldapUtil.synchronizeLdapUser(user); return loginAttempt(user, ldapUtil.validatePassword(dn1, password)); } } // If anything goes wrong or no LDAP is found in OMERO, // then use the default (configurable) logic, which will // probably return null in order to check JDBC for the password. return super.checkPassword(user, password, readOnly); } private String getOmeroDN(long id) { try { String dn = ldapUtil.lookupLdapAuthExperimenter(id); if (log.isDebugEnabled()) { log.debug(String.format("lookupLdap(%s)=%s", id, dn)); } return dn; } catch (ApiUsageException e) { if (log.isDebugEnabled()) { log.debug(String.format("lookupLdap(%s) is empty", id)); } return null; } } private String getLdapDN(String user) { try { String dn = ldapUtil.findDN(user); if (log.isDebugEnabled()) { log.debug(String.format("findDN(%s)=%s", user, dn)); } return dn; } catch (ApiUsageException e) { if (log.isDebugEnabled()) { log.debug(String.format("findDN(%s) is empty", user)); } return null; } } }