com.xwiki.authentication.trustedldap.TrustedLDAPAuthServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.xwiki.authentication.trustedldap.TrustedLDAPAuthServiceImpl.java

Source

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

package com.xwiki.authentication.trustedldap;

import java.io.UnsupportedEncodingException;
import java.security.Principal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.Cookie;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.securityfilter.realm.SimplePrincipal;

import com.novell.ldap.LDAPException;
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.XWikiLDAPException;
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.LDAPProfileXClass;
import com.xpn.xwiki.user.impl.LDAP.XWikiLDAPAuthServiceImpl;
import com.xpn.xwiki.web.XWikiRequest;

public class TrustedLDAPAuthServiceImpl extends XWikiLDAPAuthServiceImpl {
    /** LogFactory <code>LOGGER</code>. */
    private static final Log LOG = LogFactory.getLog(TrustedLDAPAuthServiceImpl.class);

    private TrustedLDAPConfig config = null;

    protected String encryptText(String text, XWikiContext context) {
        try {
            String secretKey = null;
            secretKey = context.getWiki().Param("xwiki.authentication.encryptionKey");
            secretKey = secretKey.substring(0, 24);

            if (secretKey != null) {
                SecretKeySpec key = new SecretKeySpec(secretKey.getBytes(), "TripleDES");
                Cipher cipher = Cipher.getInstance("TripleDES");
                cipher.init(Cipher.ENCRYPT_MODE, key);
                byte[] encrypted = cipher.doFinal(text.getBytes());
                String encoded = new String(Base64.encodeBase64(encrypted));
                return encoded.replaceAll("=", "_");
            } else {
                LOG.error("Encryption key not defined");
            }
        } catch (Exception e) {
            LOG.error("Failed to encrypt text", e);
        }

        return null;
    }

    protected String decryptText(String text, XWikiContext context) {
        try {
            String secretKey = null;
            secretKey = context.getWiki().Param("xwiki.authentication.encryptionKey");
            secretKey = secretKey.substring(0, 24);

            if (secretKey != null) {
                SecretKeySpec key = new SecretKeySpec(secretKey.getBytes(), "TripleDES");
                Cipher cipher = Cipher.getInstance("TripleDES");
                cipher.init(Cipher.DECRYPT_MODE, key);
                byte[] encrypted = Base64.decodeBase64(text.replaceAll("_", "=").getBytes("ISO-8859-1"));
                String decoded = new String(cipher.doFinal(encrypted));
                return decoded;
            } else {
                LOG.error("Encryption key not defined");
            }
        } catch (Exception e) {
            LOG.error("Failed to decrypt text", e);
        }

        return null;
    }

    public void setConfig(TrustedLDAPConfig config) {
        this.config = config;
    }

    public TrustedLDAPConfig getConfig() {
        if (this.config == null) {
            this.config = new TrustedLDAPConfig();
        }

        return this.config;
    }

    protected Cookie getCookie(String cookieName, XWikiContext context) {
        Cookie[] cookies = context.getRequest().getCookies();

        if (cookies == null) {
            return null;
        }

        for (Cookie cookie : cookies) {
            if (cookieName.equals(cookie.getName())) {
                return cookie;
            }
        }

        return null;
    }

    /**
     * {@inheritDoc}
     * 
     * @see com.xpn.xwiki.user.impl.xwiki.XWikiAuthServiceImpl#checkAuth(com.xpn.xwiki.XWikiContext)
     */
    public XWikiUser checkAuth(XWikiContext context) throws XWikiException {
        XWikiUser user = null;

        if (context.getRequest().getRemoteUser() != null) {
            user = checkAuthSSO(null, null, context);
        }

        if (user == null) {
            user = super.checkAuth(context);
        }

        LOG.debug("XWikiUser: " + user);

        return user;
    }

    /**
     * {@inheritDoc}
     * 
     * @see com.xpn.xwiki.user.impl.xwiki.XWikiAuthServiceImpl#checkAuth(java.lang.String, java.lang.String,
     *      java.lang.String, com.xpn.xwiki.XWikiContext)
     */
    public XWikiUser checkAuth(String username, String password, String rememberme, XWikiContext context)
            throws XWikiException {
        XWikiUser user = null;

        if (context.getRequest().getRemoteUser() != null) {
            user = checkAuthSSO(username, password, context);
        }

        if (user == null) {
            LOG.debug("Fallback on standard LDAP authenticator");

            user = super.checkAuth(username, password, rememberme, context);
        }

        LOG.debug("XWikiUser: " + user);

        return user;
    }

    public XWikiUser checkAuthSSO(String username, String password, XWikiContext context) throws XWikiException {
        Cookie cookie;

        LOG.debug("checkAuth");

        LOG.debug("Action: " + context.getAction());
        if (context.getAction().startsWith("logout")) {
            cookie = getCookie("XWIKISSOAUTHINFO", context);
            if (cookie != null) {
                cookie.setMaxAge(0);
                context.getResponse().addCookie(cookie);
            }

            return null;
        }

        Principal principal = null;

        if (LOG.isDebugEnabled()) {
            Cookie[] cookies = context.getRequest().getCookies();
            if (cookies != null) {
                for (Cookie c : cookies) {
                    LOG.debug("CookieList: " + c.getName() + " => " + c.getValue());
                }
            }
        }

        cookie = getCookie("XWIKISSOAUTHINFO", context);
        if (cookie != null) {
            LOG.debug("Found Cookie");
            String uname = decryptText(cookie.getValue(), context);
            if (uname != null) {
                principal = new SimplePrincipal(uname);
            }
        }

        XWikiUser user;

        // Authenticate
        if (principal == null) {
            principal = authenticate(username, password, context);
            if (principal == null) {
                return null;
            }

            LOG.debug("Saving auth cookie");
            String encuname = encryptText(principal.getName().contains(":") ? principal.getName()
                    : context.getDatabase() + ":" + principal.getName(), context);
            Cookie usernameCookie = new Cookie("XWIKISSOAUTHINFO", encuname);
            usernameCookie.setMaxAge(-1);
            usernameCookie.setPath("/");
            context.getResponse().addCookie(usernameCookie);

            user = new XWikiUser(principal.getName());
        } else {
            user = new XWikiUser(principal.getName().startsWith(context.getDatabase())
                    ? principal.getName().substring(context.getDatabase().length() + 1)
                    : principal.getName());
        }

        LOG.debug("XWikiUser=" + user);

        return user;
    }

    public Principal authenticate(String login, String password, XWikiContext context) throws XWikiException {
        Principal principal = null;

        String wikiName = context.getDatabase();

        // SSO authentication
        try {
            context.setDatabase(context.getMainXWiki());

            principal = authenticateSSOInContext(login, password, wikiName.equals(context.getMainXWiki()), context);
        } catch (Exception e) {
            LOG.debug("Failed to authenticate with SSO", e);
        } finally {
            context.setDatabase(wikiName);
        }

        // Falback on LDAP authenticator
        if (principal == null) {
            principal = super.authenticate(login, password, context);
        }

        return principal;
    }

    protected Map<String, String> parseRemoteUser(String ssoRemoteUser, XWikiContext context) {
        Map<String, String> ldapConfiguration = new HashMap<String, String>();

        ldapConfiguration.put("login", ssoRemoteUser);

        Pattern remoteUserParser = getConfig().getRemoteUserParser(context);

        LOG.debug("remoteUserParser: " + remoteUserParser);

        if (remoteUserParser != null) {
            Matcher marcher = remoteUserParser.matcher(ssoRemoteUser);

            if (marcher.find()) {
                int groupCount = marcher.groupCount();
                if (groupCount == 0) {
                    ldapConfiguration.put("login", marcher.group());
                } else {
                    for (int g = 1; g <= groupCount; ++g) {
                        String groupValue = marcher.group(g);

                        List<String> remoteUserMapping = getConfig().getRemoteUserMapping(g, context);

                        for (String configName : remoteUserMapping) {
                            ldapConfiguration.put(configName,
                                    convertRemoteUserMapping(configName, groupValue, context));
                        }
                    }
                }
            }
        }

        return ldapConfiguration;
    }

    private String convertRemoteUserMapping(String propertyName, String propertyValue, XWikiContext context) {
        Map<String, String> hostConvertor = getConfig().getRemoteUserMapping(propertyName, true, context);
        String converted = hostConvertor.get(propertyValue.toLowerCase());

        return converted != null ? converted : propertyValue;
    }

    public boolean open(XWikiLDAPConnection connector, Map<String, String> remoteUserLdapConfiguration,
            XWikiContext context) throws XWikiLDAPException {
        XWikiLDAPConfig config = XWikiLDAPConfig.getInstance();

        // open LDAP
        int ldapPort = config.getLDAPPort(context);
        String ldapHost = remoteUserLdapConfiguration.get("ldap_server");
        if (ldapHost == null) {
            ldapHost = config.getLDAPParam("ldap_server", "localhost", context);
        }

        String ldapUserName = remoteUserLdapConfiguration.get("login");
        String password = remoteUserLdapConfiguration.get("password");

        // allow to use the given user and password also as the LDAP bind user and password
        String bindDN = config.getLDAPBindDN(ldapUserName, password, context);
        String bindPassword = config.getLDAPBindPassword(ldapUserName, password, context);

        boolean bind;
        if ("1".equals(config.getLDAPParam("ldap_ssl", "0", context))) {
            String keyStore = config.getLDAPParam("ldap_ssl.keystore", "", context);

            if (LOG.isDebugEnabled()) {
                LOG.debug("Connecting to LDAP using SSL");
            }

            bind = connector.open(ldapHost, ldapPort, bindDN, bindPassword, keyStore, true, context);
        } else {
            bind = connector.open(ldapHost, ldapPort, bindDN, bindPassword, null, false, context);
        }

        return bind;
    }

    public Principal authenticateSSOInContext(String login, String password, boolean local, XWikiContext context)
            throws XWikiException, UnsupportedEncodingException, LDAPException {
        LOG.debug("Authenticate SSO");

        XWikiRequest request = context.getRequest();

        boolean checkAuth = false;
        Principal principal = null;

        String ssoRemoteUser = request.getRemoteUser();

        if (ssoRemoteUser == null) {
            // try using provided user name/password if no SSO information is provided
            ssoRemoteUser = login;
            checkAuth = true;

            if (ssoRemoteUser == null) {
                LOG.warn(
                        "Failed to resolve remote user. It usually mean that no SSO information has been provided to XWiki.");

                return null;
            }
        }

        LOG.debug("request remote user: " + ssoRemoteUser);

        // ////////////////////////////////////////////////////////////////////
        // Extract LDAP informations from remote user
        // ////////////////////////////////////////////////////////////////////

        Map<String, String> remoteUserLdapConfiguration = parseRemoteUser(ssoRemoteUser, context);

        // provide form password
        if (!remoteUserLdapConfiguration.containsKey("password")) {
            remoteUserLdapConfiguration.put("password", password);
        }

        String ldapUid = remoteUserLdapConfiguration.get("login");
        String validXWikiUserName = ldapUid.replace(".", "");

        LOG.debug("ldapUid: " + ldapUid);
        LOG.debug("validXWikiUserName: " + validXWikiUserName);

        // ////////////////////////////////////////////////////////////////////
        // LDAP
        // ////////////////////////////////////////////////////////////////////

        XWikiLDAPConnection connector = new XWikiLDAPConnection();
        XWikiLDAPUtils ldapUtils = new XWikiLDAPUtils(connector);

        XWikiLDAPConfig config = XWikiLDAPConfig.getInstance();
        ldapUtils.setUidAttributeName(config.getLDAPParam(XWikiLDAPConfig.PREF_LDAP_UID, "cn", context));
        ldapUtils.setGroupClasses(config.getGroupClasses(context));
        ldapUtils.setGroupMemberFields(config.getGroupMemberFields(context));
        ldapUtils.setUserSearchFormatString(config.getLDAPParam("ldap_user_search_fmt", "({0}={1})", context));

        ldapUtils.setBaseDN(remoteUserLdapConfiguration.containsKey("ldap_base_DN")
                ? remoteUserLdapConfiguration.get("ldap_base_DN")
                : config.getLDAPParam("ldap_base_DN", "", context));

        // ////////////////////////////////////////////////////////////////////
        // bind to LDAP
        // ////////////////////////////////////////////////////////////////////

        if (!open(connector, remoteUserLdapConfiguration, context)) {
            throw new XWikiException(XWikiException.MODULE_XWIKI_USER, XWikiException.ERROR_XWIKI_USER_INIT,
                    "Bind to LDAP server failed.");
        }

        // ////////////////////////////////////////////////////////////////////
        // find XWiki user profile page
        // ////////////////////////////////////////////////////////////////////

        LDAPProfileXClass ldapProfileClass = new LDAPProfileXClass(context);

        XWikiDocument userProfile = getUserProfileByUid(validXWikiUserName, ldapUid, context);

        // get DN from existing XWiki user
        String ldapDn = ldapProfileClass.getDn(userProfile);

        if (LOG.isDebugEnabled() && ldapDn != null) {
            LOG.debug("Found user dn with the user object: " + ldapDn);
        }

        List<XWikiLDAPSearchAttribute> 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, 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 + "]");
        }

        // if using form user/password, validate it
        if (checkAuth) {
            if ("1".equals(config.getLDAPParam("ldap_validate_password", "0", context))) {
                String passwordField = config.getLDAPParam("ldap_password_field", "userPassword", context);
                if (!connector.checkPassword(ldapDn, password, passwordField)) {
                    LOG.debug("Password comparison failed, are you really sure you need validate_password ?"
                            + " If you don't enable it, it does not mean user credentials are not validated."
                            + " The goal of this property is to bypass standard LDAP bind"
                            + " which is usually bad unless you really know what you do.");

                    throw new XWikiException(XWikiException.MODULE_XWIKI_USER, XWikiException.ERROR_XWIKI_USER_INIT,
                            "LDAP authentication failed:" + " could not validate the password: wrong password for "
                                    + ldapDn);
                }
            } else {
                String bindDNFormat = config.getLDAPBindDN(context);
                String bindDN = config.getLDAPBindDN(ldapUid, password, context);

                if (bindDNFormat.equals(bindDN)) {
                    // Validate user credentials
                    connector.bind(ldapDn, password);

                    // Rebind admin user
                    connector.bind(bindDN, config.getLDAPBindPassword(ldapUid, password, context));
                }
            }
        }

        // ////////////////////////////////////////////////////////////////////
        // sync user
        // ////////////////////////////////////////////////////////////////////

        boolean isNewUser = userProfile.isNew();

        syncUser(userProfile, searchAttributes, ldapDn, ldapUid, ldapUtils, context);

        // from now on we can enter the application
        if (local) {
            principal = new SimplePrincipal(userProfile.getFullName());
        } else {
            principal = new SimplePrincipal(context.getDatabase() + ":" + userProfile.getFullName());
        }

        // ////////////////////////////////////////////////////////////////////
        // sync groups membership
        // ////////////////////////////////////////////////////////////////////

        try {
            syncGroupsMembership(userProfile.getFullName(), ldapDn, isNewUser, ldapUtils, context);
        } catch (XWikiException e) {
            LOG.error("Failed to synchronise user's groups membership", e);
        }

        LOG.debug("Principal=" + principal);

        return principal;
    }
}