Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closes #2478: API to download CSV file with all registered users #2489

Merged
merged 4 commits into from
Jun 7, 2024
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@

import edu.harvard.iq.dataverse.api.annotations.ApiWriteOperation;
import edu.harvard.iq.dataverse.persistence.user.AuthenticatedUser;
import edu.harvard.iq.dataverse.userdata.AuthenticatedUserCsvWriter;
import edu.harvard.iq.dataverse.users.ChangeUserIdentifierService;
import edu.harvard.iq.dataverse.users.MergeInAccountService;

import javax.ejb.EJBException;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;

/**
*
Expand All @@ -25,6 +29,21 @@ public class Users extends AbstractApiBean {
@Inject
private MergeInAccountService mergeInAccountService;

@Inject
private AuthenticatedUserCsvWriter authenticatedUserCsvWriter;

@GET
@Produces({"text/csv"})
public Response listUsersCSV() throws WrappedResponse {
findSuperuserOrDie();

StreamingOutput csvContent = output -> authenticatedUserCsvWriter.write(output, authSvc.findAllAuthenticatedUsers());

return Response.ok(csvContent)
.header("Content-Disposition", "attachment; filename=\"authenticated-users.csv\"")
.build();
}

@POST
@ApiWriteOperation
@Path("{consumedIdentifier}/mergeIntoUser/{baseIdentifier}")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package edu.harvard.iq.dataverse.userdata;

import edu.harvard.iq.dataverse.common.Util;
import edu.harvard.iq.dataverse.persistence.user.AuthenticatedUser;
import edu.harvard.iq.dataverse.persistence.user.AuthenticatedUserLookup;
import io.vavr.control.Option;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ejb.Stateless;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;

/**
* Writes a CSV file with authenticated user details.
*/
@Stateless
public class AuthenticatedUserCsvWriter {

private static final Logger logger = LoggerFactory.getLogger(AuthenticatedUserCsvWriter.class);

// -------------------- LOGIC --------------------

public void write(OutputStream outputStream, List<AuthenticatedUser> authenticatedUsers) throws IOException {
try (Writer writer = new OutputStreamWriter(outputStream);
BufferedWriter streamWriter = new BufferedWriter(writer);
CSVPrinter csvPrinter = new CSVPrinter(streamWriter, CSVFormat.DEFAULT)) {

csvPrinter.printRecord(AuthenticatedUserCSVRecord.getHeaders());
for(AuthenticatedUser user : authenticatedUsers) {
csvPrinter.printRecord(buildRecord(user).getValues());
}
} catch (IOException ioe) {
logger.error("Couldn't write user data to csv", ioe);
throw ioe;
}
}

// -------------------- PRIVATE --------------------

private AuthenticatedUserCSVRecord buildRecord(AuthenticatedUser user) {
AuthenticatedUserCSVRecord record = new AuthenticatedUserCSVRecord();

record.setId(user.getId());
record.setUsername(user.getIdentifier());
record.setName(user.getDisplayInfo().getTitle());
record.setEmail(user.getEmail());
record.setAffiliation(user.getAffiliation());
record.setSuperuser(user.isSuperuser());
record.setAuthentication(user.getAuthenticatedUserLookup());
record.setVerificationStatus(user.getEmailConfirmed());
record.setNotificationLanguage(user.getNotificationsLanguage());
record.setAccountCreation(user.getCreatedTime());
record.setLastLogin(user.getLastLoginTime());
record.setLastApiUse(user.getLastApiUseTime());
return record;
}

// -------------------- INNER CLASSES --------------------

enum AuthenticatedUserCsvColumn {
ID("ID"),
USERNAME("Username"),
NAME("Name"),
EMAIL("Email"),
AFFILIATION("Affiliation"),
SUPERUSER("Superuser"),
AUTHENTICATION("Authentication"),
VERIFICATION_STATUS("Verification status"),
NOTIFICATION_LANGUAGE("Notification language"),
ACCOUNT_CREATION("Account creation"),
LAST_LOGIN("Last login"),
LAST_API_USE("Last API use");

final String columnName;

AuthenticatedUserCsvColumn(String columnName) {
this.columnName = columnName;
}

public String getColumnName() {
return columnName;
}
}

static class AuthenticatedUserCSVRecord {

private static final List<String> CSV_HEADERS = Arrays.stream(AuthenticatedUserCsvColumn.values())
.map(AuthenticatedUserCsvColumn::getColumnName)
.collect(Collectors.toList());

private Map<AuthenticatedUserCsvColumn, String> data = new HashMap<>();

public static List<String> getHeaders() {
return CSV_HEADERS;
}

public List<String> getValues() {
return Arrays.stream(AuthenticatedUserCsvColumn.values())
.map(data::get)
.collect(Collectors.toList());
}

public void setId(Long id) {
data.put(AuthenticatedUserCsvColumn.ID, safeToString(id));
}

public void setUsername(String username) {
data.put(AuthenticatedUserCsvColumn.USERNAME, safeToString(username));
}

public void setName(String name) {
data.put(AuthenticatedUserCsvColumn.NAME, safeToString(name));
}

public void setEmail(String email) {
data.put(AuthenticatedUserCsvColumn.EMAIL, safeToString(email));
}

public void setAffiliation(String affiliation) {
data.put(AuthenticatedUserCsvColumn.AFFILIATION, safeToString(affiliation));
}

public void setSuperuser(boolean superuser) {
data.put(AuthenticatedUserCsvColumn.SUPERUSER, Boolean.toString(superuser));
}

public void setAuthentication(AuthenticatedUserLookup lookup) {
data.put(AuthenticatedUserCsvColumn.AUTHENTICATION, Option.of(lookup)
.map(AuthenticatedUserLookup::getAuthenticationProviderId).getOrElse(""));
}

public void setVerificationStatus(Timestamp emailConfirmed) {
data.put(AuthenticatedUserCsvColumn.VERIFICATION_STATUS, Option.of(emailConfirmed)
.map(s -> "Verified").getOrElse("Not verified"));
}

public void setNotificationLanguage(Locale notificationsLanguage) {
data.put(AuthenticatedUserCsvColumn.NOTIFICATION_LANGUAGE, Option.of(notificationsLanguage)
.map(Locale::getLanguage).getOrElse(""));
}

public void setAccountCreation(Timestamp createdTime) {
data.put(AuthenticatedUserCsvColumn.ACCOUNT_CREATION, safeTimestampToString(createdTime));
}

public void setLastLogin(Timestamp lastLoginTime) {
data.put(AuthenticatedUserCsvColumn.LAST_LOGIN, safeTimestampToString(lastLoginTime));
}

public void setLastApiUse(Timestamp lastApiUseTime) {
data.put(AuthenticatedUserCsvColumn.LAST_API_USE, safeTimestampToString(lastApiUseTime));
}

// -------------------- PRIVATE --------------------

private String safeTimestampToString(Object object) {
return object != null ? Util.getDateTimeFormat().format(object) : null;
}

private String safeToString(Object object) {
return object != null ? object.toString() : "";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package edu.harvard.iq.dataverse.userdata;

import com.google.common.collect.Lists;
import edu.harvard.iq.dataverse.persistence.MocksFactory;
import edu.harvard.iq.dataverse.persistence.user.AuthenticatedUser;
import edu.harvard.iq.dataverse.persistence.user.AuthenticatedUserLookup;
import io.vavr.control.Option;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.function.Consumer;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class AuthenticatedUserCsvWriterTest {

private AuthenticatedUserCsvWriter csvWriter = new AuthenticatedUserCsvWriter();

// -------------------- TESTS --------------------

@Test
public void write() throws IOException {
// given
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
List<AuthenticatedUser> users = Lists.newArrayList(
defaultUser(1, "jo", "John", "Doe"),
defaultUser(2, "ma", "Mary", "Sue",
u -> u.setLastLoginTime(null), u -> u.setLastApiUseTime(null)),
defaultUser(3, "df", "Diana", "Flores",
u -> u.setSuperuser(true), u -> u.setNotificationsLanguage(Locale.FRENCH), u -> u.setAffiliation(null)),
defaultUser(4, "ba", "Barb", "Wire",
u -> u.setEmailConfirmed(null), u -> u.setAffiliation(null), u -> u.getAuthenticatedUserLookup().setAuthenticationProviderId("saml")),
defaultUser(5, "", "Mr.", "special\",\"char.txt",
u -> u.setCreatedTime(null), u -> u.setLastApiUseTime(null), u -> u.setLastLoginTime(null), u -> u.setEmail(""),
u -> u.setAffiliation(null), u -> u.setEmailConfirmed(null), u -> u.setNotificationsLanguage(null))
);

// when
csvWriter.write(outputStream, users);

// then
String[] csv = outputStream.toString().split("\r\n");
assertEquals(6, csv.length);
assertEquals("ID,Username,Name,Email,Affiliation,Superuser,Authentication,Verification status,Notification language,Account creation,Last login,Last API use", csv[0]);
assertEquals("1,@jo,John Doe,[email protected],UnitTester,false,buildIn,Verified,en,2024-05-28T01:01:01Z,2024-05-28T02:02:02Z,2024-05-28T03:03:03Z", csv[1]);
assertEquals("2,@ma,Mary Sue,[email protected],UnitTester,false,buildIn,Verified,en,2024-05-28T01:01:01Z,,", csv[2]);
assertEquals("3,@df,Diana Flores,[email protected],,true,buildIn,Verified,fr,2024-05-28T01:01:01Z,2024-05-28T02:02:02Z,2024-05-28T03:03:03Z", csv[3]);
assertEquals("4,@ba,Barb Wire,[email protected],,false,saml,Not verified,en,2024-05-28T01:01:01Z,2024-05-28T02:02:02Z,2024-05-28T03:03:03Z", csv[4]);
assertEquals("5,@,\"Mr. special\"\",\"\"char.txt\",,,false,buildIn,Not verified,,,,", csv[5]);
}

@Test
public void write__empty_list() throws IOException {
// when
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
csvWriter.write(outputStream, Collections.emptyList());

// then
String[] csv = outputStream.toString().split("\r\n");
assertEquals(1, csv.length);
assertEquals("ID,Username,Name,Email,Affiliation,Superuser,Authentication,Verification status,Notification language,Account creation,Last login,Last API use", csv[0]);
}

// -------------------- PRIVATE --------------------

@SafeVarargs
private static AuthenticatedUser defaultUser(int id, String username, String firstName, String lastname, Consumer<AuthenticatedUser>... overrides) {
AuthenticatedUser user = MocksFactory.makeAuthenticatedUser(firstName, lastname);
user.setId((long) id);
user.setUserIdentifier(username);
user.setAuthenticatedUserLookup(new AuthenticatedUserLookup());
user.getAuthenticatedUserLookup().setAuthenticationProviderId("buildIn");
user.getAuthenticatedUserLookup().setPersistentUserId(String.valueOf(id));
user.setCreatedTime(Timestamp.valueOf("2024-05-28 01:01:01"));
user.setLastLoginTime(Timestamp.valueOf("2024-05-28 02:02:02"));
user.setLastApiUseTime(Timestamp.valueOf("2024-05-28 03:03:03"));
user.setEmailConfirmed(Timestamp.valueOf("2024-05-28 04:04:04"));

Option.of(overrides).forEach(o -> Arrays.stream(o).forEach(override -> override.accept(user)));

return user;
}
}
Loading