From 503fe9fa463e604edbe641067f3caef20a50569c Mon Sep 17 00:00:00 2001 From: Alex Borodin Date: Mon, 30 Apr 2018 15:40:38 +0200 Subject: [PATCH] fix(users): handle external change of user email address by storing previously known email addresses and falling back to search by former addresses when necessary closes eclipse/sw360#242 --- .../sw360/datahandler/db/UserRepository.java | 33 ++++++- .../org/eclipse/sw360/users/UserHandler.java | 20 +++- .../sw360/users/db/UserDatabaseHandler.java | 9 +- .../portal/common/CustomFieldHelper.java | 12 +-- .../portal/portlets/admin/UserPortlet.java | 52 +++------- .../portal/portlets/signup/SignupPortlet.java | 2 +- .../portal/users/PortletRequestAdapter.java | 6 +- .../sw360/portal/users/SSOAutoLogin.java | 78 ++++++++------- .../eclipse/sw360/portal/users/UserCache.java | 24 ++--- .../eclipse/sw360/portal/users/UserUtils.java | 98 ++++++++++++------- .../permissions/UserPermissions.java | 3 +- .../src/main/thrift/users.thrift | 6 ++ .../core/JacksonCustomizations.java | 3 + .../resourceserver/restdocs/UserSpecTest.java | 3 + 14 files changed, 201 insertions(+), 148 deletions(-) diff --git a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/UserRepository.java b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/UserRepository.java index f676aadd7e..0e59983617 100644 --- a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/UserRepository.java +++ b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/UserRepository.java @@ -11,13 +11,16 @@ package org.eclipse.sw360.datahandler.db; import org.eclipse.sw360.components.summary.UserSummary; +import org.eclipse.sw360.datahandler.common.CommonUtils; import org.eclipse.sw360.datahandler.couchdb.DatabaseConnector; import org.eclipse.sw360.datahandler.couchdb.SummaryAwareRepository; import org.eclipse.sw360.datahandler.thrift.users.User; import org.ektorp.support.View; +import org.ektorp.support.Views; import java.util.Collection; import java.util.List; +import java.util.Set; /** * CRUD access for the User class @@ -26,7 +29,21 @@ * @author Johannes.Najjar@tngtech.com */ -@View(name = "all", map = "function(doc) { if (doc.type == 'user') emit(null, doc._id) }") +@Views({ + @View(name = "all", + map = "function(doc) { if (doc.type == 'user') emit(null, doc._id) }"), + @View(name = "byExternalId", + map = "function(doc) { if (doc.type == 'user') emit(doc.externalid, doc._id) }"), + @View(name = "byFormerEmails", + map = "function(doc) {" + + " if (doc.type == 'user' && doc.formerEmailAddresses && Array.isArray(doc.formerEmailAddresses)) {" + + " var arr = doc.formerEmailAddresses;" + + " for (var i = 0; i < arr.length; i++){" + + " emit(arr[i], doc._id);" + + " }" + + " }" + + "}") +}) public class UserRepository extends SummaryAwareRepository { public UserRepository(DatabaseConnector databaseConnector) { super(User.class, databaseConnector, new UserSummary()); @@ -37,4 +54,18 @@ public UserRepository(DatabaseConnector databaseConnector) { public List get(Collection ids) { return getConnector().get(User.class, ids, true); } + + public User getByExternalId(String externalId) { + final Set userIds = queryForIdsAsValue("byExternalId", externalId); + if (userIds != null && !userIds.isEmpty()) + return get(CommonUtils.getFirst(userIds)); + return null; + } + + public User getByFormerEmail(String email) { + final Set userIds = queryForIdsAsValue("byFormerEmails", email); + if (userIds != null && !userIds.isEmpty()) + return get(CommonUtils.getFirst(userIds)); + return null; + } } diff --git a/backend/src/src-users/src/main/java/org/eclipse/sw360/users/UserHandler.java b/backend/src/src-users/src/main/java/org/eclipse/sw360/users/UserHandler.java index f443031481..da9b05bfa9 100644 --- a/backend/src/src-users/src/main/java/org/eclipse/sw360/users/UserHandler.java +++ b/backend/src/src-users/src/main/java/org/eclipse/sw360/users/UserHandler.java @@ -10,16 +10,15 @@ */ package org.eclipse.sw360.users; +import org.apache.log4j.Logger; +import org.apache.thrift.TException; import org.eclipse.sw360.datahandler.common.DatabaseSettings; import org.eclipse.sw360.datahandler.thrift.RequestStatus; import org.eclipse.sw360.datahandler.thrift.users.User; import org.eclipse.sw360.datahandler.thrift.users.UserService; import org.eclipse.sw360.users.db.UserDatabaseHandler; -import org.apache.log4j.Logger; -import org.apache.thrift.TException; import java.io.IOException; -import java.net.MalformedURLException; import java.util.List; import static org.eclipse.sw360.datahandler.common.SW360Assert.assertNotEmpty; @@ -50,7 +49,20 @@ public User getByEmail(String email) throws TException { // Get user from database User user = db.getByEmail(email); if (user == null) { - log.info("User does not exist in DB"); + log.info("User not found by email. Falling back to former emails."); + user = db.getByFormerEmail(email); + if (user == null) { + log.info("User not found by former email"); + } + } + return user; + } + + @Override + public User getByEmailOrExternalId(String email, String externalId) throws TException { + User user = getByEmail(email); + if (user == null) { + user = db.getByExternalId(externalId); } return user; } diff --git a/backend/src/src-users/src/main/java/org/eclipse/sw360/users/db/UserDatabaseHandler.java b/backend/src/src-users/src/main/java/org/eclipse/sw360/users/db/UserDatabaseHandler.java index 147ce20f64..79e7591550 100644 --- a/backend/src/src-users/src/main/java/org/eclipse/sw360/users/db/UserDatabaseHandler.java +++ b/backend/src/src-users/src/main/java/org/eclipse/sw360/users/db/UserDatabaseHandler.java @@ -21,7 +21,6 @@ import org.ektorp.http.HttpClient; import java.io.IOException; -import java.net.MalformedURLException; import java.util.List; import java.util.function.Supplier; @@ -52,6 +51,10 @@ public User getByEmail(String email) { return db.get(User.class, email); } + public User getByFormerEmail(String email) { + return repository.getByFormerEmail(email); + } + private void prepareUser(User user) throws SW360Exception { // Prepare component for database ThriftValidate.prepareUser(user); @@ -87,4 +90,8 @@ public List getAll() { public List searchUsers(String searchText) { return userSearch.searchByNameAndEmail(searchText); } + + public User getByExternalId(String externalId) { + return repository.getByExternalId(externalId); + } } diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/CustomFieldHelper.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/CustomFieldHelper.java index a49c5870a3..12ee75a8ac 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/CustomFieldHelper.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/CustomFieldHelper.java @@ -13,15 +13,12 @@ import com.liferay.portal.kernel.exception.PortalException; import com.liferay.portal.kernel.exception.SystemException; -import com.liferay.portal.kernel.util.WebKeys; import com.liferay.portal.model.ResourceConstants; import com.liferay.portal.model.Role; import com.liferay.portal.model.RoleConstants; import com.liferay.portal.security.permission.ActionKeys; import com.liferay.portal.service.ResourcePermissionLocalServiceUtil; import com.liferay.portal.service.RoleLocalServiceUtil; -import com.liferay.portal.service.UserLocalServiceUtil; -import com.liferay.portal.theme.ThemeDisplay; import com.liferay.portlet.expando.model.ExpandoBridge; import com.liferay.portlet.expando.model.ExpandoColumn; import com.liferay.portlet.expando.model.ExpandoColumnConstants; @@ -29,6 +26,7 @@ import com.liferay.portlet.expando.service.ExpandoColumnLocalServiceUtil; import org.apache.log4j.Logger; import org.eclipse.sw360.datahandler.thrift.users.User; +import org.eclipse.sw360.portal.users.UserUtils; import javax.portlet.PortletRequest; @@ -83,19 +81,13 @@ public static Optional loadField(Class type, Port } private static ExpandoBridge getUserExpandoBridge(PortletRequest request, User user) throws PortalException, SystemException { - com.liferay.portal.model.User liferayUser = getLiferayUser(request, user); + com.liferay.portal.model.User liferayUser = UserUtils.findLiferayUser(request, user); ensureUserCustomFieldExists(liferayUser, CUSTOM_FIELD_PROJECT_GROUP_FILTER, ExpandoColumnConstants.STRING); ensureUserCustomFieldExists(liferayUser, CUSTOM_FIELD_COMPONENTS_VIEW_SIZE, ExpandoColumnConstants.INTEGER); ensureUserCustomFieldExists(liferayUser, CUSTOM_FIELD_VULNERABILITIES_VIEW_SIZE, ExpandoColumnConstants.INTEGER); return liferayUser.getExpandoBridge(); } - private static com.liferay.portal.model.User getLiferayUser(PortletRequest request, User user) throws PortalException, SystemException { - ThemeDisplay themeDisplay = (ThemeDisplay) request.getAttribute(WebKeys.THEME_DISPLAY); - long companyId = themeDisplay.getCompanyId(); - return UserLocalServiceUtil.getUserByEmailAddress(companyId, user.email); - } - private static void ensureUserCustomFieldExists(com.liferay.portal.model.User liferayUser, String customFieldName, int customFieldType) throws PortalException, SystemException { ExpandoBridge exp = liferayUser.getExpandoBridge(); if (!exp.hasAttribute(customFieldName)) { diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/admin/UserPortlet.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/admin/UserPortlet.java index bc39d4b1a0..0bc66124e1 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/admin/UserPortlet.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/admin/UserPortlet.java @@ -19,14 +19,16 @@ import com.liferay.portal.kernel.exception.SystemException; import com.liferay.portal.kernel.portlet.PortletResponseUtil; import com.liferay.portal.kernel.upload.UploadPortletRequest; -import com.liferay.portal.kernel.util.WebKeys; import com.liferay.portal.model.*; -import com.liferay.portal.model.User; import com.liferay.portal.service.*; -import com.liferay.portal.theme.ThemeDisplay; import com.liferay.portal.util.PortalUtil; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVPrinter; +import org.apache.commons.csv.CSVRecord; +import org.apache.log4j.Logger; +import org.apache.thrift.TException; import org.eclipse.sw360.datahandler.common.CommonUtils; -import org.eclipse.sw360.datahandler.thrift.SW360Exception; import org.eclipse.sw360.datahandler.thrift.users.UserGroup; import org.eclipse.sw360.datahandler.thrift.users.UserService; import org.eclipse.sw360.portal.common.PortalConstants; @@ -35,12 +37,6 @@ import org.eclipse.sw360.portal.users.UserCSV; import org.eclipse.sw360.portal.users.UserCacheHolder; import org.eclipse.sw360.portal.users.UserUtils; -import org.apache.commons.csv.CSVFormat; -import org.apache.commons.csv.CSVParser; -import org.apache.commons.csv.CSVPrinter; -import org.apache.commons.csv.CSVRecord; -import org.apache.log4j.Logger; -import org.apache.thrift.TException; import javax.portlet.*; import java.io.*; @@ -222,19 +218,13 @@ public void backUpUsers(ResourceRequest request, ResourceResponse response) thro } @UsedAsLiferayAction - public void updateUsers(ActionRequest request, ActionResponse response) throws PortletException, IOException { + public void updateUsers(ActionRequest request, ActionResponse response) throws IOException { - List users; - try { - users = getUsersFromRequest(request, "file"); - } catch (TException e) { - log.error("Error processing csv file", e); - users = Collections.emptyList(); - } + List users = getUsersFromRequest(request, "file"); try { createOrganizations(request, users); - } catch (SW360Exception | SystemException | PortalException e) { + } catch (SystemException | PortalException e) { log.error("Error creating organizations", e); } @@ -251,7 +241,7 @@ private String extractHeadDept(String input) { } - private void createOrganizations(PortletRequest request, List users) throws SW360Exception, SystemException, PortalException { + private void createOrganizations(PortletRequest request, List users) throws SystemException, PortalException { /* Find the departments of the users, create the head departments and then create the organizations */ @@ -260,13 +250,12 @@ private void createOrganizations(PortletRequest request, List users) th createOrganizations(request, departments); } - public void createOrganizations(PortletRequest request, Iterable departments) throws PortalException, SystemException { + private void createOrganizations(PortletRequest request, Iterable departments) throws PortalException, SystemException { ImmutableSet headDepartments = FluentIterable.from(departments).transform(department -> extractHeadDept(department)).toSet(); Map organizationIds = new HashMap<>(); ServiceContext serviceContext = ServiceContextFactory.getInstance(request); - ThemeDisplay themeDisplay = (ThemeDisplay) request.getAttribute(WebKeys.THEME_DISPLAY); - long companyId = themeDisplay.getCompanyId(); + long companyId = UserUtils.getCompanyId(request); for (String headDepartment : headDepartments) { long organizationId; @@ -311,20 +300,7 @@ private Organization createOrganization(ServiceContext serviceContext, String he ); } - private User getCurrentUser(PortletRequest request) throws SW360Exception { - User user; - ThemeDisplay themeDisplay = (ThemeDisplay) request.getAttribute(WebKeys.THEME_DISPLAY); - - if (themeDisplay.isSignedIn()) - user = themeDisplay.getUser(); - else { - throw new SW360Exception("Broken portlet!"); - } - return user; - } - - - private List getUsersFromRequest(PortletRequest request, String fileUploadFormId) throws IOException, TException { + private List getUsersFromRequest(PortletRequest request, String fileUploadFormId) throws IOException { final UploadPortletRequest uploadPortletRequest = PortalUtil.getUploadPortletRequest(request); @@ -362,7 +338,7 @@ private User dealWithUser(PortletRequest request, UserCSV userRec) { try { user = userRec.addLifeRayUser(request); if (user != null) { - UserUtils.synchronizeUserWithDatabase(userRec, thriftClients, userRec::getEmail, UserUtils::fillThriftUserFromUserCSV); + UserUtils.synchronizeUserWithDatabase(userRec, thriftClients, userRec::getEmail, userRec::getGid, UserUtils::fillThriftUserFromUserCSV); } } catch (SystemException | PortalException e) { log.error("Error creating a new user", e); diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/signup/SignupPortlet.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/signup/SignupPortlet.java index 3ac94fdf67..d071d180f9 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/signup/SignupPortlet.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/signup/SignupPortlet.java @@ -101,7 +101,7 @@ private User createUser(Registrant registrant, PortletRequest request) { try { com.liferay.portal.model.User liferayUser = registrant.addLifeRayUser(request); if (liferayUser != null) { - user = UserUtils.synchronizeUserWithDatabase(registrant, thriftClients, registrant::getEmail, UserUtils::fillThriftUserFromThriftUser); + user = UserUtils.synchronizeUserWithDatabase(registrant, thriftClients, registrant::getEmail, registrant::getExternalid, UserUtils::fillThriftUserFromThriftUser); } } catch (PortalException | SystemException e) { log.error(e); diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/users/PortletRequestAdapter.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/users/PortletRequestAdapter.java index 3c21639fec..447ed49540 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/users/PortletRequestAdapter.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/users/PortletRequestAdapter.java @@ -13,13 +13,10 @@ import com.liferay.portal.kernel.exception.PortalException; import com.liferay.portal.kernel.exception.SystemException; import com.liferay.portal.kernel.servlet.SessionMessages; -import com.liferay.portal.kernel.util.WebKeys; import com.liferay.portal.service.ServiceContext; import com.liferay.portal.service.ServiceContextFactory; -import com.liferay.portal.theme.ThemeDisplay; import javax.portlet.PortletRequest; -import javax.servlet.http.HttpServletRequest; import java.util.Optional; import java.util.function.Consumer; @@ -33,8 +30,7 @@ class PortletRequestAdapter implements RequestAdapter { @Override public long getCompanyId() { - ThemeDisplay themeDisplay = (ThemeDisplay) request.getAttribute(WebKeys.THEME_DISPLAY); - return themeDisplay.getCompanyId(); + return UserUtils.getCompanyId(request); } @Override diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/users/SSOAutoLogin.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/users/SSOAutoLogin.java index 459b9a766e..8331a6949b 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/users/SSOAutoLogin.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/users/SSOAutoLogin.java @@ -18,9 +18,9 @@ import com.liferay.portal.model.User; import com.liferay.portal.security.auth.AutoLogin; import com.liferay.portal.security.auth.AutoLoginException; -import com.liferay.portal.service.UserLocalServiceUtil; import com.liferay.portal.util.PortalUtil; import org.eclipse.sw360.datahandler.common.CommonUtils; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,31 +43,29 @@ public class SSOAutoLogin implements AutoLogin { private static final Logger log = LoggerFactory.getLogger(SSOAutoLogin.class); public static final String PROPERTIES_FILE_PATH = "/sw360.properties"; - private Properties props; - + public static final String AUTH_EMAIL_KEY = "key.auth.email"; - public static String authEmailHeader = "EMAIL"; + public static final String AUTH_EMAIL_HEADER; public static final String AUTH_EXTID_KEY = "key.auth.extid"; - public static String authExtidHeader = "EXTID"; + public static final String AUTH_EXTID_HEADER; public static final String AUTH_GIVEN_NAME_KEY = "key.auth.givenname"; - public static String authGivenNameHeader = "GIVENNAME"; + public static final String AUTH_GIVEN_NAME_HEADER; public static final String AUTH_SURNAME_KEY = "key.auth.surname"; - public static String authSurnameHeader = "SURNAME"; + public static final String AUTH_SURNAME_HEADER; public static final String AUTH_DEPARTMENT_KEY = "key.auth.department"; - public static String authDepartmentHeader = "DEPARTMENT"; + public static final String AUTH_DEPARTMENT_HEADER; private static final OrganizationHelper orgHelper = new OrganizationHelper(); - public SSOAutoLogin() { - super(); + static { Properties props = CommonUtils.loadProperties(SSOAutoLogin.class, PROPERTIES_FILE_PATH); - authEmailHeader = props.getProperty(AUTH_EMAIL_KEY, authEmailHeader); - authExtidHeader = props.getProperty(AUTH_EXTID_KEY, authExtidHeader); - authGivenNameHeader = props.getProperty(AUTH_GIVEN_NAME_KEY, authGivenNameHeader); - authSurnameHeader = props.getProperty(AUTH_SURNAME_KEY, authSurnameHeader); - authDepartmentHeader = props.getProperty(AUTH_DEPARTMENT_KEY, authDepartmentHeader); - log.info(String.format("Expecting the following header values for auto login email: '%s', external ID: '%s', given name: '%s', surname: '%s', group: %s", - authEmailHeader, authExtidHeader, authGivenNameHeader, authSurnameHeader, authDepartmentHeader)); + AUTH_EMAIL_HEADER = props.getProperty(AUTH_EMAIL_KEY, "EMAIL"); + AUTH_EXTID_HEADER = props.getProperty(AUTH_EXTID_KEY, "EXTID"); + AUTH_GIVEN_NAME_HEADER = props.getProperty(AUTH_GIVEN_NAME_KEY, "GIVENNAME"); + AUTH_SURNAME_HEADER = props.getProperty(AUTH_SURNAME_KEY, "SURNAME"); + AUTH_DEPARTMENT_HEADER = props.getProperty(AUTH_DEPARTMENT_KEY, "DEPARTMENT"); + log.info("Expecting the following header values for auto login email: '%s', external ID: '%s', given name: '%s', surname: '%s', group: %s", + AUTH_EMAIL_HEADER, AUTH_EXTID_HEADER, AUTH_GIVEN_NAME_HEADER, AUTH_SURNAME_HEADER, AUTH_DEPARTMENT_HEADER); } @Override @@ -78,14 +76,14 @@ public String[] handleException(HttpServletRequest request, HttpServletResponse @Override public String[] login(HttpServletRequest request, HttpServletResponse response) throws AutoLoginException { - String emailId = request.getHeader(authEmailHeader); - String extid = request.getHeader(authExtidHeader); - String givenName = request.getHeader(authGivenNameHeader); - String surname = request.getHeader(authSurnameHeader); - String department = request.getHeader(authDepartmentHeader); + String emailId = request.getHeader(AUTH_EMAIL_HEADER); + String extid = request.getHeader(AUTH_EXTID_HEADER); + String givenName = request.getHeader(AUTH_GIVEN_NAME_HEADER); + String surname = request.getHeader(AUTH_SURNAME_HEADER); + String department = request.getHeader(AUTH_DEPARTMENT_HEADER); - log.info(String.format("Attempting auto login for email: '%s', external ID: '%s', given name: '%s', surname: '%s', group: %s", - emailId, extid, givenName, surname, department)); + log.info("Attempting auto login for email: '%s', external ID: '%s', given name: '%s', surname: '%s', group: %s", + emailId, extid, givenName, surname, department); dumpHeadersToLog(request); @@ -99,24 +97,14 @@ public String[] login(HttpServletRequest request, HttpServletResponse response) String organizationName = orgHelper.mapOrganizationName(department); Organization organization = orgHelper.addOrGetOrganization(organizationName, companyId); - log.info(String.format("Mapped orgcode %s to %s", department, organizationName)); + log.info("Mapped orgcode %s to %s", department, organizationName); User user; try { - user = UserLocalServiceUtil.getUserByEmailAddress(companyId, emailId); + user = UserUtils.findLiferayUser(new HttpServletRequestAdapter(request), emailId, extid); } catch (NoSuchUserException e) { - log.error("Could not find user with email: '" + emailId + "'. Will create one with a random password."); - String password = UUID.randomUUID().toString(); - - user = UserPortletUtils.addLiferayUser(request, givenName, surname, emailId, - organizationName, RoleConstants.USER, false, extid, password, false, true); - if (user == null) { - throw new AutoLoginException("Couldn't create user for '" + emailId + "' and company id: '" + companyId + "'"); - } - log.info("Created user " + user); + user = createLiferayUser(request, emailId, extid, givenName, surname, companyId, organizationName); } - orgHelper.reassignUserToOrganizationIfNecessary(user, organization); - // Create a return credentials object return new String[]{ String.valueOf(user.getUserId()), @@ -129,12 +117,26 @@ public String[] login(HttpServletRequest request, HttpServletResponse response) } } + @NotNull + public User createLiferayUser(HttpServletRequest request, String emailId, String extid, String givenName, String surname, long companyId, String organizationName) throws SystemException, PortalException { + User user; + String password = UUID.randomUUID().toString(); + + user = UserPortletUtils.addLiferayUser(request, givenName, surname, emailId, + organizationName, RoleConstants.USER, false, extid, password, false, true); + if (user == null) { + throw new AutoLoginException("Couldn't create user for '" + emailId + "' and company id: '" + companyId + "'"); + } + log.info("Created user %s", user); + return user; + } + private void dumpHeadersToLog(HttpServletRequest request) { Enumeration headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String key = (String) headerNames.nextElement(); String value = request.getHeader(key); - log.debug(key + ":" + value); + log.debug("%s:%s", key, value); } } } diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/users/UserCache.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/users/UserCache.java index b5017be0d0..cc58771f73 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/users/UserCache.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/users/UserCache.java @@ -10,15 +10,14 @@ */ package org.eclipse.sw360.portal.users; -import com.google.common.base.Function; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.Maps; +import org.apache.thrift.TException; import org.eclipse.sw360.datahandler.thrift.ThriftClients; import org.eclipse.sw360.datahandler.thrift.users.User; import org.eclipse.sw360.datahandler.thrift.users.UserService; -import org.apache.thrift.TException; import java.util.Collections; import java.util.List; @@ -51,13 +50,8 @@ public UserCache() { .expireAfterWrite(1, TimeUnit.DAYS) .build(loader); - if(allUsers.size()>0) { - cache.putAll(Maps.uniqueIndex(allUsers, new Function() { - @Override - public String apply(User input) { - return input.getEmail(); - } - })); + if(!allUsers.isEmpty()) { + cache.putAll(Maps.uniqueIndex(allUsers, User::getEmail)); } } @@ -72,17 +66,19 @@ public User getRefreshed (String email) throws ExecutionException { private static class UserLoader extends CacheLoader { + private UserService.Iface createUserClient() { + ThriftClients thriftClients = new ThriftClients(); + return thriftClients.makeUserClient(); + } + @Override public User load(String email) throws TException { - ThriftClients thriftClients = new ThriftClients(); - UserService.Iface client = thriftClients.makeUserClient(); + UserService.Iface client = createUserClient(); return client.getByEmail(email); } private List getAllUsers() throws TException { - ThriftClients thriftClients = new ThriftClients(); - UserService.Iface client = thriftClients.makeUserClient(); - + UserService.Iface client = createUserClient(); return client.getAllUsers(); } } diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/users/UserUtils.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/users/UserUtils.java index 654f343f8a..4b2879e0a9 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/users/UserUtils.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/users/UserUtils.java @@ -11,6 +11,7 @@ */ package org.eclipse.sw360.portal.users; +import com.liferay.portal.NoSuchUserException; import com.liferay.portal.kernel.exception.PortalException; import com.liferay.portal.kernel.exception.SystemException; import com.liferay.portal.kernel.util.WebKeys; @@ -26,17 +27,16 @@ import org.eclipse.sw360.portal.common.PortalConstants; import org.apache.log4j.Logger; import org.apache.thrift.TException; +import org.jetbrains.annotations.NotNull; import javax.portlet.PortletRequest; import javax.portlet.RenderRequest; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.function.BiConsumer; import java.util.function.Supplier; import java.util.stream.Collectors; +import static org.eclipse.sw360.datahandler.common.CommonUtils.nullToEmptySet; import static org.eclipse.sw360.datahandler.common.SW360Constants.TYPE_USER; /** @@ -58,32 +58,56 @@ public UserUtils() { thriftClients = new ThriftClients(); } - public static org.eclipse.sw360.datahandler.thrift.users.User synchronizeUserWithDatabase(T source, ThriftClients thriftClients, Supplier emailSupplier, BiConsumer synchronizer) { + public static org.eclipse.sw360.datahandler.thrift.users.User synchronizeUserWithDatabase( + T source, ThriftClients thriftClients, Supplier emailSupplier, + Supplier extIdSupplier, BiConsumer synchronizer) { UserService.Iface client = thriftClients.makeUserClient(); - org.eclipse.sw360.datahandler.thrift.users.User thriftUser = null; + org.eclipse.sw360.datahandler.thrift.users.User existingThriftUser = null; + String email = emailSupplier.get(); try { - thriftUser = client.getByEmail(emailSupplier.get()); + existingThriftUser = client.getByEmailOrExternalId(email, extIdSupplier.get()); } catch (TException e) { //This occurs for every new user, so there is not necessarily something wrong - log.trace("User does not exist in DB yet."); + log.trace("User not found by email or external ID"); } + org.eclipse.sw360.datahandler.thrift.users.User resultUser = null; try { - if (thriftUser == null) { + if (existingThriftUser == null) { log.info("Creating new user."); - thriftUser = new org.eclipse.sw360.datahandler.thrift.users.User(); - synchronizer.accept(thriftUser, source); - client.addUser(thriftUser); + resultUser = new org.eclipse.sw360.datahandler.thrift.users.User(); + synchronizer.accept(resultUser, source); + client.addUser(resultUser); } else { - synchronizer.accept(thriftUser, source); - client.updateUser(thriftUser); + if (existingThriftUser.getEmail().equals(email)){ + resultUser = existingThriftUser; + synchronizer.accept(resultUser, source); + client.updateUser(resultUser); + } else { // email has changed + org.eclipse.sw360.datahandler.thrift.users.User newThriftUser = new org.eclipse.sw360.datahandler.thrift.users.User(); + synchronizer.accept(newThriftUser, source); + newThriftUser.setFormerEmailAddresses(prepareFormerEmailAddresses(existingThriftUser, email)); + + client.addUser(newThriftUser); + client.deleteUser(existingThriftUser, existingThriftUser); + resultUser = newThriftUser; + } } } catch (TException e) { log.error("Thrift exception when saving the user", e); } - return thriftUser; + return resultUser; + } + + @NotNull + private static Set prepareFormerEmailAddresses(org.eclipse.sw360.datahandler.thrift.users.User thriftUser, String email) { + Set formerEmailAddresses = nullToEmptySet(thriftUser.getFormerEmailAddresses()).stream() + .filter(e -> !e.equals(email)) // make sure the current email is not in the former addresses + .collect(Collectors.toCollection(HashSet::new)); + formerEmailAddresses.add(thriftUser.getEmail()); + return formerEmailAddresses; } public static String displayUser(String email, org.eclipse.sw360.datahandler.thrift.users.User user) { @@ -108,52 +132,56 @@ public static List getOrganizations(RenderRequest request) { return organizations; } - public static void activateLiferayUser(PortletRequest request, org.eclipse.sw360.datahandler.thrift.users.User user){ - Optional liferayUser = findLiferayUser(request, user); + public static void activateLiferayUser(PortletRequest request, org.eclipse.sw360.datahandler.thrift.users.User user) { try { - if (liferayUser.isPresent()) { - UserLocalServiceUtil.updateStatus(liferayUser.get().getUserId(), WorkflowConstants.STATUS_APPROVED); - } + User liferayUser = findLiferayUser(request, user); + UserLocalServiceUtil.updateStatus(liferayUser.getUserId(), WorkflowConstants.STATUS_APPROVED); } catch (SystemException | PortalException e) { log.error("Could not activate Liferay user", e); } } - public static void deleteLiferayUser(PortletRequest request, org.eclipse.sw360.datahandler.thrift.users.User user){ - Optional liferayUser = findLiferayUser(request, user); + public static void deleteLiferayUser(PortletRequest request, org.eclipse.sw360.datahandler.thrift.users.User user) { try { - if (liferayUser.isPresent()){ - UserLocalServiceUtil.deleteUser(liferayUser.get()); - } + User liferayUser = findLiferayUser(request, user); + UserLocalServiceUtil.deleteUser(liferayUser); } catch (PortalException | SystemException e) { log.error("Could not delete Liferay user", e); } } - private static Optional findLiferayUser(PortletRequest request, org.eclipse.sw360.datahandler.thrift.users.User user) { - long companyId = getCompanyId(request); - User liferayUser = null; + public static User findLiferayUser(PortletRequest request, org.eclipse.sw360.datahandler.thrift.users.User user) throws PortalException, SystemException { + return findLiferayUser(new PortletRequestAdapter(request), user.getEmail(), user.getExternalid()); + } + + public static User findLiferayUser(RequestAdapter requestAdapter, String email, String externalId) throws SystemException, PortalException { + long companyId = requestAdapter.getCompanyId(); try { - liferayUser = UserLocalServiceUtil.getUserByEmailAddress(companyId, user.getEmail()); - } catch (PortalException | SystemException e) { - log.error("Could not find Liferay user", e); + return UserLocalServiceUtil.getUserByEmailAddress(companyId, email); + } catch (NoSuchUserException e) { + log.info("Could not find user with email: '" + email + "'. Will try searching by external id."); + try { + return UserLocalServiceUtil.getUserByOpenId(companyId, externalId); + } catch (NoSuchUserException nsue) { + log.info("Could not find user with externalId: '" + externalId); + throw new NoSuchUserException("Couldn't find user either with email or external id", nsue); + } } - return Optional.ofNullable(liferayUser); } - private static long getCompanyId(PortletRequest request) { + + public static long getCompanyId(PortletRequest request) { ThemeDisplay themeDisplay = (ThemeDisplay) request.getAttribute(WebKeys.THEME_DISPLAY); return themeDisplay.getCompanyId(); } public void synchronizeUserWithDatabase(User user) { String userEmailAddress = user.getEmailAddress(); - org.eclipse.sw360.datahandler.thrift.users.User refreshed = UserCacheHolder.getRefreshedUserFromEmail(userEmailAddress); if (!equivalent(refreshed, user)) { - synchronizeUserWithDatabase(user, thriftClients, user::getEmailAddress, UserUtils::fillThriftUserFromLiferayUser); + synchronizeUserWithDatabase(user, thriftClients, user::getEmailAddress, user::getOpenId, UserUtils::fillThriftUserFromLiferayUser); UserCacheHolder.getRefreshedUserFromEmail(userEmailAddress); } } diff --git a/libraries/lib-datahandler/src/main/java/org/eclipse/sw360/datahandler/permissions/UserPermissions.java b/libraries/lib-datahandler/src/main/java/org/eclipse/sw360/datahandler/permissions/UserPermissions.java index 34409316e0..f3d9d45cd4 100644 --- a/libraries/lib-datahandler/src/main/java/org/eclipse/sw360/datahandler/permissions/UserPermissions.java +++ b/libraries/lib-datahandler/src/main/java/org/eclipse/sw360/datahandler/permissions/UserPermissions.java @@ -42,7 +42,8 @@ public boolean isActionAllowed(RequestedAction action) { return true; case WRITE: case DELETE: - return PermissionUtils.isUserAtLeast(ADMIN, user); + return document.getEmail().equals(user.getEmail()) || // allow to write and delete oneself + PermissionUtils.isUserAtLeast(ADMIN, user); default: return false; } diff --git a/libraries/lib-datahandler/src/main/thrift/users.thrift b/libraries/lib-datahandler/src/main/thrift/users.thrift index aabe931217..713d4ce8c4 100644 --- a/libraries/lib-datahandler/src/main/thrift/users.thrift +++ b/libraries/lib-datahandler/src/main/thrift/users.thrift @@ -56,6 +56,7 @@ struct User { 11: optional bool wantsMailNotification, 12: optional string commentMadeDuringModerationRequest, 13: optional map notificationPreferences, + 14: optional set formerEmailAddresses, } service UserService { @@ -65,6 +66,11 @@ service UserService { **/ User getByEmail(1:string email); + /** + * searches for a SW360 user by email, or, if no such user is found, by externalId + **/ + User getByEmailOrExternalId(1:string email, 2:string externalId); + /** * get list of all SW360-users in database with name equal to parameter name **/ diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java index 5c9b9489ac..0d1d52abd3 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java @@ -183,6 +183,9 @@ static abstract class ProjectMixin extends Project { "setDepartment", "notificationPreferencesSize", "setNotificationPreferences", + "formerEmailAddressesSize", + "formerEmailAddressesIterator", + "setFormerEmailAddresses", "setCommentMadeDuringModerationRequest" }) static abstract class UserMixin extends User { diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/UserSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/UserSpecTest.java index 9fb8077295..be9aee2277 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/UserSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/UserSpecTest.java @@ -8,6 +8,7 @@ */ package org.eclipse.sw360.rest.resourceserver.restdocs; +import com.google.common.collect.Sets; import org.eclipse.sw360.datahandler.thrift.users.User; import org.eclipse.sw360.datahandler.thrift.users.UserGroup; import org.eclipse.sw360.rest.resourceserver.TestHelper; @@ -61,6 +62,7 @@ public void before() throws UnsupportedEncodingException { user.setLastname("Doe"); user.setDepartment("SW360 Administration"); user.setWantsMailNotification(true); + user.setFormerEmailAddresses(Sets.newHashSet("admin_bachelor@sw360.org")); userList.add(user); given(this.userServiceMock.getUserByEmail("admin@sw360.org")).willReturn(user); @@ -115,6 +117,7 @@ public void should_document_get_user() throws Exception { fieldWithPath("givenName").description("The user's given name"), fieldWithPath("lastName").description("The user's last name"), fieldWithPath("department").description("The user's company department"), + fieldWithPath("formerEmailAddresses").description("The user's former email addresses"), fieldWithPath("_links").description("<> to other resources") ))); }