package org.apereo.cas.authorization;

import org.apereo.cas.configuration.support.Beans;
import org.ldaptive.ConnectionFactory;
import org.ldaptive.LdapAttribute;
import org.ldaptive.LdapEntry;
import org.ldaptive.LdapException;
import org.ldaptive.Response;
import org.ldaptive.SearchExecutor;
import org.ldaptive.SearchResult;
import org.pac4j.core.authorization.generator.AuthorizationGenerator;
import org.pac4j.core.profile.CommonProfile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;

/**
 * Provides a simple {@link AuthorizationGenerator} implementation that obtains user roles from an LDAP search.
 * Two searches are performed by this component for every user details lookup:
 * <ol>
 * <li>Search for an entry to resolve the username. In most cases the search should return exactly one result,
 * but the {@link #allowMultipleResults} property may be toggled to change that behavior.</li>
 * <li>Search for groups of which the user is a member. This search commonly occurs on a separate directory
 * branch than that of the user search.</li>
 * </ol>
 *
 * @author Jerome Leleu
 * @author Marvin S. Addison
 * @author Misagh Moayyed
 * @since 5.1.0
 */
public class LdapUserGroupsToRolesAuthorizationGenerator extends BaseUseAttributesAuthorizationGenerator {

    private static final Logger LOGGER = LoggerFactory.getLogger(LdapUserGroupsToRolesAuthorizationGenerator.class);

    private final String groupAttributeName;
    private final String groupPrefix;
    private final SearchExecutor groupSearchExecutor;

    /**
     * Instantiates a new Ldap user groups to roles authorization generator.
     *
     * @param factory              the factory
     * @param userSearchExecutor   the user search executor
     * @param allowMultipleResults the allow multiple results
     * @param groupAttributeName   the group attribute name
     * @param groupPrefix          the group prefix
     * @param groupSearchExecutor  the group search executor
     */
    public LdapUserGroupsToRolesAuthorizationGenerator(final ConnectionFactory factory,
                                                       final SearchExecutor userSearchExecutor,
                                                       final boolean allowMultipleResults,
                                                       final String groupAttributeName,
                                                       final String groupPrefix,
                                                       final SearchExecutor groupSearchExecutor) {
        super(factory, userSearchExecutor, allowMultipleResults);
        this.groupAttributeName = groupAttributeName;
        this.groupPrefix = groupPrefix;
        this.groupSearchExecutor = groupSearchExecutor;
    }

    @Override
    protected CommonProfile generateAuthorizationForLdapEntry(final CommonProfile profile, final LdapEntry userEntry) {
        try {
            LOGGER.debug("Attempting to get roles for user [{}].", userEntry.getDn());
            final Response<SearchResult> response = this.groupSearchExecutor.search(
                    this.connectionFactory,
                    Beans.newLdaptiveSearchFilter(this.groupSearchExecutor.getSearchFilter().getFilter(),
                            Beans.LDAP_SEARCH_FILTER_DEFAULT_PARAM_NAME, Arrays.asList(userEntry.getDn())));
            LOGGER.debug("LDAP role search response: [{}]", response);
            final SearchResult groupResult = response.getResult();

            for (final LdapEntry entry : groupResult.getEntries()) {
                final LdapAttribute groupAttribute = entry.getAttribute(this.groupAttributeName);
                if (groupAttribute == null) {
                    LOGGER.warn("Role attribute not found on entry [{}]", entry);
                    continue;
                }
                addProfileRolesFromAttributes(profile, groupAttribute, this.groupPrefix);
            }
        } catch (final LdapException e) {
            throw new RuntimeException("LDAP error fetching roles for user.", e);
        }
        return profile;
    }
}
