diff --git a/src/org/opencms/ui/apps/Messages.java b/src/org/opencms/ui/apps/Messages.java index e686998fa6e..a708f6f214c 100644 --- a/src/org/opencms/ui/apps/Messages.java +++ b/src/org/opencms/ui/apps/Messages.java @@ -3890,12 +3890,42 @@ public final class Messages extends A_CmsMessageBundle { /** Message constant for key in the resource bundle. */ public static final String RPT_USERIMPORT_FILE_CONTAINS_1 = "RPT_USERIMPORT_FILE_CONTAINS_1"; + /** Message constant for key in the resource bundle. */ + public static final String RPT_USERIMPORT_GROUP_FAILED_2 = "RPT_USERIMPORT_GROUP_FAILED_2"; + + /** Message constant for key in the resource bundle. */ + public static final String RPT_USERIMPORT_IMPORT_EMPTY_NAME_0 = "RPT_USERIMPORT_IMPORT_EMPTY_NAME_0"; + + /** Message constant for key in the resource bundle. */ + public static final String RPT_USERIMPORT_IMPORT_EMPTY_RECORD_0 = "RPT_USERIMPORT_IMPORT_EMPTY_RECORD_0"; + + /** Message constant for key in the resource bundle. */ + public static final String RPT_USERIMPORT_IMPORT_FAILED_1 = "RPT_USERIMPORT_IMPORT_FAILED_1"; + /** Message constant for key in the resource bundle. */ public static final String RPT_USERIMPORT_IMPORT_ALREADY_IN_OU_1 = "RPT_USERIMPORT_IMPORT_ALREADY_IN_OU_1"; + /** Message constant for key in the resource bundle. */ + public static final String RPT_USERIMPORT_IMPORT_CREATE_FAILED_1 = "RPT_USERIMPORT_IMPORT_CREATE_FAILED_1"; + /** Message constant for key in the resource bundle. */ public static final String RPT_USERIMPORT_IMPORT_SUCCESFULL_1 = "RPT_USERIMPORT_IMPORT_SUCCESFULL_1"; + /** Message constant for key in the resource bundle. */ + public static final String RPT_USERIMPORT_MAIL_FAILED_1 = "RPT_USERIMPORT_MAIL_FAILED_1"; + + /** Message constant for key in the resource bundle. */ + public static final String RPT_USERIMPORT_PASSWORD_EMPTY_1 = "RPT_USERIMPORT_PASSWORD_EMPTY_1"; + + /** Message constant for key in the resource bundle. */ + public static final String RPT_USERIMPORT_PASSWORD_ENCRYPTION_FAILED_1 = "RPT_USERIMPORT_PASSWORD_ENCRYPTION_FAILED_1"; + + /** Message constant for key in the resource bundle. */ + public static final String RPT_USERIMPORT_ROLE_FAILED_2 = "RPT_USERIMPORT_ROLE_FAILED_2"; + + /** Message constant for key in the resource bundle. */ + public static final String RPT_USERIMPORT_SETTINGS_FAILED_1 = "RPT_USERIMPORT_SETTINGS_FAILED_1"; + /** Name of the used resource bundle. */ private static final String BUNDLE_NAME = "org.opencms.ui.apps.messages"; diff --git a/src/org/opencms/ui/apps/messages.properties b/src/org/opencms/ui/apps/messages.properties index 76b701bb676..ccf281f1370 100644 --- a/src/org/opencms/ui/apps/messages.properties +++ b/src/org/opencms/ui/apps/messages.properties @@ -1376,6 +1376,16 @@ RPT_USERIMPORT_BEGIN_0=Userimport from csv-file starts.. RPT_USERIMPORT_FILE_CONTAINS_1=The file contains {0} user. RPT_USERIMPORT_IMPORT_SUCCESFULL_1=User {0} was imported successfully RPT_USERIMPORT_IMPORT_ALREADY_IN_OU_1=A user with the name {0} is in the given OU. Ignore this user for import. +RPT_USERIMPORT_IMPORT_FAILED_1=Unable to import user {0}. See the OpenCms log for details. +RPT_USERIMPORT_IMPORT_EMPTY_RECORD_0=Unable to import an empty user record. +RPT_USERIMPORT_IMPORT_EMPTY_NAME_0=Unable to import user: the user name is empty. +RPT_USERIMPORT_IMPORT_CREATE_FAILED_1=Unable to create user {0}. See the OpenCms log for details. +RPT_USERIMPORT_PASSWORD_EMPTY_1=Unable to import user {0}: password is empty. +RPT_USERIMPORT_PASSWORD_ENCRYPTION_FAILED_1=Unable to import user {0}: password encryption failed. +RPT_USERIMPORT_GROUP_FAILED_2=User {0} could not be added to group {1}. +RPT_USERIMPORT_ROLE_FAILED_2=User {0} could not be added to role {1}. +RPT_USERIMPORT_SETTINGS_FAILED_1=User {0} was created, but its workplace settings could not be saved. +RPT_USERIMPORT_MAIL_FAILED_1=User {0} was created, but the notification email failed. RPT_USERIMPORT_END_0=Userimport has finished. GUI_RESOURCETYPE_NAME_0=Resource typ @@ -1485,4 +1495,4 @@ label.ContentUserNotification.Subject.help=The subject of the mail label.ContentUserNotification.Footer=Footer label.ContentUserNotification.Footer.help=The footer of the mail -GUI_APP_RELOAD_0=Refresh \ No newline at end of file +GUI_APP_RELOAD_0=Refresh diff --git a/src/org/opencms/ui/apps/user/CmsCsvImportUtils.java b/src/org/opencms/ui/apps/user/CmsCsvImportUtils.java new file mode 100644 index 00000000000..18b475196f1 --- /dev/null +++ b/src/org/opencms/ui/apps/user/CmsCsvImportUtils.java @@ -0,0 +1,264 @@ +/* + * This library is part of OpenCms - + * the Open Source Content Management System + * + * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * For further information about Alkacon Software GmbH & Co. KG, please see the + * company website: https://www.alkacon.com + * + * For further information about OpenCms, please see the + * project website: https://www.opencms.org + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.opencms.ui.apps.user; + +import org.opencms.file.CmsUser; +import org.opencms.main.CmsRuntimeException; +import org.opencms.util.CmsStringUtil; +import org.opencms.util.CmsXsltUtil; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import com.google.common.base.Splitter; + +/** + * Utility methods for CSV user imports.

+ */ +final class CmsCsvImportUtils { + + /** UTF-8 byte order mark. */ + private static final String BOM = "\ufeff"; + + /** Hidden constructor. */ + private CmsCsvImportUtils() { + + // utility class + } + + /** + * Normalizes a CSV field name or value by removing an optional UTF-8 BOM and matching surrounding quotes.

+ * + * @param value the raw CSV token + * @return the normalized token + */ + static String normalizeToken(String value) { + + if (value == null) { + return null; + } + String result = value; + if (result.startsWith(BOM)) { + result = result.substring(BOM.length()); + } + if ((result.length() >= 2) && result.startsWith("\"") && result.endsWith("\"")) { + result = result.substring(1, result.length() - 1); + } + return result; + } + + /** + * Parses CSV user import data.

+ * + * @param data the UTF-8 encoded CSV data + * @param defaultPassword the password to use if the CSV password should not be used + * @param keepPasswordIfPossible true if a CSV password value should be imported + * + * @return the parsed users + */ + static List readUsers(byte[] data, String defaultPassword, boolean keepPasswordIfPossible) { + + List users = new ArrayList(); + List columns = null; + String separator = null; + int lineNumber = 0; + + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(new ByteArrayInputStream(data), StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + lineNumber++; + if ((lineNumber == 1) && CmsStringUtil.isEmptyOrWhitespaceOnly(line)) { + throw new IllegalArgumentException("The CSV header is empty."); + } + if (separator == null) { + separator = CmsXsltUtil.getPreferredDelimiter(line); + if (CmsStringUtil.isEmptyOrWhitespaceOnly(separator)) { + throw new IllegalArgumentException("Unable to detect the CSV separator."); + } + } + List lineValues = Splitter.on(separator).splitToList(line); + if (columns == null) { + columns = normalizeHeader(lineValues); + validateHeader(columns); + continue; + } + if (CmsStringUtil.isEmptyOrWhitespaceOnly(line)) { + continue; + } + users.add(parseUser(columns, lineValues, lineNumber, defaultPassword, keepPasswordIfPossible)); + } + } catch (IOException e) { + throw new IllegalArgumentException("Unable to read the CSV user import file.", e); + } + + if (columns == null) { + throw new IllegalArgumentException("The CSV file does not contain a header."); + } + return users; + } + + /** + * Gets a normalized value by column name.

+ * + * @param columns the normalized columns + * @param rawValues the raw values + * @param columnName the column name + * + * @return the value + */ + private static String getValue(List columns, List rawValues, String columnName) { + + int index = columns.indexOf(columnName); + String value = ((index >= 0) && (index < rawValues.size())) ? normalizeToken(rawValues.get(index)) : ""; + return value == null ? "" : value; + } + + /** + * Normalizes the CSV header.

+ * + * @param rawHeader the raw header fields + * + * @return the normalized header fields + */ + private static List normalizeHeader(List rawHeader) { + + List result = new ArrayList(); + for (int i = 0; i < rawHeader.size(); i++) { + String column = normalizeToken(rawHeader.get(i)); + if (column != null) { + column = column.trim(); + } + if (CmsStringUtil.isEmptyOrWhitespaceOnly(column)) { + throw new IllegalArgumentException("CSV column " + (i + 1) + " has an empty name."); + } + if (result.contains(column)) { + throw new IllegalArgumentException("Duplicate CSV column '" + column + "'."); + } + result.add(column); + } + return result; + } + + /** + * Parses a single CSV user line.

+ * + * @param columns the normalized columns + * @param rawValues the raw values + * @param lineNumber the CSV line number + * @param defaultPassword the default password + * @param keepPasswordIfPossible true if CSV passwords should be imported + * + * @return the parsed user + */ + private static CmsUser parseUser( + List columns, + List rawValues, + int lineNumber, + String defaultPassword, + boolean keepPasswordIfPossible) { + + CmsUser user = new CmsUser(); + String userName = getValue(columns, rawValues, "name"); + if (CmsStringUtil.isEmptyOrWhitespaceOnly(userName)) { + throw new IllegalArgumentException("CSV line " + lineNumber + " does not contain a user name."); + } + for (int i = 0; i < columns.size(); i++) { + String column = columns.get(i); + String value = i < rawValues.size() ? normalizeToken(rawValues.get(i)) : ""; + if (value == null) { + value = ""; + } + if ("password".equals(column) + && (CmsStringUtil.isEmptyOrWhitespaceOnly(value) || !keepPasswordIfPossible)) { + value = defaultPassword; + } + setUserValue(user, column, value, lineNumber); + } + if (CmsStringUtil.isEmptyOrWhitespaceOnly(user.getName())) { + throw new IllegalArgumentException("CSV line " + lineNumber + " does not contain a user name."); + } + if (CmsStringUtil.isEmptyOrWhitespaceOnly(user.getPassword())) { + user.setPassword(defaultPassword); + } + return user; + } + + /** + * Sets a field value on a user by reflection or stores it as additional information.

+ * + * @param user the user + * @param column the CSV column + * @param value the CSV value + * @param lineNumber the CSV line number + */ + private static void setUserValue(CmsUser user, String column, String value, int lineNumber) { + + if (CmsStringUtil.isEmptyOrWhitespaceOnly(value) || "null".equals(value)) { + return; + } + try { + Method method = CmsUser.class.getMethod( + "set" + column.substring(0, 1).toUpperCase() + column.substring(1), + new Class[] {String.class}); + method.invoke(user, new Object[] {value}); + } catch (NoSuchMethodException e) { + user.setAdditionalInfo(column, value); + } catch (IllegalAccessException e) { + throw new IllegalArgumentException( + "CSV line " + lineNumber + ": field '" + column + "' is not accessible.", + e); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause() == null ? e : e.getCause(); + throw new IllegalArgumentException( + "CSV line " + lineNumber + ": invalid value for field '" + column + "'.", + cause); + } catch (CmsRuntimeException e) { + throw new IllegalArgumentException("CSV line " + lineNumber + ": unable to set field '" + column + "'.", e); + } + } + + /** + * Validates the CSV header.

+ * + * @param columns the normalized columns + */ + private static void validateHeader(List columns) { + + if (!columns.contains("name")) { + throw new IllegalArgumentException("The mandatory CSV column 'name' is missing."); + } + } +} diff --git a/src/org/opencms/ui/apps/user/CmsImportExportUserDialog.java b/src/org/opencms/ui/apps/user/CmsImportExportUserDialog.java index 6b6d58747c2..228c80014cf 100644 --- a/src/org/opencms/ui/apps/user/CmsImportExportUserDialog.java +++ b/src/org/opencms/ui/apps/user/CmsImportExportUserDialog.java @@ -31,7 +31,6 @@ import org.opencms.file.CmsUser; import org.opencms.main.CmsException; import org.opencms.main.CmsLog; -import org.opencms.main.CmsRuntimeException; import org.opencms.main.OpenCms; import org.opencms.security.CmsRole; import org.opencms.security.I_CmsPrincipal; @@ -46,17 +45,9 @@ import org.opencms.ui.dialogs.permissions.CmsPrincipalSelect.WidgetType; import org.opencms.util.CmsStringUtil; import org.opencms.util.CmsUUID; -import org.opencms.util.CmsXsltUtil; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.FileReader; -import java.io.IOException; -import java.io.InputStreamReader; import java.io.OutputStream; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -65,7 +56,6 @@ import org.apache.commons.logging.Log; -import com.google.common.base.Splitter; import com.google.common.base.Supplier; import com.vaadin.data.HasValue.ValueChangeEvent; import com.vaadin.data.HasValue.ValueChangeListener; @@ -84,107 +74,42 @@ import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.Window; -/** - * Dialog for CSV im- and export.

- */ +/** Dialog for CSV user import and export. */ public final class CmsImportExportUserDialog extends A_CmsImportExportUserDialog implements Receiver, I_CmsPasswordFetcher { - /** The "bom" bytes as String that need to be placed at the very beginning of the produced csv. */ - private static final String BOM = "\ufeff"; - - /**The dialog height. */ public static final String DIALOG_HEIGHT = "650px"; - /** Log instance for this class. */ static final Log LOG = CmsLog.getLog(CmsImportExportUserDialog.class); - /**vaadin serial id. */ private static final long serialVersionUID = -2055302491540892101L; - /**Label to show uploaded file. */ protected Label m_uploadname; - - /**Start import button. */ Button m_startImport; - - /**Vaadin Component. */ private Panel m_includeTechnicalFieldsPanel; - - /**Vaadin Component. */ private CheckBox m_includeTechnicalFields; - - /**Cancel button. */ private Button m_cancel; - - /**CmsObject. */ private CmsObject m_cms; - - /**Download button for export. */ private Button m_download; - - /**Layout for groups. */ private VerticalLayout m_exportGroups; - - /**Groups. */ private CmsEditableGroup m_exportGroupsGroup; - - /**Layout for roles. */ private VerticalLayout m_exportRoles; - - /**Roles. */ private CmsEditableGroup m_exportRolesGroup; - - /**Generate password button. */ private Button m_generateButton; - - /**Should the group field be editable? */ private boolean m_groupEditable = true; - - /**ID of group. */ private CmsUUID m_groupID; - - /**Stream for upload file. */ private ByteArrayOutputStream m_importFileStream; - - /**Layout for groups.*/ private VerticalLayout m_importGroups; - - /**Groups. */ private CmsEditableGroup m_importGroupsGroup; - - /**Should password be imported? */ private CheckBox m_importPasswords; - - /**Layout for roles. */ private VerticalLayout m_importRoles; - - /**Roles. */ private CmsEditableGroup m_importRolesGroup; - - /**Password for imported user. */ private TextField m_password; - - /**List of user to import. */ List m_userImportList; - - /**Should the user get an email? */ private CheckBox m_sendMail; - - /**Tab with import and export sheet. */ private TabSheet m_tab; - - /**Upload for import. */ private Upload m_upload; - /** - * public constructor.

- * - * @param ou ou name - * @param groupID id of group - * @param window window - * @param allowTechnicalFieldsExport flag indicates if technical field export option should be available - */ private CmsImportExportUserDialog( final String ou, CmsUUID groupID, @@ -192,13 +117,11 @@ private CmsImportExportUserDialog( boolean allowTechnicalFieldsExport) { setHeight(DIALOG_HEIGHT); - m_groupID = groupID; - try { m_cms = OpenCms.initCmsObject(A_CmsUI.getCmsObject()); - } catch (CmsException e1) { - // + } catch (CmsException e) { + LOG.error("Unable to initialize the CMS context for CSV user import/export.", e); } CmsVaadinUtils.readAndLocalizeDesign(this, CmsVaadinUtils.getWpMessagesForCurrentLocale(), null); m_includeTechnicalFieldsPanel.setVisible(allowTechnicalFieldsExport); @@ -207,22 +130,16 @@ private CmsImportExportUserDialog( public void valueChange(ValueChangeEvent event) { initDownloadButton(); - } - }); m_importPasswords.setValue(Boolean.TRUE); m_sendMail.setValue(Boolean.TRUE); - setButtonVisibility(0); - m_tab.addSelectedTabChangeListener( event -> setButtonVisibility(m_tab.getTabPosition(m_tab.getTab(m_tab.getSelectedTab())))); - m_password.setValue(CmsGeneratePasswordDialog.getRandomPassword()); m_startImport.setEnabled(false); m_startImport.addClickListener(event -> importUserFromFile()); - m_generateButton.addClickListener(new Button.ClickListener() { private static final long serialVersionUID = 4128513094772586752L; @@ -239,16 +156,13 @@ public void buttonClick(ClickEvent event) { public void run() { windowDialog.close(); - } }); windowDialog.setContent(dialog); A_CmsUI.get().addWindow(windowDialog); } }); - m_upload.setReceiver(this); - m_upload.addSucceededListener(new Upload.SucceededListener() { private static final long serialVersionUID = -6865652127878123021L; @@ -257,10 +171,10 @@ public void uploadSucceeded(SucceededEvent event) { try { m_userImportList = getUsersFromFile(); - m_startImport.setEnabled(true); + m_startImport.setEnabled(!m_userImportList.isEmpty()); m_uploadname.setValue(event.getFilename()); - } catch (Exception e) { - //wrong csv columns + } catch (RuntimeException e) { + LOG.error("Invalid CSV user import file '" + event.getFilename() + "'.", e); m_startImport.setEnabled(false); m_uploadname.setValue(""); CmsConfirmationDialog.show( @@ -270,35 +184,29 @@ public void uploadSucceeded(SucceededEvent event) { public void run() { + // nothing to do } - }); } } }); if (groupID == null) { - m_importGroupsGroup = new CmsEditableGroup(m_importGroups, new Supplier() { public Component get() { return getGroupSelect(ou, true, null); } - }, CmsVaadinUtils.getMessageText(Messages.GUI_USERMANAGEMENT_USER_IMEXPORT_ADD_GROUP_0)); - m_importGroupsGroup.init(); - m_exportGroupsGroup = new CmsEditableGroup(m_exportGroups, new Supplier() { public Component get() { return getGroupSelect(ou, true, null); } - }, CmsVaadinUtils.getMessageText(Messages.GUI_USERMANAGEMENT_USER_IMEXPORT_ADD_GROUP_0)); - m_exportGroupsGroup.init(); } else { m_exportGroups.addComponent(getGroupSelect(ou, false, groupID)); @@ -311,42 +219,25 @@ public Component get() { return getRoleComboBox(ou); } - }, CmsVaadinUtils.getMessageText(Messages.GUI_USERMANAGEMENT_USER_IMEXPORT_ADD_ROLE_0)); - m_importRolesGroup.init(); - m_exportRolesGroup = new CmsEditableGroup(m_exportRoles, new Supplier() { public Component get() { return getRoleComboBox(ou); } - }, CmsVaadinUtils.getMessageText(Messages.GUI_USERMANAGEMENT_USER_IMEXPORT_ADD_ROLE_0)); - m_exportRolesGroup.init(); - super.init(ou, window); - } - /** - * Returns a map with the users to export added.

- * @param cms CmsObject - * @param ou ou name - * @param exportUsers the map to add the users - * @return a map with the users to export added - * @throws CmsException if getting users failed - */ public static Map addExportAllUsers(CmsObject cms, String ou, Map exportUsers) throws CmsException { List users = OpenCms.getOrgUnitManager().getUsers(cms, ou, false); - if ((users != null) && (users.size() > 0)) { - Iterator itUsers = users.iterator(); - while (itUsers.hasNext()) { - CmsUser user = itUsers.next(); + if (users != null) { + for (CmsUser user : users) { if (!exportUsers.containsKey(user.getId())) { exportUsers.put(user.getId(), user); } @@ -355,31 +246,17 @@ public static Map addExportAllUsers(CmsObject cms, String ou, return exportUsers; } - /** - * Returns a map with the users to export added.

- * @param cms CmsObject - * @param groups the selected groups - * @param exportUsers the map to add the users - * - * @return a map with the users to export added - * - * @throws CmsException if getting groups or users of group failed - */ public static Map addExportUsersFromGroups( CmsObject cms, List groups, Map exportUsers) throws CmsException { - if ((groups != null) && (groups.size() > 0)) { - Iterator itGroups = groups.iterator(); - while (itGroups.hasNext()) { - List groupUsers = cms.getUsersOfGroup(itGroups.next()); - Iterator itGroupUsers = groupUsers.iterator(); - while (itGroupUsers.hasNext()) { - CmsUser groupUser = itGroupUsers.next(); - if (!exportUsers.containsKey(groupUser.getId())) { - exportUsers.put(groupUser.getId(), groupUser); + if (groups != null) { + for (String group : groups) { + for (CmsUser user : cms.getUsersOfGroup(group)) { + if (!exportUsers.containsKey(user.getId())) { + exportUsers.put(user.getId(), user); } } } @@ -387,18 +264,6 @@ public static Map addExportUsersFromGroups( return exportUsers; } - /** - * Returns a map with the users to export added.

- * @param cms CmsObject - * @param ou ou name - * - * @param roles the selected roles - * @param exportUsers the map to add the users - * - * @return a map with the users to export added - * - * @throws CmsException if getting roles or users of role failed - */ public static Map addExportUsersFromRoles( CmsObject cms, String ou, @@ -406,20 +271,16 @@ public static Map addExportUsersFromRoles( Map exportUsers) throws CmsException { - if ((roles != null) && (roles.size() > 0)) { - Iterator itRoles = roles.iterator(); - while (itRoles.hasNext()) { + if (roles != null) { + for (String role : roles) { List roleUsers = OpenCms.getRoleManager().getUsersOfRole( cms, - CmsRole.valueOfGroupName(itRoles.next()).forOrgUnit(ou), + CmsRole.valueOfGroupName(role).forOrgUnit(ou), true, false); - Iterator itRoleUsers = roleUsers.iterator(); - while (itRoleUsers.hasNext()) { - CmsUser roleUser = itRoleUsers.next(); - // contains - if (exportUsers.get(roleUser.getId()) == null) { - exportUsers.put(roleUser.getId(), roleUser); + for (CmsUser user : roleUsers) { + if (!exportUsers.containsKey(user.getId())) { + exportUsers.put(user.getId(), user); } } } @@ -427,68 +288,34 @@ public static Map addExportUsersFromRoles( return exportUsers; } - /** - * Gets an dialog instance for fixed group.

- * - * @param groupID id - * @param ou ou name - * @param window window - * @param allowTechnicalFieldsExport flag indicates if technical field export option should be available - * @return an instance of this class - */ public static CmsImportExportUserDialog getExportUserDialogForGroup( CmsUUID groupID, String ou, Window window, boolean allowTechnicalFieldsExport) { - CmsImportExportUserDialog res = new CmsImportExportUserDialog(ou, groupID, window, allowTechnicalFieldsExport); - return res; + return new CmsImportExportUserDialog(ou, groupID, window, allowTechnicalFieldsExport); } - /** - * Gets an dialog instance for fixed group.

- * - * @param ou ou name - * @param window window - * @param allowTechnicalFieldsExport flag indicates if technical field export option should be available - * @return an instance of this class - */ public static CmsImportExportUserDialog getExportUserDialogForOU( String ou, Window window, boolean allowTechnicalFieldsExport) { - CmsImportExportUserDialog res = new CmsImportExportUserDialog(ou, null, window, allowTechnicalFieldsExport); - return res; + return new CmsImportExportUserDialog(ou, null, window, allowTechnicalFieldsExport); } - /** - * @see org.opencms.ui.apps.user.I_CmsPasswordFetcher#fetchPassword(java.lang.String) - */ public void fetchPassword(String password) { m_password.setValue(password); - } - /** - * @see com.vaadin.ui.Upload.Receiver#receiveUpload(java.lang.String, java.lang.String) - */ public OutputStream receiveUpload(String filename, String mimeType) { m_importFileStream = new ByteArrayOutputStream(); return m_importFileStream; } - /** - * Get a principle select for choosing groups.

- * - * @param ou name - * @param enabled enabled? - * @param groupID default value - * @return CmsPrinicpalSelect - */ protected CmsPrincipalSelect getGroupSelect(String ou, boolean enabled, CmsUUID groupID) { CmsPrincipalSelect select = new CmsPrincipalSelect(); @@ -497,7 +324,6 @@ protected CmsPrincipalSelect getGroupSelect(String ou, boolean enabled, CmsUUID select.setRealPrincipalsOnly(true); select.setPrincipalType(I_CmsPrincipal.PRINCIPAL_GROUP); select.setWidgetType(WidgetType.groupwidget); - if (groupID != null) { try { select.setValue(m_cms.readGroup(groupID).getName()); @@ -505,125 +331,29 @@ protected CmsPrincipalSelect getGroupSelect(String ou, boolean enabled, CmsUUID LOG.error("Unable to read group", e); } } - - //OU Change enabled because ou-user can be part of other ou-groups return select; } - /** - * Get ComboBox for selecting roles.

- * - * @param ou name - * @return ComboBox - */ protected ComboBox getRoleComboBox(String ou) { ComboBox box = new ComboBox(); CmsUserEditDialog.iniRole(A_CmsUI.getCmsObject(), ou, box, null); box.setSelectedItem(CmsRole.EDITOR.forOrgUnit(ou)); - return box; } - /** - * Reads user from import file.

- * - * @return List of user (with passwords) - */ protected List getUsersFromFile() { - String separator = null; - List values = null; - - FileReader fileReader; - BufferedReader bufferedReader; - List users = null; - - boolean keepPasswordIfPossible = m_importPasswords.getValue().booleanValue(); - - try { - bufferedReader = new BufferedReader( - new InputStreamReader(new ByteArrayInputStream(m_importFileStream.toByteArray()))); - String line; - boolean headline = true; - boolean hasBOM = false; - while ((line = bufferedReader.readLine()) != null) { - if (users == null) { - users = new ArrayList(); - } - if (separator == null) { - separator = CmsXsltUtil.getPreferredDelimiter(line); - } - List lineValues = Splitter.on(separator).splitToList(line); - if (headline) { - values = new ArrayList(); - Iterator itLineValues = lineValues.iterator(); - while (itLineValues.hasNext()) { - String va = (String)itLineValues.next(); - if (va.startsWith(BOM) && va.substring(1, 2).equals("\"")) { - hasBOM = true; - va = va.substring(1); //Cut BOM - } - if (hasBOM) { - va = va.substring(1, va.length() - 1); - } - //} - values.add(va); - } - headline = false; - } else if (values != null) { - CmsUser curUser = new CmsUser(); - try { - for (int i = 0; i < values.size(); i++) { - String curValue = (String)values.get(i); - try { - Method method = CmsUser.class.getMethod( - "set" + curValue.substring(0, 1).toUpperCase() + curValue.substring(1), - new Class[] {String.class}); - String value = ""; - if ((lineValues.size() > i) && (lineValues.get(i) != null)) { - value = (String)lineValues.get(i); - if (hasBOM) { - - value = value.substring(1, value.length() - 1); - } - - } - if (curValue.equals("password")) { - if (CmsStringUtil.isEmptyOrWhitespaceOnly(value) | !keepPasswordIfPossible) { - value = m_password.getValue(); - } - } - if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(value) && !value.equals("null")) { - method.invoke(curUser, new Object[] {value}); - } - } catch (NoSuchMethodException ne) { - if (!CmsStringUtil.isEmptyOrWhitespaceOnly((String)lineValues.get(i))) { - curUser.setAdditionalInfo(curValue, lineValues.get(i)); - } - } catch (IllegalAccessException le) { - // - } catch (InvocationTargetException te) { - // - } - } - } catch (CmsRuntimeException e) { - // - } - users.add(curUser); - } - } - bufferedReader.close(); - } catch (IOException e) { - //noop + if (m_importFileStream == null) { + throw new IllegalArgumentException("No CSV import data is available."); } - return users; + return CmsCsvImportUtils.readUsers( + m_importFileStream.toByteArray(), + m_password.getValue(), + m_importPasswords.getValue().booleanValue()); } - /** - * Import user from file. - */ protected void importUserFromFile() { CmsImportUserThread thread = new CmsImportUserThread( @@ -642,50 +372,37 @@ public void run() { } }); m_window.setContent(dialog); - } - /** - * @see org.opencms.ui.apps.user.A_CmsImportExportUserDialog#getCloseButton() - */ @Override Button getCloseButton() { return m_cancel; } - /** - * @see org.opencms.ui.apps.user.A_CmsImportExportUserDialog#getDownloadButton() - */ @Override Button getDownloadButton() { return m_download; } - /** - * @see org.opencms.ui.apps.user.A_CmsImportExportUserDialog#getUserToExport() - */ @Override Map getUserToExport() { - // get the data object from session List groups = getGroupsList(m_exportGroups, false); - Iterator it = m_exportRolesGroup.getRows().iterator(); List roles = new ArrayList(); while (it.hasNext()) { CmsRole role = (CmsRole)((ComboBox)it.next().getComponent()).getValue(); roles.add(role.getGroupName()); } - Map exportUsers = new HashMap(); try { - if (((groups.size() < 1)) && ((roles.size() < 1))) { - exportUsers = CmsImportExportUserDialog.addExportAllUsers(m_cms, m_ou, exportUsers); + if ((groups.size() < 1) && (roles.size() < 1)) { + exportUsers = addExportAllUsers(m_cms, m_ou, exportUsers); } else { - exportUsers = CmsImportExportUserDialog.addExportUsersFromGroups(m_cms, groups, exportUsers); - exportUsers = CmsImportExportUserDialog.addExportUsersFromRoles(m_cms, m_ou, roles, exportUsers); + exportUsers = addExportUsersFromGroups(m_cms, groups, exportUsers); + exportUsers = addExportUsersFromRoles(m_cms, m_ou, roles, exportUsers); } } catch (CmsException e) { LOG.error("Unable to get export user list.", e); @@ -693,77 +410,51 @@ Map getUserToExport() { return exportUsers; } - /** - * @see org.opencms.ui.apps.user.A_CmsImportExportUserDialog#isExportWithTechnicalFields() - */ @Override boolean isExportWithTechnicalFields() { return m_includeTechnicalFields.getValue().booleanValue(); } - /** - * Gets selected groups in List.

- * - * @param parent layout - * @param importCase boolean - * @return List of group names - */ private List getGroupsList(VerticalLayout parent, boolean importCase) { - List res = new ArrayList(); - + List result = new ArrayList(); if (m_groupID != null) { try { - res.add(m_cms.readGroup(m_groupID).getName()); + result.add(m_cms.readGroup(m_groupID).getName()); } catch (CmsException e) { LOG.error("Unable to read group", e); } - return res; + return result; } - if (m_groupEditable) { CmsEditableGroup editableGroup = importCase ? m_importGroupsGroup : m_exportGroupsGroup; for (I_CmsEditableGroupRow row : editableGroup.getRows()) { String groupName = ((CmsPrincipalSelect)row.getComponent()).getValue(); if (!CmsStringUtil.isEmptyOrWhitespaceOnly(groupName)) { - res.add(groupName); + result.add(groupName); } } } else { - TextField comp = (TextField)parent.getComponent(0); - res.add(comp.getValue()); + TextField component = (TextField)parent.getComponent(0); + result.add(component.getValue()); } - return res; + return result; } - /** - * Get selected roles list.

- * - * @param parent layout - * @param importCase boolean - * @return List of roles - */ private List getRolesList(VerticalLayout parent, boolean importCase) { - List res = new ArrayList(); - + List result = new ArrayList(); CmsEditableGroup editableGroup = importCase ? m_importRolesGroup : m_exportRolesGroup; for (I_CmsEditableGroupRow row : editableGroup.getRows()) { - res.add(((ComboBox)row.getComponent()).getValue()); + result.add(((ComboBox)row.getComponent()).getValue()); } - return res; + return result; } - /** - * Set the visibility of the buttons.

- * - * @param tab which is selected. - */ private void setButtonVisibility(int tab) { m_download.setVisible(tab == 1); m_startImport.setVisible(tab == 0); - } } diff --git a/src/org/opencms/ui/apps/user/CmsImportUserThread.java b/src/org/opencms/ui/apps/user/CmsImportUserThread.java index 54710a72aad..f1ea6354a02 100644 --- a/src/org/opencms/ui/apps/user/CmsImportUserThread.java +++ b/src/org/opencms/ui/apps/user/CmsImportUserThread.java @@ -41,6 +41,7 @@ import org.opencms.ui.apps.CmsFileExplorerConfiguration; import org.opencms.ui.apps.CmsPageEditorConfiguration; import org.opencms.ui.apps.Messages; +import org.opencms.util.CmsStringUtil; import org.opencms.util.CmsUUID; import java.util.ArrayList; @@ -91,9 +92,9 @@ protected CmsImportUserThread( boolean sendmail) { super(cms, "importUser"); - m_userList = userList; - m_roleList = roles; - m_groupList = groups; + m_userList = userList == null ? new ArrayList() : userList; + m_roleList = roles == null ? new ArrayList() : roles; + m_groupList = groups == null ? new ArrayList() : groups; m_ou = ou; m_sendMail = sendmail; initHtmlReport(A_CmsUI.get().getLocale()); @@ -119,87 +120,17 @@ public void run() { getReport().println( Messages.get().container(Messages.RPT_USERIMPORT_FILE_CONTAINS_1, String.valueOf(m_userList.size())), I_CmsReport.FORMAT_DEFAULT); + Iterator itUsers = m_userList.iterator(); while (itUsers.hasNext()) { CmsUser user = itUsers.next(); - CmsUser createdUser = null; - if (!isAlreadyAvailable(user.getName())) { - - String password = user.getPassword(); - - if (password.indexOf("_") == -1) { - try { - - password = OpenCms.getPasswordHandler().digest(password); - - if (m_sendMail) { - CmsUserEditDialog.sendMail(getCms(), user.getPassword(), user, m_ou, true, false); - } - } catch (CmsPasswordEncryptionException e) { - // - } - } else { - password = password.substring(password.indexOf("_") + 1); - if (m_sendMail) { - CmsUserEditDialog.sendMail(getCms(), "your old password", user, m_ou, true, false); - } - } - - try { - createdUser = getCms().importUser( - new CmsUUID().toString(), - m_ou + user.getName(), - password, - user.getFirstname(), - user.getLastname(), - user.getEmail(), - user.getFlags(), - System.currentTimeMillis(), - user.getAdditionalInfo()); - - if (!m_groupList.isEmpty()) { - - Iterator itGroups = m_groupList.iterator(); - while (itGroups.hasNext()) { - try { - getCms().addUserToGroup(createdUser.getName(), itGroups.next()); - } catch (CmsException e) { - // - } - } - } - - if (!m_roleList.isEmpty()) { - Iterator itRoles = m_roleList.iterator(); - while (itRoles.hasNext()) { - - OpenCms.getRoleManager().addUserToRole( - getCms(), - itRoles.next().forOrgUnit(m_ou), - createdUser.getName()); - - } - - } - String startAppId = CmsPageEditorConfiguration.APP_ID; - if (OpenCms.getRoleManager().hasRole(getCms(), createdUser.getName(), CmsRole.WORKPLACE_USER)) { - startAppId = CmsFileExplorerConfiguration.APP_ID; - } - CmsUserSettings settings = new CmsUserSettings(createdUser); - settings.setStartView(startAppId); - settings.setStartProject("Offline"); - settings.save(getCms()); - } catch (CmsException e) { - LOG.error("Unable to create user", e); - } - if (createdUser != null) { - getReport().println( - Messages.get().container(Messages.RPT_USERIMPORT_IMPORT_SUCCESFULL_1, createdUser.getName()), - I_CmsReport.FORMAT_OK); - } - } else { + try { + importUser(user); + } catch (RuntimeException e) { + String userName = user == null ? "" : String.valueOf(user.getName()); + LOG.error("Unexpected error while importing user '" + userName + "' into OU '" + m_ou + "'.", e); getReport().println( - Messages.get().container(Messages.RPT_USERIMPORT_IMPORT_ALREADY_IN_OU_1, user.getName()), + Messages.get().container(Messages.RPT_USERIMPORT_IMPORT_FAILED_1, userName), I_CmsReport.FORMAT_ERROR); } } @@ -218,6 +149,7 @@ protected boolean isAlreadyAvailable(String userName) { try { availableUsers = OpenCms.getOrgUnitManager().getUsers(getCms(), m_ou, false); } catch (CmsException e) { + LOG.error("Unable to read users from OU '" + m_ou + "' while checking user '" + userName + "'.", e); availableUsers = new ArrayList(); } Iterator itAvailableUsers = availableUsers.iterator(); @@ -229,4 +161,177 @@ protected boolean isAlreadyAvailable(String userName) { return false; } + /** + * Adds the imported user to the selected groups.

+ * + * @param createdUser the imported user + */ + private void addGroups(CmsUser createdUser) { + + Iterator itGroups = m_groupList.iterator(); + while (itGroups.hasNext()) { + String groupName = itGroups.next(); + try { + getCms().addUserToGroup(createdUser.getName(), groupName); + } catch (CmsException e) { + LOG.error( + "Unable to add imported user '" + createdUser.getName() + "' to group '" + groupName + "'.", + e); + getReport().println( + Messages.get().container(Messages.RPT_USERIMPORT_GROUP_FAILED_2, createdUser.getName(), groupName), + I_CmsReport.FORMAT_WARNING); + } + } + } + + /** + * Adds the imported user to the selected roles.

+ * + * @param createdUser the imported user + */ + private void addRoles(CmsUser createdUser) { + + Iterator itRoles = m_roleList.iterator(); + while (itRoles.hasNext()) { + CmsRole role = itRoles.next(); + try { + OpenCms.getRoleManager().addUserToRole(getCms(), role.forOrgUnit(m_ou), createdUser.getName()); + } catch (CmsException e) { + LOG.error("Unable to add imported user '" + createdUser.getName() + "' to role '" + role + "'.", e); + getReport().println( + Messages.get().container(Messages.RPT_USERIMPORT_ROLE_FAILED_2, createdUser.getName(), role), + I_CmsReport.FORMAT_WARNING); + } + } + } + + /** + * Imports a single user.

+ * + * @param user the user data parsed from the CSV file + */ + private void importUser(CmsUser user) { + + if (user == null) { + LOG.error("Unable to import null user into OU '" + m_ou + "'."); + getReport().println( + Messages.get().container(Messages.RPT_USERIMPORT_IMPORT_EMPTY_RECORD_0), + I_CmsReport.FORMAT_ERROR); + return; + } + + String userName = user.getName(); + if (CmsStringUtil.isEmptyOrWhitespaceOnly(userName)) { + LOG.error("Unable to import user without name into OU '" + m_ou + "'."); + getReport().println( + Messages.get().container(Messages.RPT_USERIMPORT_IMPORT_EMPTY_NAME_0), + I_CmsReport.FORMAT_ERROR); + return; + } + + if (isAlreadyAvailable(userName)) { + getReport().println( + Messages.get().container(Messages.RPT_USERIMPORT_IMPORT_ALREADY_IN_OU_1, userName), + I_CmsReport.FORMAT_ERROR); + return; + } + + String sourcePassword = user.getPassword(); + if (CmsStringUtil.isEmptyOrWhitespaceOnly(sourcePassword)) { + LOG.error("Unable to import user '" + userName + "': no password was supplied by the CSV import."); + getReport().println( + Messages.get().container(Messages.RPT_USERIMPORT_PASSWORD_EMPTY_1, userName), + I_CmsReport.FORMAT_ERROR); + return; + } + + String password = sourcePassword; + String mailPassword = sourcePassword; + if (password.indexOf('_') == -1) { + try { + password = OpenCms.getPasswordHandler().digest(password); + } catch (CmsPasswordEncryptionException e) { + LOG.error("Unable to encrypt password for user '" + userName + "'.", e); + getReport().println( + Messages.get().container(Messages.RPT_USERIMPORT_PASSWORD_ENCRYPTION_FAILED_1, userName), + I_CmsReport.FORMAT_ERROR); + return; + } + } else { + password = password.substring(password.indexOf('_') + 1); + if (CmsStringUtil.isEmptyOrWhitespaceOnly(password)) { + LOG.error("Unable to import user '" + userName + "': encrypted password value is empty."); + getReport().println( + Messages.get().container(Messages.RPT_USERIMPORT_PASSWORD_EMPTY_1, userName), + I_CmsReport.FORMAT_ERROR); + return; + } + mailPassword = "your old password"; + } + + CmsUser createdUser; + try { + createdUser = getCms().importUser( + new CmsUUID().toString(), + m_ou + userName, + password, + user.getFirstname(), + user.getLastname(), + user.getEmail(), + user.getFlags(), + System.currentTimeMillis(), + user.getAdditionalInfo()); + } catch (CmsException e) { + LOG.error("Unable to create user '" + userName + "' in OU '" + m_ou + "'.", e); + getReport().println( + Messages.get().container(Messages.RPT_USERIMPORT_IMPORT_CREATE_FAILED_1, userName), + I_CmsReport.FORMAT_ERROR); + return; + } + + addGroups(createdUser); + addRoles(createdUser); + saveUserSettings(createdUser); + + if (m_sendMail) { + try { + CmsUserEditDialog.sendMail(getCms(), mailPassword, createdUser, m_ou, true, false, true); + } catch (RuntimeException e) { + LOG.error( + "User '" + createdUser.getName() + "' was created, but the notification email could not be sent.", + e); + getReport().println( + Messages.get().container(Messages.RPT_USERIMPORT_MAIL_FAILED_1, createdUser.getName()), + I_CmsReport.FORMAT_WARNING); + } + } + + getReport().println( + Messages.get().container(Messages.RPT_USERIMPORT_IMPORT_SUCCESFULL_1, createdUser.getName()), + I_CmsReport.FORMAT_OK); + } + + /** + * Saves the default workplace settings for the imported user.

+ * + * @param createdUser the imported user + */ + private void saveUserSettings(CmsUser createdUser) { + + try { + String startAppId = CmsPageEditorConfiguration.APP_ID; + if (OpenCms.getRoleManager().hasRole(getCms(), createdUser.getName(), CmsRole.WORKPLACE_USER)) { + startAppId = CmsFileExplorerConfiguration.APP_ID; + } + CmsUserSettings settings = new CmsUserSettings(createdUser); + settings.setStartView(startAppId); + settings.setStartProject("Offline"); + settings.save(getCms()); + } catch (CmsException e) { + LOG.error("Unable to save settings for imported user '" + createdUser.getName() + "'.", e); + getReport().println( + Messages.get().container(Messages.RPT_USERIMPORT_SETTINGS_FAILED_1, createdUser.getName()), + I_CmsReport.FORMAT_WARNING); + } + } } diff --git a/src/org/opencms/ui/apps/user/CmsUserEditDialog.java b/src/org/opencms/ui/apps/user/CmsUserEditDialog.java index fa7dd14cae0..e300cc6ff7b 100644 --- a/src/org/opencms/ui/apps/user/CmsUserEditDialog.java +++ b/src/org/opencms/ui/apps/user/CmsUserEditDialog.java @@ -693,6 +693,29 @@ protected static void sendMail( boolean newUser, boolean changePassword) { + sendMail(cms, password, user, ou, newUser, changePassword, false); + } + + /** + * Sends an email to the user.

+ * + * @param cms CmsObject + * @param password of the user + * @param user user to send mail to + * @param ou name + * @param newUser flag indicates if user is new + * @param changePassword has the user to change password? + * @param throwOnError if the mail error should be propagated + */ + protected static void sendMail( + CmsObject cms, + String password, + CmsUser user, + String ou, + boolean newUser, + boolean changePassword, + boolean throwOnError) { + if (CmsStringUtil.isEmptyOrWhitespaceOnly(user.getEmail())) { return; } @@ -708,6 +731,9 @@ protected static void sendMail( notification.send(); } catch (EmailException e) { LOG.error("Unable to send email with password", e); + if (throwOnError) { + throw new IllegalStateException("Unable to send email with password.", e); + } } } diff --git a/test/org/opencms/ui/apps/user/TestCmsCsvImportUtils.java b/test/org/opencms/ui/apps/user/TestCmsCsvImportUtils.java new file mode 100644 index 00000000000..0e364531748 --- /dev/null +++ b/test/org/opencms/ui/apps/user/TestCmsCsvImportUtils.java @@ -0,0 +1,259 @@ +/* + * This library is part of OpenCms - + * the Open Source Content Management System + * + * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * For further information about Alkacon Software GmbH & Co. KG, please see the + * company website: https://www.alkacon.com + * + * For further information about OpenCms, please see the + * project website: https://www.opencms.org + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.opencms.ui.apps.user; + +import org.opencms.file.CmsUser; +import org.opencms.main.OpenCmsCore; +import org.opencms.security.CmsDefaultPasswordHandler; +import org.opencms.security.CmsDefaultValidationHandler; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import junit.framework.TestCase; + +/** + * Tests CSV user import parsing.

+ */ +public class TestCmsCsvImportUtils extends TestCase { + + /** Default password for parser tests. */ + private static final String DEFAULT_PASSWORD = "generated"; + + /** + * Sets a private field value.

+ * + * @param target the target object + * @param fieldName the field name + * @param value the field value + * + * @throws Exception if setting the field fails + */ + private static void setField(Object target, String fieldName, Object value) throws Exception { + + Field field = OpenCmsCore.class.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(target, value); + } + + /** + * Tests empty user name rejection.

+ */ + public void testEmptyUserName() { + + IllegalArgumentException exception = expectIllegalArgument("name;firstname;lastname\n;Ana;López", true); + + assertEquals("CSV line 2 does not contain a user name.", exception.getMessage()); + } + + /** + * Tests missing name column rejection.

+ */ + public void testMissingNameColumn() { + + IllegalArgumentException exception = expectIllegalArgument( + "firstname;lastname;email\nAna;López;ana@example.com", + true); + + assertEquals("The mandatory CSV column 'name' is missing.", exception.getMessage()); + } + + /** + * Tests token normalization.

+ */ + public void testNormalizeToken() { + + assertNull(CmsCsvImportUtils.normalizeToken(null)); + assertEquals("name", CmsCsvImportUtils.normalizeToken("\ufeffname")); + assertEquals("name", CmsCsvImportUtils.normalizeToken("\"name\"")); + assertEquals("name", CmsCsvImportUtils.normalizeToken("\ufeff\"name\"")); + } + + /** + * Tests password import handling.

+ */ + public void testPasswordImportHandling() { + + CmsUser importedPassword = readOne("name;password\nuser1;csvPassword", true); + CmsUser defaultPassword = readOne("name;password\nuser1;csvPassword", false); + + assertEquals("csvPassword", importedPassword.getPassword()); + assertEquals(DEFAULT_PASSWORD, defaultPassword.getPassword()); + } + + /** + * Tests additional CSV columns.

+ */ + public void testReadAdditionalColumn() { + + CmsUser user = readOne("name;firstname;customField\nuser1;Ana;customValue"); + + assertEquals("Ana", user.getFirstname()); + assertEquals("customValue", user.getAdditionalInfo("customField")); + } + + /** + * Tests UTF-8 CSV with BOM and unquoted headers.

+ */ + public void testReadBomWithoutQuotedHeader() { + + CmsUser user = readOne("\ufeffname;firstname;lastname;email;password\nuser1;Ana;López;ana@example.com;"); + + assertEquals("user1", user.getName()); + assertEquals("Ana", user.getFirstname()); + } + + /** + * Tests UTF-8 CSV with BOM and quoted headers.

+ */ + public void testReadBomWithQuotedHeader() { + + CmsUser user = readOne( + "\ufeff\"name\";\"firstname\";\"lastname\";\"email\";\"password\"\nuser1;Ana;López;ana@example.com;"); + + assertEquals("user1", user.getName()); + assertEquals("Ana", user.getFirstname()); + } + + /** + * Tests incomplete CSV rows.

+ */ + public void testReadIncompleteRow() { + + CmsUser user = readOne("name;firstname;lastname;email\nuser1;Ana"); + + assertEquals("user1", user.getName()); + assertEquals("Ana", user.getFirstname()); + assertEquals("", user.getLastname()); + assertEquals("", user.getEmail()); + } + + /** + * Tests UTF-8 CSV without BOM.

+ */ + public void testReadUtf8WithoutBom() { + + CmsUser user = readOne("name;firstname;lastname;email;password\nuser1;Ana;López;ana@example.com;"); + + assertEquals("user1", user.getName()); + assertEquals("Ana", user.getFirstname()); + assertEquals("López", user.getLastname()); + assertEquals("ana@example.com", user.getEmail()); + assertEquals(DEFAULT_PASSWORD, user.getPassword()); + } + + /** + * Tests CSV without password column.

+ */ + public void testReadWithoutPasswordColumn() { + + CmsUser user = readOne("name;firstname;lastname;email\nuser1;Ana;López;ana@example.com"); + + assertEquals(DEFAULT_PASSWORD, user.getPassword()); + } + + /** + * Installs the minimal OpenCms handlers needed by CmsUser setters.

+ * + * @throws Exception if handler setup fails + */ + @Override + protected void setUp() throws Exception { + + super.setUp(); + Method getInstance = OpenCmsCore.class.getDeclaredMethod("getInstance"); + getInstance.setAccessible(true); + Object core = getInstance.invoke(null); + setField(core, "m_validationHandler", new CmsDefaultValidationHandler()); + setField(core, "m_passwordHandler", new CmsDefaultPasswordHandler()); + } + + /** + * Expects CSV parsing to fail with an illegal argument exception.

+ * + * @param csv the CSV text + * @param keepPasswordIfPossible true if CSV passwords should be imported + * + * @return the thrown exception + */ + private IllegalArgumentException expectIllegalArgument(String csv, boolean keepPasswordIfPossible) { + + try { + readUsers(csv, keepPasswordIfPossible); + fail("Expected IllegalArgumentException."); + return null; + } catch (IllegalArgumentException e) { + return e; + } + } + + /** + * Reads one user from CSV.

+ * + * @param csv the CSV text + * + * @return the parsed user + */ + private CmsUser readOne(String csv) { + + return readOne(csv, true); + } + + /** + * Reads one user from CSV.

+ * + * @param csv the CSV text + * @param keepPasswordIfPossible true if CSV passwords should be imported + * + * @return the parsed user + */ + private CmsUser readOne(String csv, boolean keepPasswordIfPossible) { + + List users = readUsers(csv, keepPasswordIfPossible); + assertEquals(1, users.size()); + return users.get(0); + } + + /** + * Reads users from CSV.

+ * + * @param csv the CSV text + * @param keepPasswordIfPossible true if CSV passwords should be imported + * + * @return the parsed users + */ + private List readUsers(String csv, boolean keepPasswordIfPossible) { + + return CmsCsvImportUtils.readUsers( + csv.getBytes(StandardCharsets.UTF_8), + DEFAULT_PASSWORD, + keepPasswordIfPossible); + } +}