001/* 002 * Copyright 2024 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.io.IOException; 019import java.io.InputStream; 020import java.nio.charset.StandardCharsets; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Map; 024 025import org.apache.avalon.framework.component.Component; 026import org.apache.avalon.framework.context.Context; 027import org.apache.avalon.framework.context.ContextException; 028import org.apache.avalon.framework.context.Contextualizable; 029import org.apache.avalon.framework.service.ServiceException; 030import org.apache.avalon.framework.service.ServiceManager; 031import org.apache.avalon.framework.service.Serviceable; 032import org.apache.cocoon.components.ContextHelper; 033import org.apache.cocoon.environment.Request; 034import org.apache.cocoon.servlet.multipart.Part; 035import org.apache.cocoon.servlet.multipart.RejectedPart; 036import org.apache.commons.io.FilenameUtils; 037import org.apache.commons.io.IOUtils; 038import org.apache.commons.io.input.BOMInputStream; 039import org.quartz.SchedulerException; 040 041import org.ametys.core.cocoon.JSonReader; 042import org.ametys.core.right.RightManager; 043import org.ametys.core.right.RightManager.RightResult; 044import org.ametys.core.ui.Callable; 045import org.ametys.core.user.CurrentUserProvider; 046import org.ametys.core.user.UserIdentity; 047import org.ametys.plugins.core.schedule.Scheduler; 048import org.ametys.runtime.plugin.component.AbstractLogEnabled; 049import org.ametys.web.WebHelper; 050 051/** 052 * Import user to invit from a CSV or text file. 053 */ 054public class ImportInvitations extends AbstractLogEnabled implements Serviceable, Component, Contextualizable 055{ 056 /** Avalon Role */ 057 public static final String ROLE = ImportInvitations.class.getName(); 058 059 private static final String[] _ALLOWED_EXTENSIONS = new String[] {"txt", "csv"}; 060 private Scheduler _scheduler; 061 private CurrentUserProvider _currentUserProvider; 062 private Context _context; 063 private RightManager _rightManager; 064 065 @Override 066 public void service(ServiceManager smanager) throws ServiceException 067 { 068 _scheduler = (Scheduler) smanager.lookup(Scheduler.ROLE); 069 _currentUserProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE); 070 _rightManager = (RightManager) smanager.lookup(RightManager.ROLE); 071 } 072 073 public void contextualize(Context context) throws ContextException 074 { 075 _context = context; 076 } 077 078 /** 079 * Do the import of the file 080 * @param populationAndUserDirectory The population and user directory 081 * @param resendInvitation booloean to know if we resend invitation 082 * @param part The file 083 * @return The result of the import 084 * @throws Exception If an error occurs 085 */ 086 @Callable (rights = Callable.SKIP_BUILTIN_CHECK) 087 public Map doImport (String populationAndUserDirectory, boolean resendInvitation, Part part) throws Exception 088 { 089 // Check user can access this feature 090 _checkUserRight(List.of("Web_Rights_HandleInvitations", "Web_Rights_HandleInvitations_OwnPopulation"), "/${WorkspaceName}"); 091 092 Request request = ContextHelper.getRequest(_context); 093 Map<String, Object> result = new HashMap<>(); 094 095 if (part instanceof RejectedPart) 096 { 097 result = Map.of("success", false, "error", "rejected-file"); 098 099 request.setAttribute(JSonReader.OBJECT_TO_READ, result); 100 return result; 101 } 102 103 String filename = (part != null) ? part.getFileName().toLowerCase() : null; 104 105 if (!FilenameUtils.isExtension(filename, _ALLOWED_EXTENSIONS)) 106 { 107 result = Map.of("success", false, "error", "invalid-extension"); 108 request.setAttribute(JSonReader.OBJECT_TO_READ, result); 109 return result; 110 } 111 112 try (InputStream fileIS = part.getInputStream(); 113 BOMInputStream bomIS = new BOMInputStream(fileIS)) 114 { 115 List<String> guestLines = IOUtils.readLines(bomIS, StandardCharsets.UTF_8); 116 117 String[] split = populationAndUserDirectory.split("#"); 118 String populationId = split[0]; 119 String userDirectoryId = split[1]; 120 String siteName = WebHelper.getSiteName(request); 121 122 // Schedule the sending of invitations 123 _scheduler.scheduleJob(new SendInvitationsRunnable(guestLines, siteName, populationId, userDirectoryId, resendInvitation, _currentUserProvider.getUser())); 124 125 } 126 catch (IOException e) 127 { 128 getLogger().error("Failed to read file to send invitations from CSV file", e); 129 130 result = Map.of("success", false, "error", "file-error"); 131 request.setAttribute(JSonReader.OBJECT_TO_READ, result); 132 133 return result; 134 } 135 catch (SchedulerException e) 136 { 137 getLogger().error("An exception occurred while trying to schedule the sending of invitations", e); 138 139 result = Map.of("success", false, "error", "schedule-error"); 140 request.setAttribute(JSonReader.OBJECT_TO_READ, result); 141 142 return result; 143 } 144 145 result.put("success", true); 146 request.setAttribute(JSonReader.OBJECT_TO_READ, result); 147 return result; 148 } 149 150 /** 151 * Check if the user right to access the feature 152 * @param rightIds The rights ids 153 * @param context The rights context 154 * @throws IllegalStateException if the user has no right 155 */ 156 protected void _checkUserRight(List<String> rightIds, String context) throws IllegalStateException 157 { 158 UserIdentity user = _currentUserProvider.getUser(); 159 for (String rightId : rightIds) 160 { 161 if (_rightManager.hasRight(user, rightId, context) != RightResult.RIGHT_ALLOW) 162 { 163 getLogger().error("User '" + user + "' tried to access a privileged feature without convenient right. Should have right '" + rightId + "' on context '" + context + "'"); 164 throw new IllegalStateException("You have no right to access this feature."); 165 } 166 } 167 } 168}