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
+ *
+ * @param columns the normalized columns
+ * @param rawValues the raw values
+ * @param columnName the column name
+ *
+ * @return the value
+ */
+ private static String getValue(List
+ *
+ * @param rawHeader the raw header fields
+ *
+ * @return the normalized header fields
+ */
+ private static List
+ *
+ * @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
+ *
+ * @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
- */
+/** 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
- *
- * @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
- * @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
- * @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
- * @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
- *
- * @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
- *
- * @return List of user (with passwords)
- */
protected List
- *
- * @param parent layout
- * @param importCase boolean
- * @return List of group names
- */
private List
- *
- * @param parent layout
- * @param importCase boolean
- * @return List of roles
- */
private List
- *
- * @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
+ *
+ * @param createdUser the imported user
+ */
+ private void addGroups(CmsUser createdUser) {
+
+ Iterator
+ *
+ * @param createdUser the imported user
+ */
+ private void addRoles(CmsUser createdUser) {
+
+ Iterator
+ *
+ * @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
+ *
+ * @param csv the CSV text
+ * @param keepPasswordIfPossible true if CSV passwords should be imported
+ *
+ * @return the parsed users
+ */
+ private List