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.1.0
Show newest version
package org.apereo.cas.consent;

import org.apereo.cas.authentication.Authentication;
import org.apereo.cas.authentication.principal.Service;
import org.apereo.cas.configuration.model.support.consent.LdapConsentProperties;
import org.apereo.cas.services.RegisteredService;
import org.apereo.cas.util.CollectionUtils;
import org.apereo.cas.util.LdapConnectionFactory;
import org.apereo.cas.util.LdapUtils;
import org.apereo.cas.util.LoggingUtils;
import org.apereo.cas.util.function.FunctionUtils;
import org.apereo.cas.util.serialization.JacksonObjectMapperFactory;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.hjson.JsonValue;
import org.jooq.lambda.Unchecked;
import org.ldaptive.LdapAttribute;
import org.ldaptive.LdapEntry;
import org.springframework.beans.factory.DisposableBean;

import java.io.Serial;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

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

    private static final ObjectMapper MAPPER = JacksonObjectMapperFactory.builder()
        .defaultTypingEnabled(false).build().toObjectMapper();

    private final LdapConnectionFactory connectionFactory;

    private final LdapConsentProperties ldapProperties;

    private static ConsentDecision mapFromJson(final String json) {
        return FunctionUtils.doAndHandle(() -> {
            LOGGER.trace("Mapping JSON value [{}] to consent object", json);
            return MAPPER.readValue(JsonValue.readHjson(json).toString(), ConsentDecision.class);
        }, throwable -> null).get();
    }

    private static String mapToJson(final ConsentDecision consent) throws Exception {
        val json = MAPPER.writeValueAsString(consent);
        LOGGER.trace("Transformed consent object [{}] as JSON value [{}]", consent, json);
        return json;
    }

    private static Set mergeDecision(final LdapAttribute ldapConsent,
                                             final ConsentDecision decision) throws Exception {
        if (decision.getId() < 0) {
            decision.setId(System.currentTimeMillis());
        }

        if (ldapConsent != null) {
            val result = removeDecision(ldapConsent, decision.getId());
            val json = mapToJson(decision);
            result.add(json);
            LOGGER.debug("Merged consent decision [{}] with LDAP attribute [{}]", decision, ldapConsent.getName());
            return CollectionUtils.wrap(result);
        }
        val json = mapToJson(decision);
        val result = new HashSet(1);
        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 static Set removeDecision(final LdapAttribute ldapConsent, final long decisionId) {
        return removeDecisions(ldapConsent, d -> d.getId() != decisionId);
    }

    private static Set removeDecisions(final LdapAttribute ldapConsent, final Predicate filter) {
        if (ldapConsent.size() != 0) {
            return ldapConsent.getStringValues()
                .stream()
                .map(LdapConsentRepository::mapFromJson)
                .filter(Objects::nonNull)
                .filter(filter)
                .map(Unchecked.function(LdapConsentRepository::mapToJson))
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        }
        return new HashSet<>(0);
    }

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

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

    @Override
    public Collection findConsentDecisions() {
        val entries = readConsentEntries();
        if (!entries.isEmpty()) {
            return entries
                .stream()
                .map(e -> e.getAttribute(this.ldapProperties.getConsentAttributeName()))
                .filter(Objects::nonNull)
                .map(attr -> attr.getStringValues()
                    .stream()
                    .map(LdapConsentRepository::mapFromJson)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toSet()))
                .flatMap(Set::stream)
                .collect(Collectors.toList());
        }
        LOGGER.debug("No consent decision could be found");
        return new HashSet<>(0);
    }

    @Override
    public ConsentDecision storeConsentDecision(final ConsentDecision decision) throws Exception {
        LOGGER.debug("Storing consent decision [{}]", decision);
        val entry = readConsentEntry(decision.getPrincipal());
        if (entry != null) {
            val newConsent = mergeDecision(entry.getAttribute(ldapProperties.getConsentAttributeName()), decision);
            if (executeModifyOperation(newConsent, entry)) {
                return decision;
            }
        }
        LOGGER.debug("Unable to read consent entry for [{}]. Consent decision is not stored", decision.getPrincipal());
        return null;
    }

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

    @Override
    public void deleteAll() {
        val entries = readConsentEntries();
        if (!entries.isEmpty()) {
            entries.forEach(entry -> executeModifyOperation(Set.of(), entry));
        }
    }

    @Override
    public boolean deleteConsentDecisions(final String principal) {
        LOGGER.debug("Deleting consent decisions for principal [{}]", principal);
        val entry = readConsentEntry(principal);
        return entry != null && executeModifyOperation(new HashSet<>(), entry);
    }

    @Override
    public void destroy() {
        connectionFactory.close();
    }

    /**
     * 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) {
        val attrMap = new HashMap>();
        attrMap.put(this.ldapProperties.getConsentAttributeName(), newConsent);

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

    /**
     * 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 {
            val searchFilter = '(' + ldapProperties.getSearchFilter() + ')';
            val filter = LdapUtils.newLdaptiveSearchFilter(searchFilter, CollectionUtils.wrapList(principal));
            LOGGER.debug("Locating consent LDAP entry via filter [{}] based on attribute [{}]", filter, ldapProperties.getConsentAttributeName());
            val response = connectionFactory.executeSearchOperation(ldapProperties.getBaseDn(),
                filter, ldapProperties.getPageSize(), ldapProperties.getConsentAttributeName());
            if (LdapUtils.containsResultEntry(response)) {
                val entry = response.getEntry();
                LOGGER.debug("Locating consent LDAP entry [{}]", entry);
                return entry;
            }
        } catch (final Exception e) {
            LoggingUtils.error(LOGGER, e);
        }
        return null;
    }

    /**
     * Fetches all user entries that contain consent attributes along with these.
     *
     * @return the collection of user entries
     */
    private Collection readConsentEntries() {
        val att = ldapProperties.getConsentAttributeName();
        try {
            val filter = LdapUtils.newLdaptiveSearchFilter('(' + att + "=*)");
            LOGGER.debug("Locating consent LDAP entries via filter [{}] based on attribute [{}]", filter, att);
            val response = connectionFactory.executeSearchOperation(ldapProperties.getBaseDn(),
                filter, ldapProperties.getPageSize(), att);
            if (LdapUtils.containsResultEntry(response)) {
                val results = response.getEntries();
                LOGGER.debug("Locating [{}] consent LDAP entries based on response [{}]", results.size(), response);
                return results;
            }
        } catch (final Exception e) {
            LoggingUtils.error(LOGGER, e);
        }
        LOGGER.debug("Unable to read consent entries from LDAP for attribute [{}]", att);
        return new HashSet<>(0);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy