001/*
002 *  Copyright 2025 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.plugins.newsletter.subscribe;
017
018import java.time.Period;
019import java.time.ZonedDateTime;
020import java.util.Arrays;
021import java.util.Map;
022import java.util.Set;
023
024import org.apache.avalon.framework.service.ServiceException;
025import org.apache.avalon.framework.service.ServiceManager;
026import org.apache.avalon.framework.service.Serviceable;
027import org.apache.commons.lang3.StringUtils;
028
029import org.ametys.core.trace.ForensicLogger;
030import org.ametys.core.user.UserManager;
031import org.ametys.core.user.directory.NotUniqueUserException;
032import org.ametys.core.user.population.PopulationContextHelper;
033import org.ametys.core.user.population.UserPopulationDAO;
034import org.ametys.core.user.status.PersonalDataPolicy;
035import org.ametys.core.user.status.UserStatusInfo;
036import org.ametys.plugins.newsletter.daos.SubscribersDAO;
037import org.ametys.plugins.newsletter.daos.SubscribersDAO.UnsubscribeOrigin;
038import org.ametys.plugins.repository.AmetysObjectIterable;
039import org.ametys.runtime.config.Config;
040import org.ametys.web.repository.site.Site;
041import org.ametys.web.repository.site.SiteManager;
042
043/**
044 * Personal data policy removing subscription of missing user
045 */
046public class SubscriberDataPolicy implements PersonalDataPolicy, Serviceable
047{
048    /** The population context helper */
049    protected PopulationContextHelper _populationContextHelper;
050    /** The site DAO */
051    protected SiteManager _siteManager;
052    /** The subscriber DAO */
053    protected SubscribersDAO _subscribersDao;
054    /** The user manager */
055    protected UserManager _userManager;
056    private Period _retentionPeriod;
057    
058    public void service(ServiceManager manager) throws ServiceException
059    {
060        _populationContextHelper = (PopulationContextHelper) manager.lookup(PopulationContextHelper.ROLE);
061        _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE);
062        _subscribersDao = (SubscribersDAO) manager.lookup(SubscribersDAO.ROLE);
063        _userManager = (UserManager) manager.lookup(UserManager.ROLE);
064        Long config = Config.getInstance().<Long>getValue("runtime.data.policy.userpref.retention", false, null);
065        // disable the processing if config is negative
066        _retentionPeriod = config != null && config >= 0 ? Period.ofMonths(config.intValue()) : null;
067    }
068    public AnonymizationResult process(UserStatusInfo userStatusInfo)
069    {
070        if (_retentionPeriod != null && userStatusInfo.getMissingSinceDate().isBefore(ZonedDateTime.now().minus(_retentionPeriod)))
071        {
072            // remove the subscription from every site where the unknown user does not belong to a user.
073            // the subscription from the remaining site will be processed once the matching user will be deleted
074            
075            long deletedSubscriptions = 0;
076            String email = userStatusInfo.getEmail();
077            if (StringUtils.isNotBlank(email))
078            {
079                try (AmetysObjectIterable<Site> sites = _siteManager.getSites())
080                {
081                    for (Site site: sites)
082                    {
083                        String siteName = site.getName();
084                        Set<String> userPopulationsOnSite = _populationContextHelper.getUserPopulationsOnContexts(Arrays.asList("/sites/" + siteName, "/sites-fo/" + siteName), false, false);
085                        try
086                        {
087                            if (_userManager.getUserByEmail(userPopulationsOnSite, email) == null)
088                            {
089                                deletedSubscriptions += _subscribersDao.unsubscribe(email, siteName, UnsubscribeOrigin.DATAPOLICY);
090                            }
091                        }
092                        catch (NotUniqueUserException e)
093                        {
094                            // Do nothing, this email is still matching existing users
095                        }
096                    }
097                }
098            }
099            
100            if (deletedSubscriptions > 0)
101            {
102                ForensicLogger.info("data.policy.gdpr.remove.newsletter.subscriptions", Map.of("handled", Long.toString(deletedSubscriptions), "identity", userStatusInfo.getUserIdentity()), UserPopulationDAO.SYSTEM_USER_IDENTITY);
103                return AnonymizationResult.PROCESSED;
104            }
105            else
106            {
107                return AnonymizationResult.NO_DATA;
108            }
109        }
110        else
111        {
112            return AnonymizationResult.TOO_EARLY;
113        }
114    }
115
116}