Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/org/opencms/ui/apps/Messages.java
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
12 changes: 11 additions & 1 deletion src/org/opencms/ui/apps/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
GUI_APP_RELOAD_0=Refresh
264 changes: 264 additions & 0 deletions src/org/opencms/ui/apps/user/CmsCsvImportUtils.java
Original file line number Diff line number Diff line change
@@ -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.<p>
*/
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.<p>
*
* @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.<p>
*
* @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<CmsUser> readUsers(byte[] data, String defaultPassword, boolean keepPasswordIfPossible) {

List<CmsUser> users = new ArrayList<CmsUser>();
List<String> 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<String> 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.<p>
*
* @param columns the normalized columns
* @param rawValues the raw values
* @param columnName the column name
*
* @return the value
*/
private static String getValue(List<String> columns, List<String> 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.<p>
*
* @param rawHeader the raw header fields
*
* @return the normalized header fields
*/
private static List<String> normalizeHeader(List<String> rawHeader) {

List<String> result = new ArrayList<String>();
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.<p>
*
* @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<String> columns,
List<String> 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.<p>
*
* @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.<p>
*
* @param columns the normalized columns
*/
private static void validateHeader(List<String> columns) {

if (!columns.contains("name")) {
throw new IllegalArgumentException("The mandatory CSV column 'name' is missing.");
}
}
}
Loading