All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apereo.cas.consent.LdapConsentRepository Maven / Gradle / Ivy

There is a newer version: 7.2.0-RC3
Show newest version
package org.apereo.cas.consent;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.authentication.Authentication;
import org.apereo.cas.authentication.principal.Service;
import org.apereo.cas.configuration.model.support.consent.ConsentProperties.Ldap;
import org.apereo.cas.services.RegisteredService;
import org.apereo.cas.util.CollectionUtils;
import org.apereo.cas.util.LdapUtils;
import org.ldaptive.ConnectionFactory;
import org.ldaptive.LdapAttribute;
import org.ldaptive.LdapEntry;
import org.ldaptive.LdapException;
import org.ldaptive.Response;
import org.ldaptive.SearchFilter;
import org.ldaptive.SearchResult;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * This is {@link LdapConsentRepository}.
 *
 * @author Arnold Bergner
 * @since 5.2.0
 */
@Slf4j
public class LdapConsentRepository implements ConsentRepository {
    private static final long serialVersionUID = 8561763114482490L;

    private static final ObjectMapper MAPPER = new ObjectMapper().findAndRegisterModules();

    private final transient ConnectionFactory connectionFactory;
    private final Ldap ldap;
    private final String searchFilter;

    public LdapConsentRepository(final ConnectionFactory connectionFactory, final Ldap ldap) {
        this.connectionFactory = connectionFactory;
        this.ldap = ldap;
        this.searchFilter = '(' + this.ldap.getSearchFilter() + ')';
    }

    @Override
    public ConsentDecision findConsentDecision(final Service service,
                                               final RegisteredService registeredService,
                                               final Authentication authentication) {
        final String principal = authentication.getPrincipal().getId();
        final LdapEntry entry = readConsentEntry(principal);
        if (entry != null) {
            final LdapAttribute consentDecisions = entry.getAttribute(this.ldap.getConsentAttributeName());
            if (consentDecisions != null) {
                final Collection values = consentDecisions.getStringValues();
                LOGGER.debug("Locating consent decision(s) for [{}] and service [{}]", principal, service.getId());
                return values
                    .stream()
                    .map(LdapConsentRepository::mapFromJson)
                    .filter(d -> d.getService().equals(service.getId()))
                    .findFirst()
                    .orElse(null);
            }
        }
        return null;
    }

    @Override
    public Collection findConsentDecisions(final String principal) {
        final LdapEntry entry = readConsentEntry(principal);
        if (entry != null) {
            final LdapAttribute consentDecisions = entry.getAttribute(this.ldap.getConsentAttributeName());
            if (consentDecisions != null) {
                LOGGER.debug("Located consent decision for [{}] at attribute [{}]", principal, this.ldap.getConsentAttributeName());
                return consentDecisions.getStringValues()
                    .stream()
                    .map(LdapConsentRepository::mapFromJson)
                    .collect(Collectors.toSet());
            }
        }
        return new HashSet<>(0);
    }

    @Override
    public Collection findConsentDecisions() {
        final Collection entries = readConsentEntries();
        if (entries != null && !entries.isEmpty()) {
            final Set decisions = new HashSet<>();
            entries
                .stream()
                .map(e -> e.getAttribute(this.ldap.getConsentAttributeName()))
                .filter(Objects::nonNull)
                .map(attr -> attr.getStringValues()
                    .stream()
                    .map(LdapConsentRepository::mapFromJson)
                    .collect(Collectors.toSet()))
                .forEach(decisions::addAll);
            return CollectionUtils.wrap(decisions);
        }
        LOGGER.debug("No consent decision could be found");
        return new HashSet<>(0);
    }

    @Override
    public boolean storeConsentDecision(final ConsentDecision decision) {
        final LdapEntry entry = readConsentEntry(decision.getPrincipal());
        if (entry != null) {
            final Set newConsent = mergeDecision(entry.getAttribute(this.ldap.getConsentAttributeName()), decision);
            return executeModifyOperation(newConsent, entry);
        }
        return false;
    }

    @Override
    public boolean deleteConsentDecision(final long id, final String principal) {
        LOGGER.debug("Deleting consent decision [{}] for principal [{}]", id, principal);
        final LdapEntry entry = readConsentEntry(principal);
        if (entry != null) {
            final Set newConsent = removeDecision(entry.getAttribute(this.ldap.getConsentAttributeName()), id);
            return executeModifyOperation(newConsent, entry);
        }
        return false;
    }

    /**
     * Modifies the consent decisions attribute on the entry.
     *
     * @param newConsent new set of consent decisions
     * @param entry      entry of consent decisions
     * @return true / false
     */
    private boolean executeModifyOperation(final Set newConsent, final LdapEntry entry) {
        final Map> attrMap = new HashMap<>();
        attrMap.put(this.ldap.getConsentAttributeName(), newConsent);

        LOGGER.debug("Storing consent decisions [{}] at LDAP attribute [{}] for [{}]", newConsent, attrMap.keySet(), entry.getDn());
        return LdapUtils.executeModifyOperation(entry.getDn(), this.connectionFactory, CollectionUtils.wrap(attrMap));
    }

    /**
     * Merges a new decision into existing decisions.
     * Decisions are matched by ID.
     *
     * @param ldapConsent existing consent decisions
     * @param decision    new decision
     * @return new decision set
     */
    private Set mergeDecision(final LdapAttribute ldapConsent, final ConsentDecision decision) {
        if (decision.getId() < 0) {
            decision.setId(System.currentTimeMillis());
        }

        if (ldapConsent != null) {
            final Set result = removeDecision(ldapConsent, decision.getId());
            final String json = mapToJson(decision);
            if (StringUtils.isBlank(json)) {
                throw new IllegalArgumentException("Could not map consent decision to JSON");
            }
            result.add(json);
            LOGGER.debug("Merged consent decision [{}] with LDAP attribute [{}]", decision, ldapConsent.getName());
            return CollectionUtils.wrap(result);
        }
        final Set result = new HashSet<>();
        final String json = mapToJson(decision);
        if (StringUtils.isBlank(json)) {
            throw new IllegalArgumentException("Could not map consent decision to JSON");
        }
        result.add(json);
        return result;
    }

    /**
     * Removes decision from ldap attribute set.
     *
     * @param ldapConsent the ldap attribute holding consent decisions
     * @param decisionId  the decision Id
     * @return the new decision set
     */
    private Set removeDecision(final LdapAttribute ldapConsent, final long decisionId) {
        final Set result = new HashSet<>();
        if (ldapConsent.size() != 0) {
            ldapConsent.getStringValues()
                .stream()
                .map(LdapConsentRepository::mapFromJson)
                .filter(d -> d.getId() != decisionId)
                .map(LdapConsentRepository::mapToJson)
                .filter(Objects::nonNull)
                .forEach(result::add);
        }
        return result;
    }

    /**
     * Fetches a user entry along with its consent attributes.
     *
     * @param principal user name
     * @return the user's LDAP entry
     */
    private LdapEntry readConsentEntry(final String principal) {
        try {
            final SearchFilter filter = LdapUtils.newLdaptiveSearchFilter(this.searchFilter, CollectionUtils.wrap(Arrays.asList(principal)));
            LOGGER.debug("Locating consent LDAP entry via filter [{}] based on attribute [{}]", filter, this.ldap.getConsentAttributeName());
            final Response response =
                LdapUtils.executeSearchOperation(this.connectionFactory, this.ldap.getBaseDn(), filter, this.ldap.getConsentAttributeName());
            if (LdapUtils.containsResultEntry(response)) {
                final LdapEntry entry = response.getResult().getEntry();
                LOGGER.debug("Locating consent LDAP entry [{}]", entry);
                return entry;
            }
        } catch (final LdapException e) {
            LOGGER.debug(e.getMessage(), e);
        }
        return null;
    }

    /**
     * Fetches all user entries that contain consent attributes along with these.
     *
     * @return the collection of user entries
     */
    private Collection readConsentEntries() {
        try {
            final String att = this.ldap.getConsentAttributeName();
            final SearchFilter filter = LdapUtils.newLdaptiveSearchFilter('(' + att + "=*)");

            LOGGER.debug("Locating consent LDAP entries via filter [{}] based on attribute [{}]", filter, att);
            final Response response = LdapUtils
                .executeSearchOperation(this.connectionFactory, this.ldap.getBaseDn(), filter, att);
            if (LdapUtils.containsResultEntry(response)) {

                final Collection results = response.getResult().getEntries();
                LOGGER.debug("Locating [{}] consent LDAP entries", results.size());
                return results;
            }
        } catch (final LdapException e) {
            LOGGER.debug(e.getMessage(), e);
        }
        return new HashSet<>(0);
    }

    private static ConsentDecision mapFromJson(final String json) {
        try {
            LOGGER.trace("Mapping JSON value [{}] to consent object", json);
            return MAPPER.readValue(json, ConsentDecision.class);
        } catch (final IOException e) {
            LOGGER.error(e.getMessage(), e);
        }
        return null;
    }

    private static String mapToJson(final ConsentDecision consent) {
        try {
            final String json = MAPPER.writeValueAsString(consent);
            LOGGER.trace("Transformed consent object [{}] as JSON value [{}]", consent, json);
            return json;
        } catch (final Exception e) {
            LOGGER.error(e.getMessage(), e);
        }
        return null;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy