package com.xpn.xwiki.user.impl.xwiki; import java.security.Principal; import java.text.MessageFormat; import java.util.List; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.securityfilter.realm.SimplePrincipal; import org.xwiki.cache.Cache; import org.xwiki.cache.CacheException; import org.xwiki.cache.CacheManager; import org.xwiki.cache.config.CacheConfiguration; import org.xwiki.cache.eviction.LRUEvictionConfiguration; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.plugin.ldap.XWikiLDAPConfig; import com.xpn.xwiki.plugin.ldap.XWikiLDAPConnection; import com.xpn.xwiki.plugin.ldap.XWikiLDAPSearchAttribute; import com.xpn.xwiki.plugin.ldap.XWikiLDAPUtils; import com.xpn.xwiki.user.api.XWikiUser; import com.xpn.xwiki.user.impl.LDAP.XWikiLDAPAuthServiceImpl; import com.xpn.xwiki.web.Utils; public class AppServerTrustedKerberosLDAPSyncAuthServiceImpl extends XWikiLDAPAuthServiceImpl { private static final Log LOG = LogFactory.getLog(AppServerTrustedKerberosLDAPSyncAuthServiceImpl.class); private static Cache recentlySynchronizedProfiles; private Cache getSynchronizedProfilesCache() throws CacheException { if (recentlySynchronizedProfiles == null) { // Create a new CacheConfiguration object CacheConfiguration cacheConfiguration = new CacheConfiguration(); // Set the configuration identifier. This can be used to overwrite this cache configuration based on // configuration file. // It can also be used for other things like clustering or filesystem cache depending of the implementation. cacheConfiguration.setConfigurationId("xwiki.auth.profileSynchronizationCache"); // Configure cache eviction policy LRUEvictionConfiguration lru = new LRUEvictionConfiguration(); // Set the maximum time to live without being used to 1 hour lru.setTimeToLive(3600); // Affect eviction cache configuration to cache configuration cacheConfiguration.put(LRUEvictionConfiguration.CONFIGURATIONID, lru); CacheManager cacheManager = Utils.getComponent(org.xwiki.cache.CacheManager.class); recentlySynchronizedProfiles = cacheManager.createNewCache(cacheConfiguration); } return recentlySynchronizedProfiles; } /** * @see com.xpn.xwiki.user.impl.xwiki.XWikiAuthServiceImpl#checkAuth(com.xpn.xwiki.XWikiContext) */ @Override public XWikiUser checkAuth(XWikiContext context) throws XWikiException { return checkAuth(null, null, null, context); } /** * @see com.xpn.xwiki.user.impl.xwiki.XWikiAuthServiceImpl#checkAuth(java.lang.String, java.lang.String, * java.lang.String, com.xpn.xwiki.XWikiContext) */ @Override public XWikiUser checkAuth(String username, String password, String rememberme, XWikiContext context) throws XWikiException { String principal = context.getRequest().getRemoteUser(); String user = principal; if ((user == null) || user.equals("")) { LOG.debug(MessageFormat.format("Remote user is blank : {0}", user)); return super.checkAuth(context); } else { if (user.contains("\\")) if (user.lastIndexOf("\\") + 1 < user.length()) user = user.substring(user.lastIndexOf("\\") + 1); else { user = ""; return super.checkAuth(context); } if (user.contains("@")) { user = StringUtils.substringBeforeLast(user, "@"); } LOG.debug(MessageFormat.format("Remote user is legitimate : {0}", user)); // Force this happening in Main Wiki String db = context.getDatabase(); try { context.setDatabase(context.getMainXWiki()); String createdUser = null; try { createdUser = getSynchronizedProfilesCache().get(user.toLowerCase()); } catch (CacheException e) { LOG.error("Failed to initialize the cache", e); } if (createdUser == null) { // Synchronize the user profile and group memberships with LDAP. LOG.debug("Starting LDAP synchronization of user : " + user); createdUser = syncLDAP(user, user, context); LOG.debug("Finished LDAP synchronization of user : " + user); try { // Mark profile as being synchronized in the cache getSynchronizedProfilesCache().set(user.toLowerCase(), createdUser); } catch (CacheException e) { // Don't log the initialization exception a second time, this has been done in previous call to // getSynchronizedProfilesCache } } else { LOG.debug("Skipping LDAP synchronization since it is in cache for user " + user); } user = "xwiki:XWiki." + createdUser; LOG.debug(MessageFormat.format("Authentication is now completed for user : {0}", user)); } finally { context.setDatabase(db); } } context.setUser(user); return new XWikiUser(user); } protected String syncLDAP(String ldapUid, String validXWikiUserName, XWikiContext context) throws XWikiException { XWikiLDAPConfig config = XWikiLDAPConfig.getInstance(); XWikiLDAPConnection connector = new XWikiLDAPConnection(); XWikiLDAPUtils ldapUtils = new XWikiLDAPUtils(connector); ldapUtils.setUidAttributeName(config.getLDAPParam(XWikiLDAPConfig.PREF_LDAP_UID, LDAP_DEFAULT_UID, context)); ldapUtils.setGroupClasses(config.getGroupClasses(context)); ldapUtils.setGroupMemberFields(config.getGroupMemberFields(context)); ldapUtils.setBaseDN(config.getLDAPParam("ldap_base_DN", "", context)); ldapUtils.setUserSearchFormatString(config.getLDAPParam("ldap_user_search_fmt", "({0}={1})", context)); String bindDNFormat = config.getLDAPBindDN(context); String password = config.getLDAPBindPassword("", "", context); if (!connector.open(bindDNFormat, password, context)) { throw new XWikiException(XWikiException.MODULE_XWIKI_USER, XWikiException.ERROR_XWIKI_USER_INIT, "Bind to LDAP server failed with creds {0} : {1}"); } try { connector.bind(bindDNFormat, config.getLDAPBindPassword("", "", context)); } catch (Exception e) { LOG.error("failed to bind", e); throw new XWikiException(XWikiException.MODULE_XWIKI_USER, XWikiException.ERROR_XWIKI_USER_INIT, "Failed to bind admin user with DN {0}", null, new Object[] {bindDNFormat}); } // //////////////////////////////////////////////////////////////////// // 3. find XWiki user profile page // //////////////////////////////////////////////////////////////////// XWikiDocument userProfile = ldapUtils.getUserProfileByUid(validXWikiUserName, ldapUid, context); // //////////////////////////////////////////////////////////////////// // 4. if group param, verify group membership (& get DN) // //////////////////////////////////////////////////////////////////// String ldapDn = null; String filterGroupDN = config.getLDAPParam("ldap_user_group", "", context); if (filterGroupDN.length() > 0) { if (LOG.isDebugEnabled()) { LOG.debug("Checking if the user belongs to the user group: " + filterGroupDN); } ldapDn = ldapUtils.isUidInGroup(ldapUid, filterGroupDN, context); if (ldapDn == null) { throw new XWikiException(XWikiException.MODULE_XWIKI_USER, XWikiException.ERROR_XWIKI_USER_INIT, "LDAP user {0} does not belong to LDAP group {1}.", null, new Object[] {ldapUid, filterGroupDN}); } } // //////////////////////////////////////////////////////////////////// // 5. if exclude group param, verify group membership // //////////////////////////////////////////////////////////////////// String excludeGroupDN = config.getLDAPParam("ldap_exclude_group", "", context); if (excludeGroupDN.length() > 0) { if (LOG.isDebugEnabled()) { LOG.debug("Checking if the user does not belongs to the exclude group: " + excludeGroupDN); } if (ldapUtils.isUidInGroup(ldapUid, excludeGroupDN, context) != null) { throw new XWikiException(XWikiException.MODULE_XWIKI_USER, XWikiException.ERROR_XWIKI_USER_INIT, "LDAP user {0} should not belong to LDAP group {1}.", null, new Object[] {ldapUid, filterGroupDN}); } } // //////////////////////////////////////////////////////////////////// // 6. if no dn search for user // //////////////////////////////////////////////////////////////////// List searchAttributes = null; // if we still don't have a dn, search for it. Also get the attributes, we might need // them if (ldapDn == null) { searchAttributes = ldapUtils.searchUserAttributesByUid(ldapUid, ldapUtils.getAttributeNameTable(context)); if (searchAttributes != null) { for (XWikiLDAPSearchAttribute searchAttribute : searchAttributes) { if ("dn".equals(searchAttribute.name)) { ldapDn = searchAttribute.value; break; } } } } if (ldapDn == null) { throw new XWikiException(XWikiException.MODULE_XWIKI_USER, XWikiException.ERROR_XWIKI_USER_INIT, "Can't find LDAP user DN for [" + ldapUid + "]"); } // String bindDNFormat = config.getLDAPBindDN(context); try { connector.bind(bindDNFormat, config.getLDAPBindPassword("", "", context)); } catch (Exception e) { LOG.error("failed to bind", e); throw new XWikiException(XWikiException.MODULE_XWIKI_USER, XWikiException.ERROR_XWIKI_USER_INIT, "Failed to bind admin user with DN {0}", null, new Object[] {bindDNFormat}); } boolean isNewUser = userProfile.isNew(); LOG.debug("Starting profile synchronization"); syncUser(userProfile, searchAttributes, ldapDn, ldapUid, ldapUtils, context); // //////////////////////////////////////////////////////////////////// // 9. sync groups membership // //////////////////////////////////////////////////////////////////// try { LOG.debug("Starting group membership synchronization"); syncGroupsMembership(userProfile.getFullName(), ldapDn, isNewUser, ldapUtils, context); } catch (XWikiException e) { LOG.error("Failed to synchronise user's groups membership", e); } return userProfile.getName(); } /** * Copy {@link XWikiAuthServiceImpl#authenticate(String, String, XWikiContext)} since we never want LDAP * authentication. This will only get called when bypassing the trusted app server (Apache), for example by xinit * when checking the wiki with a http request, It will do a normal XWiki DB authentication. */ @Override public Principal authenticate(String username, String password, XWikiContext context) throws XWikiException { /* * This method was returning null on failure so I preserved that behaviour, while adding the exact error * messages to the context given as argument. However, the right way to do this would probably be to throw * XWikiException-s. */ if (username == null) { // If we can't find the username field then we are probably on the login screen return null; } // Trim the username to allow users to enter their names with spaces before or after String cannonicalUsername = username.replaceAll(" ", ""); // Check for empty usernames if (cannonicalUsername.equals("")) { context.put("message", "nousername"); return null; } // Check for empty passwords if ((password == null) || (password.trim().equals(""))) { context.put("message", "nopassword"); return null; } // Check for superadmin if (isSuperAdmin(cannonicalUsername)) { return authenticateSuperAdmin(password, context); } // If we have the context then we are using direct mode, and we should be able to specify the database // This is needed for virtual mode to work if (context != null) { String susername = cannonicalUsername; String virtualXwikiName = null; int i = cannonicalUsername.indexOf("."); int j = cannonicalUsername.indexOf(":"); // Extract the specified wiki name, if it exists if (j > 0) { virtualXwikiName = cannonicalUsername.substring(0, j); } // Use just the username, without a wiki or space prefix if (i != -1) { susername = cannonicalUsername.substring(i + 1); } else if (j > 0) { // The username could be in the format xwiki:Username, so strip the wiki prefix. susername = cannonicalUsername.substring(j + 1); } String db = context.getDatabase(); try { // Set the context database to the specified wiki, if any if (virtualXwikiName != null) { context.setDatabase(virtualXwikiName); } // Check in the current database first try { String user = findUser(susername, context); if (user != null) { if (checkPassword(user, password, context)) { return new SimplePrincipal(virtualXwikiName != null ? context.getDatabase() + ":" + user : user); } else { context.put("message", "invalidcredentials"); } } else { context.put("message", "invalidcredentials"); } } catch (Exception e) { // continue } if (!context.isMainWiki()) { // Then we check in the main database context.setDatabase(context.getMainXWiki()); try { String user = findUser(susername, context); if (user != null) { if (checkPassword(user, password, context)) { return new SimplePrincipal(context.getDatabase() + ":" + user); } else { context.put("message", "invalidcredentials"); return null; } } else { context.put("message", "invalidcredentials"); return null; } } catch (Exception e) { context.put("message", "loginfailed"); return null; } } else { // error message was already set return null; } } finally { context.setDatabase(db); } } else { LOG.error("XWikiContext is null"); return null; } } }