SearchDirectoryServlet.java

/*
 * Copyright (C) 2020-2024 by Savoir-faire Linux
 *
 * This program 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.
 *
 * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
 */
package net.jami.jams.server.servlets.api.auth.directory;

import static net.jami.jams.server.Server.dataStore;
import static net.jami.jams.server.Server.nameServer;
import static net.jami.jams.server.Server.userAuthenticationModule;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;

import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import lombok.extern.slf4j.Slf4j;

import net.jami.datastore.main.DataStore;
import net.jami.jams.common.annotations.JsonContent;
import net.jami.jams.common.authentication.AuthenticationSource;
import net.jami.jams.common.authentication.AuthenticationSourceType;
import net.jami.jams.common.authmodule.AuthModuleKey;
import net.jami.jams.common.objects.user.AccessLevel;
import net.jami.jams.common.objects.user.Policy;
import net.jami.jams.common.objects.user.PolicyData;
import net.jami.jams.common.objects.user.User;
import net.jami.jams.common.objects.user.UserProfile;
import net.jami.jams.common.serialization.adapters.GsonFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@WebServlet("/api/auth/directory/search")
public class SearchDirectoryServlet extends HttpServlet {
    private final Gson gson = GsonFactory.createGson();

    // The search directory function does not automatically create users, this would
    // be costly at this point
    // right now, we will implement it when Jami supports lists of users. this is a
    // work in progress as it
    // requires changes on the name server as well.
    List<UserProfile> userProfiles = new ArrayList<>();

    @Override
    @JsonContent
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String pageParam = req.getParameter("page");
        Optional<Integer> page;
        if (pageParam == null) page = Optional.empty();
        else page = Optional.ofNullable(Integer.parseInt(pageParam));

        ConcurrentHashMap<AuthModuleKey, AuthenticationSource> authSources =
                new ConcurrentHashMap<>(userAuthenticationModule.getAuthSources());

        // Check if the actual user is allowed to lookup in the directory
        String username = req.getAttribute("username").toString();

        try {
            Policy policy = dataStore.getPolicyDao().getByUsername(username).orElseThrow();
            PolicyData policyData = gson.fromJson(policy.getPolicyData(), PolicyData.class);

            if (!policyData.getAllowLookup()) {
                resp.sendError(403, "Operation not allowed!");
                return;
            }
        } catch (Exception e1) {
            log.warn("No policy available for user - not adding a policy component to response");
        }

        if (authSources.size() > 1) {
            authSources.forEach(
                    (k, v) -> {
                        if (k.getType() == AuthenticationSourceType.LOCAL) authSources.remove(k);
                    });
        }

        String queryString = req.getParameter("queryString");

        authSources.forEach(
                (k, v) -> {
                    if (queryString.equals("*")) {
                        userProfiles = v.searchUserProfiles(queryString, "FULL_TEXT_NAME", page);
                    }
                    userProfiles = v.searchUserProfiles(queryString, "FULL_TEXT_NAME", page);
                    if (userProfiles.isEmpty()
                            && userProfiles.addAll(
                                    v.searchUserProfiles(queryString, "LOGON_NAME", page))) {
                        Set<UserProfile> s =
                                new TreeSet<UserProfile>(
                                        new Comparator<UserProfile>() {
                                            @Override
                                            public int compare(UserProfile o1, UserProfile o2) {
                                                if (o1.getUsername().equals(o2.getUsername()))
                                                    return 0;
                                                return 1;
                                            }
                                        });
                        s.addAll(userProfiles);
                        userProfiles = new ArrayList<>(s);
                    }
                    userProfiles.forEach(
                            profile -> {
                                List<User> results = new ArrayList<>();
                                while (results.isEmpty()) {
                                    results =
                                            dataStore
                                                    .getUserDao()
                                                    .getByUsername(profile.getUsername())
                                                    .map(user -> List.of(user))
                                                    .orElseGet(() -> List.of());
                                    if (results.isEmpty()) {
                                        User user = new User();
                                        user.setUsername(profile.getUsername());
                                        user.setRealm(k.getRealm());
                                        user.setUserType(k.getType());
                                        user.setAccessLevel(AccessLevel.USER);
                                        userAuthenticationModule.createUser(
                                                user.getUserType(),
                                                user.getRealm(),
                                                nameServer,
                                                user);
                                    } else {
                                        profile.setId(results.get(0).getJamiId());
                                    }
                                }
                            });
                });

        JsonObject obj = new JsonObject();
        JsonArray profilesArray = gson.toJsonTree(userProfiles).getAsJsonArray();

        obj.add("profiles", profilesArray);
        obj.addProperty("numPages", DataStore.NUM_PAGES);

        resp.getOutputStream().write(gson.toJson(obj).getBytes());
        resp.flushBuffer();
        resp.setStatus(200);
    }
}