io.lavagna.service.Ldap.java Source code

Java tutorial

Introduction

Here is the source code for io.lavagna.service.Ldap.java

Source

/**
 * This file is part of lavagna.
 *
 * lavagna is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * lavagna 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with lavagna.  If not, see <http://www.gnu.org/licenses/>.
 */
package io.lavagna.service;

import io.lavagna.model.Key;
import io.lavagna.model.Pair;
import io.lavagna.service.LdapConnection.InitialDirContextCloseable;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Service;

import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static java.lang.String.format;
import static java.util.EnumSet.of;
import static java.util.Objects.requireNonNull;

@Service
public class Ldap {

    private static final Logger LOG = LogManager.getLogger();

    private final ConfigurationRepository configurationRepository;
    private final LdapConnection ldapConnection;

    public Ldap(ConfigurationRepository configurationRepository, LdapConnection ldapConnection) {
        this.configurationRepository = configurationRepository;
        this.ldapConnection = ldapConnection;
    }

    public boolean authenticate(String username, String password) {

        Map<Key, String> conf = configurationRepository
                .findConfigurationFor(of(Key.LDAP_SERVER_URL, Key.LDAP_MANAGER_DN, Key.LDAP_MANAGER_PASSWORD,
                        Key.LDAP_USER_SEARCH_BASE, Key.LDAP_USER_SEARCH_FILTER));

        String providerUrl = requireNonNull(conf.get(Key.LDAP_SERVER_URL));
        String ldapManagerDn = requireNonNull(conf.get(Key.LDAP_MANAGER_DN));
        String ldapManagerPwd = requireNonNull(conf.get(Key.LDAP_MANAGER_PASSWORD));
        String base = requireNonNull(conf.get(Key.LDAP_USER_SEARCH_BASE));
        String filter = requireNonNull(conf.get(Key.LDAP_USER_SEARCH_FILTER));
        //

        return authenticateWithParams(providerUrl, ldapManagerDn, ldapManagerPwd, base, filter, username, password)
                .getFirst();
    }

    public Pair<Boolean, List<String>> authenticateWithParams(String providerUrl, String ldapManagerDn,
            String ldapManagerPwd, String base, String filter, String username, String password) {
        requireNonNull(username);
        requireNonNull(password);
        List<String> msgs = new ArrayList<>();

        msgs.add(format("connecting to %s with managerDn %s", providerUrl, ldapManagerDn));
        try (InitialDirContextCloseable dctx = ldapConnection.context(providerUrl, ldapManagerDn, ldapManagerPwd)) {
            msgs.add(format("connected [ok]"));
            msgs.add(format("now searching user \"%s\" with base %s and filter %s", username, base, filter));

            SearchControls sc = new SearchControls();
            sc.setReturningAttributes(null);
            sc.setSearchScope(SearchControls.SUBTREE_SCOPE);

            List<SearchResult> srs = Ldap.search(dctx, base,
                    new MessageFormat(filter).format(new Object[] { Ldap.escapeLDAPSearchFilter(username) }), sc);
            if (srs.size() != 1) {
                String msg = format("error for username \"%s\" we have %d results instead of 1 [error]", username,
                        srs.size());
                msgs.add(msg);
                LOG.info(msg, username, srs.size());
                return Pair.Companion.of(false, msgs);
            }

            msgs.add("user found, now will connect with given password [ok]");

            SearchResult sr = srs.get(0);

            try (InitialDirContextCloseable uctx = ldapConnection.context(providerUrl, sr.getNameInNamespace(),
                    password)) {
                msgs.add("user authenticated, everything seems ok [ok]");
                return Pair.Companion.of(true, msgs);
            } catch (NamingException e) {
                String msg = format("error while checking with username \"%s\" with message: %s [error]", username,
                        e.getMessage());
                msgs.add(msg);
                LOG.info(msg, e);
                return Pair.Companion.of(false, msgs);
            }
        } catch (Throwable e) {
            String errMsg = format(
                    "error while opening the connection with message: %s [error], check the logs for a more complete trace",
                    e.getMessage());
            msgs.add(errMsg);
            msgs.add("Full stacktrace is:");
            msgs.add(ExceptionUtils.getStackTrace(e));
            LOG.error(errMsg, e);
            return Pair.Companion.of(false, msgs);
        }
    }

    private static List<SearchResult> search(DirContext dctx, String base, String filter, SearchControls sc)
            throws NamingException {
        List<SearchResult> res = new ArrayList<>();
        NamingEnumeration<SearchResult> search = dctx.search(base, filter, sc);
        while (search.hasMore()) {
            res.add(search.next());
        }
        return res;
    }

    // imported from
    // https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java .
    // Checked against spring implementation too...
    private static final String escapeLDAPSearchFilter(String filter) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < filter.length(); i++) {
            char curChar = filter.charAt(i);
            switch (curChar) {
            case '\\':
                sb.append("\\5c");
                break;
            case '*':
                sb.append("\\2a");
                break;
            case '(':
                sb.append("\\28");
                break;
            case ')':
                sb.append("\\29");
                break;
            case '\u0000':
                sb.append("\\00");
                break;
            default:
                sb.append(curChar);
            }
        }
        return sb.toString();
    }
}