001/*
002 *  Copyright 2022 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.web.usermanagement;
017
018import java.time.ZoneId;
019import java.time.ZonedDateTime;
020import java.util.ArrayList;
021import java.util.Date;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026import java.util.stream.Collectors;
027
028import org.apache.avalon.framework.context.Context;
029import org.apache.avalon.framework.context.ContextException;
030import org.apache.avalon.framework.context.Contextualizable;
031import org.apache.avalon.framework.service.ServiceException;
032import org.apache.avalon.framework.service.ServiceManager;
033import org.apache.cocoon.components.ContextHelper;
034import org.apache.cocoon.environment.Request;
035import org.apache.commons.collections4.map.HashedMap;
036
037import org.ametys.core.right.RightManager.RightResult;
038import org.ametys.core.ui.Callable;
039import org.ametys.core.ui.StaticClientSideElement;
040import org.ametys.core.user.User;
041import org.ametys.core.user.UserIdentity;
042import org.ametys.core.user.UserManager;
043import org.ametys.core.user.directory.ModifiableUserDirectory;
044import org.ametys.core.user.directory.UserDirectory;
045import org.ametys.core.user.population.UserPopulation;
046import org.ametys.core.user.population.UserPopulationDAO;
047import org.ametys.runtime.i18n.I18nizableText;
048import org.ametys.web.WebHelper;
049import org.ametys.web.usermanagement.UserManagementException.StatusError;
050import org.ametys.web.usermanagement.UserSignupManager.TempUser;
051import org.ametys.web.usermanagement.UserSignupManager.TempUser.TempUserOrigin;
052
053/**
054 * Client side element for sending invitations
055 *
056 */
057public class SignupInvitationClientSideElement extends StaticClientSideElement implements Contextualizable
058{
059    
060    private Context _context;
061    private UserPopulationDAO _userPopulationDAO;
062    private UserManager _userManager;
063    private UserSignupManager _signupManager;
064
065    public void contextualize(Context context) throws ContextException
066    {
067        _context = context;
068    }
069    
070    @Override
071    public void service(ServiceManager serviceManager) throws ServiceException
072    {
073        super.service(serviceManager);
074        _userPopulationDAO = (UserPopulationDAO) serviceManager.lookup(UserPopulationDAO.ROLE);
075        _userManager = (UserManager) serviceManager.lookup(UserManager.ROLE);
076        _signupManager = (UserSignupManager) serviceManager.lookup(UserSignupManager.ROLE);
077    }
078    
079    @Override
080    public List<Script> getScripts(boolean ignoreRights, Map<String, Object> contextParameters)
081    {
082        List<Script> scripts = super.getScripts(ignoreRights, contextParameters);
083        
084        if (!scripts.isEmpty())
085        {
086            Request request = ContextHelper.getRequest(_context);
087            String siteName = WebHelper.getSiteName(request);
088            UserIdentity currentUser = _currentUserProvider.getUser();
089            
090            Set<String> allowedPopulationIds = _signupManager.getAllowedUserPopulationForInvitation(siteName);
091            
092            List<UserPopulation> populations = allowedPopulationIds.stream()
093                    .map(pId -> _userPopulationDAO.getUserPopulation(pId))
094                    .filter(up -> _containsModifiableUserDirectory(up))
095                    .collect(Collectors.toList());
096            
097            if (populations.isEmpty())
098            {
099                // User has necessary rights but there is no modifiable population
100                return List.of();
101            }
102            
103            Map<String, Object> parameters = scripts.get(0).getParameters();
104            boolean canInvitAll = _rightManager.hasRight(currentUser, "Web_Rights_HandleInvitations", "/cms") == RightResult.RIGHT_ALLOW;
105            parameters.put("userPopulationOnly", !canInvitAll);
106            
107            User user = _userManager.getUser(currentUser);
108            UserDirectory userDirectory = user.getUserDirectory();
109            if (userDirectory instanceof ModifiableUserDirectory)
110            {
111                parameters.put("defaultUserDirectory", currentUser.getPopulationId() + "#" + userDirectory.getId());
112            }
113        }
114        
115        return scripts;
116        
117    }
118    
119    /**
120     * Send an invitation
121     * @param siteName the site name
122     * @param popAndUserDirectory the population and user directory
123     * @param email the email
124     * @param lastname the last name. Can be null or empty
125     * @param firstname the first name. Can be null or empty
126     * @return the result map
127     */
128    @Callable
129    public Map<String, Object> sendInvitation(String siteName, String popAndUserDirectory, String email, String lastname, String firstname)
130    {
131        String[] split = popAndUserDirectory.split("#");
132        String populationId = split[0];
133        String userDirectoryId = split[1];
134        
135        try
136        {
137            _signupManager.inviteToSignup(siteName, null, email, populationId, userDirectoryId, lastname, firstname, true, false, true);
138            return Map.of("success", true, "siteName", siteName, "email", email);
139        }
140        catch (UserManagementException e)
141        {
142            Map<String, Object> result = new HashedMap<>();
143            result.put("success", false);
144            
145            StatusError statusError = e.getStatusError();
146            if (statusError != null)
147            {
148                result.put("message", new I18nizableText("plugin.web", "PLUGINS_WEB_USERS_SEND_INVITATION_ERROR_" + statusError.name()));
149            }
150            
151            return result;
152        }
153    }
154    
155    /**
156     * Resend an invitation
157     * @param siteName the site name
158     * @param email the email
159     * @param populationId the population id
160     * @param udId the user directory id
161     * @return the result map
162     */
163    @Callable
164    public Map<String, Object> resendInvitation(String siteName, String email, String populationId, String udId)
165    {
166        try
167        {
168            _signupManager.resendInvitation(siteName, email, populationId, udId);
169            
170            TempUser tempUser = _signupManager.getTempUser(siteName, email, populationId, udId);
171            
172            Date rawSubscriptionDate = tempUser.getSubscriptionDate();
173            ZonedDateTime subscriptionDate = rawSubscriptionDate.toInstant().atZone(ZoneId.systemDefault());
174            
175            return Map.of("success", true, "siteName", siteName, "email", email, "subscriptionDate", subscriptionDate);
176        }
177        catch (UserManagementException e)
178        {
179            Map<String, Object> result = new HashedMap<>();
180            result.put("success", false);
181            
182            StatusError statusError = e.getStatusError();
183            if (statusError != null)
184            {
185                result.put("message", new I18nizableText("plugin.web", "PLUGINS_WEB_USERS_SEND_INVITATION_ERROR_" + statusError.name()));
186            }
187            
188            return result;
189        }
190    }
191    
192    /**
193     * Delete invitations
194     * @param siteName the site name
195     * @param emails the emails to delete
196     * @return the result map
197     */
198    @Callable
199    public Map<String, Object> removeInvitation(String siteName, List<String> emails)
200    {
201        Map<String, Object> result = new HashMap<>();
202        
203        List<String> deletedInvitations = new ArrayList<>();
204        List<String> noRightInvitations = new ArrayList<>();
205        List<String> noInvitations = new ArrayList<>();
206        
207        try
208        {
209            Set<String> allowedUserPopulation = _signupManager.getAllowedUserPopulationForInvitation(siteName);
210            for (String email : emails)
211            {
212                TempUser tempUser = _signupManager.getTempUser(siteName, email, null, null);
213                if (tempUser != null && tempUser.getOrigin() == TempUserOrigin.INVITATION)
214                {
215                    String population = tempUser.getPopulation();
216                    if (allowedUserPopulation.contains(population))
217                    {
218                        _signupManager.removeTempUser(siteName, email, null, tempUser.getPopulation(), tempUser.getUserDirectoryId());
219                        deletedInvitations.add(email);
220                    }
221                    else
222                    {
223                        noRightInvitations.add(email);
224                    }
225                }
226                else
227                {
228                    noInvitations.add(email);
229                }
230            }
231            
232            result.put("success", true);
233            result.put("deleted-invitations", deletedInvitations);
234            result.put("noright-invitations", noRightInvitations);
235            result.put("no-invitations", noInvitations);
236            
237            return result;
238        }
239        catch (UserManagementException e)
240        {
241            result.put("success", false);
242            
243            StatusError statusError = e.getStatusError();
244            if (statusError != null)
245            {
246                result.put("message", new I18nizableText("plugin.web", "PLUGINS_WEB_USERS_REMOVE_INVITATION_ERROR_" + statusError.name()));
247            }
248            
249            return result;
250        }
251    }
252    
253    private boolean _containsModifiableUserDirectory(UserPopulation up)
254    {
255        return up.getUserDirectories().stream().anyMatch(ud -> ud instanceof ModifiableUserDirectory);
256    }
257}