diff --git a/cms-plugin/README.md b/cms-plugin/README.md index b7847bb84b..efc3b4bcf2 100644 --- a/cms-plugin/README.md +++ b/cms-plugin/README.md @@ -17,19 +17,6 @@ entando-plugin-jacms CMS is a plugin that allows to registered users to manage in the Back Office dynamic contents and digital assets. -**Installation** - -In order to install the CMS plugin, you must insert the following dependency in the pom.xml file of your project: - -``` - - org.entando.entando.bundles.app-view - entando-app-view-cms-default - ${entando.version} - war - -``` - # Developing against local versions of upstream projects (e.g. admin-console, entando-engine). Full instructions on how to develop against local versions of upstream projects are available in the diff --git a/engine/src/main/java/com/agiletec/aps/system/SystemConstants.java b/engine/src/main/java/com/agiletec/aps/system/SystemConstants.java index 93676ee290..5d070030ba 100644 --- a/engine/src/main/java/com/agiletec/aps/system/SystemConstants.java +++ b/engine/src/main/java/com/agiletec/aps/system/SystemConstants.java @@ -296,11 +296,6 @@ private SystemConstants(){} public static final String USER_PROFILE_ATTRIBUTE_DISABLING_CODE_ON_EDIT = "userprofile:onEdit"; - /** - * The name of the role for attribute attribute that contains the profile picture file name - */ - public static final String USER_PROFILE_ATTRIBUTE_ROLE_PROFILE_PICTURE = "userprofile:profilepicture"; - public static final String ENTANDO_THREAD_NAME_PREFIX = "EntandoThread_"; public static final String API_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/IUserPreferencesDAO.java b/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/IUserPreferencesDAO.java index ac96cca259..2fb201cefd 100644 --- a/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/IUserPreferencesDAO.java +++ b/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/IUserPreferencesDAO.java @@ -54,4 +54,5 @@ public interface IUserPreferencesDAO { * @throws EntException the ent exception */ void deleteUserPreferences(String username) throws EntException; + } diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/IUserPreferencesManager.java b/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/IUserPreferencesManager.java index 8ad6fd4bca..24ed516b2b 100644 --- a/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/IUserPreferencesManager.java +++ b/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/IUserPreferencesManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-Present Entando Inc. (http://www.entando.com) All rights reserved. + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. * * 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 @@ -54,4 +54,18 @@ public interface IUserPreferencesManager { * @throws EntException the ent exception */ void deleteUserPreferences(String username) throws EntException; -} \ No newline at end of file + + boolean isUserGravatarEnabled(String username) throws EntException; + + void updateUserGravatarPreference(String username, boolean enabled) throws EntException; + + public default UserPreferences createDefaultUserPreferences(String username) { + UserPreferences userPreferences = new UserPreferences(); + userPreferences.setUsername(username); + userPreferences.setWizard(true); + userPreferences.setTranslationWarning(true); + userPreferences.setLoadOnPageSelect(true); + return userPreferences; + } + +} diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferences.java b/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferences.java index a17819e890..b4cdacbab4 100644 --- a/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferences.java +++ b/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferences.java @@ -21,7 +21,7 @@ @XmlRootElement(name = "userPreferences") @XmlType(propOrder = {"username", "wizard", "loadOnPageSelect", "translationWarning", "defaultPageOwnerGroup", "defaultPageJoinGroups", "defaultContentOwnerGroup", "defaultContentJoinGroups", "defaultWidgetOwnerGroup", - "defaultWidgetJoinGroups", "disableContentMenu"}) + "defaultWidgetJoinGroups", "disableContentMenu", "gravatar"}) public class UserPreferences implements Serializable { private String username; @@ -35,6 +35,7 @@ public class UserPreferences implements Serializable { private String defaultWidgetOwnerGroup; private String defaultWidgetJoinGroups; private boolean disableContentMenu; + private boolean gravatar; @XmlElement(name = "username", required = true) public String getUsername() { @@ -135,6 +136,15 @@ public void setDisableContentMenu(boolean disableContentMenu) { this.disableContentMenu = disableContentMenu; } + @XmlElement(name = "gravatar") + public boolean isGravatar() { + return gravatar; + } + + public void setGravatar(boolean gravatar) { + this.gravatar = gravatar; + } + @Override public String toString() { return "UserPreferences{" + @@ -148,7 +158,8 @@ public String toString() { ", defaultContentJoinGroups='" + defaultContentJoinGroups + '\'' + ", defaultWidgetOwnerGroup='" + defaultWidgetOwnerGroup + '\'' + ", defaultWidgetJoinGroups='" + defaultWidgetJoinGroups + '\'' + - ", disableContentMenu='" + disableContentMenu + + ", disableContentMenu='" + disableContentMenu + '\'' + + ", gravatar='" + gravatar + '}'; } } diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferencesDAO.java b/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferencesDAO.java index dbab2edaab..a340dc67d8 100644 --- a/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferencesDAO.java +++ b/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferencesDAO.java @@ -29,19 +29,19 @@ public class UserPreferencesDAO extends AbstractDAO implements IUserPreferencesD private static final String LOAD_USER_PREFERENCES = "SELECT wizard, loadonpageselect, translationwarning, defaultpageownergroup, defaultpagejoingroups, " + "defaultcontentownergroup, defaultcontentjoingroups, defaultwidgetownergroup, " - + "defaultwidgetjoingroups, disableContentMenu FROM userpreferences WHERE username = ? "; + + "defaultwidgetjoingroups, disableContentMenu, gravatar FROM userpreferences WHERE username = ? "; private static final String ADD_USER_PREFERENCES = "INSERT INTO userpreferences (username, wizard, loadonpageselect, translationwarning, " + "defaultpageownergroup, defaultpagejoingroups, defaultcontentownergroup, " - + "defaultcontentjoingroups, defaultwidgetownergroup, defaultwidgetjoingroups, disableContentMenu) " - + "VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? )"; + + "defaultcontentjoingroups, defaultwidgetownergroup, defaultwidgetjoingroups, disableContentMenu, gravatar) " + + "VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? )"; private static final String UPDATE_USER_PREFERENCES = "UPDATE userpreferences SET wizard = ? , loadonpageselect = ? , translationwarning = ? , " + "defaultpageownergroup = ? , defaultpagejoingroups = ? , defaultcontentownergroup = ? , " + "defaultcontentjoingroups = ? , defaultwidgetownergroup = ?, defaultwidgetjoingroups = ? , " - + "disableContentMenu = ? WHERE username = ? "; + + "disableContentMenu = ? , gravatar = ? WHERE username = ? "; private static final String DELETE_USER_PREFERENCES = "DELETE FROM userpreferences WHERE username = ? "; @@ -70,6 +70,7 @@ public UserPreferences loadUserPreferences(String username) throws EntException response.setDefaultWidgetOwnerGroup(res.getString(8)); response.setDefaultWidgetJoinGroups(res.getString(9)); response.setDisableContentMenu(1 == res.getInt(10)); + response.setGravatar(1 == res.getInt(11)); } } catch (SQLException e) { _logger.error("Error loading user preferences for user {}", username, e); @@ -99,6 +100,7 @@ public void addUserPreferences(UserPreferences userPreferences) throws EntExcept stat.setString(9, userPreferences.getDefaultWidgetOwnerGroup()); stat.setString(10, userPreferences.getDefaultWidgetJoinGroups()); stat.setInt(11, userPreferences.getDisableContentMenu() ? 1 : 0); + stat.setInt(12, userPreferences.isGravatar() ? 1 : 0); stat.executeUpdate(); conn.commit(); } catch (SQLException e) { @@ -128,7 +130,8 @@ public void updateUserPreferences(UserPreferences userPreferences) throws EntExc stat.setString(8, userPreferences.getDefaultWidgetOwnerGroup()); stat.setString(9, userPreferences.getDefaultWidgetJoinGroups()); stat.setInt(10, userPreferences.getDisableContentMenu() ? 1 : 0); - stat.setString(11, userPreferences.getUsername()); + stat.setInt(11, userPreferences.isGravatar() ? 1 : 0); + stat.setString(12, userPreferences.getUsername()); stat.executeUpdate(); conn.commit(); } catch (SQLException e) { @@ -158,4 +161,5 @@ public void deleteUserPreferences(String username) throws EntException { closeDaoResources(null, stat, conn); } } + } diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferencesManager.java b/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferencesManager.java index 6a932060f6..fa439aae90 100644 --- a/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferencesManager.java +++ b/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferencesManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-Present Entando Inc. (http://www.entando.com) All rights reserved. + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. * * 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 @@ -13,6 +13,7 @@ */ package org.entando.entando.aps.system.services.userpreferences; +import java.util.Optional; import org.entando.entando.ent.exception.EntException; public class UserPreferencesManager implements IUserPreferencesManager { @@ -34,6 +35,24 @@ public void updateUserPreferences(UserPreferences userPreferences) throws EntExc userPreferencesDAO.updateUserPreferences(userPreferences); } + @Override + public boolean isUserGravatarEnabled(String username) throws EntException { + return Optional.ofNullable(this.getUserPreferences(username)).map(p -> p.isGravatar()).orElse(Boolean.FALSE); + } + + @Override + public void updateUserGravatarPreference(String username, boolean enabled) throws EntException { + UserPreferences userPreferences = this.getUserPreferences(username); + if (null != userPreferences) { + userPreferences.setGravatar(enabled); + this.updateUserPreferences(userPreferences); + } else { + userPreferences = this.createDefaultUserPreferences(username); + userPreferences.setGravatar(enabled); + this.addUserPreferences(userPreferences); + } + } + @Override public void deleteUserPreferences(String username) throws EntException { userPreferencesDAO.deleteUserPreferences(username); @@ -42,4 +61,5 @@ public void deleteUserPreferences(String username) throws EntException { public void setUserPreferencesDAO(UserPreferencesDAO userPreferencesDAO) { this.userPreferencesDAO = userPreferencesDAO; } + } diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferencesService.java b/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferencesService.java index d43ca0ea65..0d407fa1ee 100644 --- a/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferencesService.java +++ b/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferencesService.java @@ -95,6 +95,9 @@ public UserPreferencesDto updateUserPreferences(String username, UserPreferences if (request.getDisableContentMenu() != null) { userPreferences.setDisableContentMenu(request.getDisableContentMenu()); } + if (request.getGravatar() != null) { + userPreferences.setGravatar(request.getGravatar()); + } userPreferencesManager.updateUserPreferences(userPreferences); return new UserPreferencesDto(userPreferencesManager.getUserPreferences(username)); } else { @@ -109,12 +112,8 @@ public UserPreferencesDto updateUserPreferences(String username, UserPreferences private void createNewDefaultUserPreferences(String username) { try { - UserPreferences userPreferences = new UserPreferences(); - userPreferences.setUsername(username); - userPreferences.setWizard(true); - userPreferences.setTranslationWarning(true); - userPreferences.setLoadOnPageSelect(true); - userPreferencesManager.addUserPreferences(userPreferences); + UserPreferences userPreferences = this.userPreferencesManager.createDefaultUserPreferences(username); + this.userPreferencesManager.addUserPreferences(userPreferences); } catch (EntException e) { logger.error("Error in creating new default userPreferences for {}", username, e); throw new RestServerError("Error creating new default userPreferences", e); diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/AvatarService.java b/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/AvatarService.java index bda1d64c13..5b601dfbab 100644 --- a/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/AvatarService.java +++ b/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/AvatarService.java @@ -1,13 +1,22 @@ +/* + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * 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. + */ package org.entando.entando.aps.system.services.userprofile; -import com.agiletec.aps.system.SystemConstants; -import com.agiletec.aps.system.common.entity.model.attribute.AttributeInterface; -import com.agiletec.aps.system.common.entity.model.attribute.MonoTextAttribute; import com.agiletec.aps.system.services.user.UserDetails; import java.nio.file.Paths; import java.util.List; import java.util.Optional; -import java.util.function.Consumer; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FilenameUtils; @@ -16,10 +25,9 @@ import org.entando.entando.aps.system.exception.RestServerError; import org.entando.entando.aps.system.services.storage.IFileBrowserService; import org.entando.entando.aps.system.services.storage.model.BasicFileAttributeViewDto; +import org.entando.entando.aps.system.services.userpreferences.IUserPreferencesManager; import org.entando.entando.aps.system.services.userprofile.model.AvatarDto; -import org.entando.entando.aps.system.services.userprofile.model.IUserProfile; import org.entando.entando.ent.exception.EntException; -import org.entando.entando.ent.exception.EntRuntimeException; import org.entando.entando.web.entity.validator.EntityValidator; import org.entando.entando.web.filebrowser.model.FileBrowserFileRequest; import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; @@ -28,10 +36,9 @@ @Slf4j @RequiredArgsConstructor public class AvatarService implements IAvatarService { - - // Services + private final IFileBrowserService fileBrowserService; - private final IUserProfileManager userProfileManager; + private final IUserPreferencesManager userPreferencesManager; // CONSTANTS private static final String DEFAULT_AVATAR_PATH = "static/profile"; @@ -39,7 +46,11 @@ public class AvatarService implements IAvatarService { @Override public AvatarDto getAvatarData(UserDetails userDetails) { try { - String fileName = this.getAvatarFilename(userDetails); + boolean isGravatarEnabled = this.userPreferencesManager.isUserGravatarEnabled(userDetails.getUsername()); + if (isGravatarEnabled) { + return AvatarDto.builder().gravatar(true).build(); + } + String fileName = this.getAvatarFilenameByUsername(userDetails.getUsername()); if (StringUtils.isEmpty(fileName)) { throw new ResourceNotFoundException(EntityValidator.ERRCODE_ENTITY_DOES_NOT_EXIST, "image", userDetails.getUsername()); @@ -68,16 +79,13 @@ public AvatarDto getAvatarData(UserDetails userDetails) { public String updateAvatar(ProfileAvatarRequest request, UserDetails userDetails, BindingResult bindingResult) { try { String username = userDetails.getUsername(); - IUserProfile userProfile = userProfileManager.getProfile(username); // remove previous image if present - deletePrevUserAvatarFromFileSystemIfPresent(username, userProfile); - // add profile picture file - FileBrowserFileRequest fileBrowserFileRequest = addProfileImageToFileSystem(request, userDetails, bindingResult); - // update profile picture attribute or add a new one if no image was already set by user - if (getProfilePictureAttribute(userProfile).isPresent()) { - this.setProfilePictureAttribute(userProfile, fileBrowserFileRequest.getFilename()); - userProfileManager.updateProfile(userProfile.getId(), userProfile); + deletePrevUserAvatarFromFileSystemIfPresent(username); + this.userPreferencesManager.updateUserGravatarPreference(userDetails.getUsername(), request.isUseGravatar()); + if (request.isUseGravatar()) { + return null; } + FileBrowserFileRequest fileBrowserFileRequest = addProfileImageToFileSystem(request, userDetails, bindingResult); return fileBrowserFileRequest.getFilename(); } catch (Exception e) { log.error("Error updating avatar", e); @@ -89,15 +97,8 @@ public String updateAvatar(ProfileAvatarRequest request, UserDetails userDetails public void deleteAvatar(UserDetails userDetails, BindingResult bindingResult) { try { String username = userDetails.getUsername(); - IUserProfile userProfile = userProfileManager.getProfile(username); - // remove previous image if present - this.deletePrevUserAvatarFromFileSystemIfPresent(username, userProfile); - // update profile picture attribute (if present) with an empty value - if (getProfilePictureAttribute(userProfile).isPresent()) { - this.setProfilePictureAttribute(userProfile, null); - // update user profile with the fresh data related to profile picture - userProfileManager.updateProfile(userProfile.getId(), userProfile); - } + this.deletePrevUserAvatarFromFileSystemIfPresent(username); + this.userPreferencesManager.updateUserGravatarPreference(userDetails.getUsername(), false); } catch (Exception e) { log.error("Error deleting avatar", e); throw new RestServerError("Error deleting avatar", e); @@ -115,31 +116,19 @@ private FileBrowserFileRequest addProfileImageToFileSystem( return fileBrowserFileRequest; } - private void deletePrevUserAvatarFromFileSystemIfPresent(String username, IUserProfile userProfile) { - Consumer deleteFile = filename -> { - if (null == filename) { - return; - } - String profilePicturePath = Paths.get(DEFAULT_AVATAR_PATH, filename).toString(); - this.removePictureFromFilesystem(profilePicturePath); - }; - this.getProfilePictureAttribute(userProfile) - .ifPresentOrElse(attribute -> deleteFile.accept((String) attribute.getValue()), - () -> deleteFile.accept(this.getAvatarFilenameByUsername(username))); - } - - private String getAvatarFilename(UserDetails userDetails) { - try { - IUserProfile userProfile = userProfileManager.getProfile(userDetails.getUsername()); - return getProfilePictureAttribute(userProfile).map(pr -> (String)pr.getValue()).orElseGet(() -> - this.getAvatarFilenameByUsername(userDetails.getUsername()) - ); - } catch (Exception e) { - throw new EntRuntimeException("Error extracting avatar " + userDetails.getUsername(), e); + private void deletePrevUserAvatarFromFileSystemIfPresent(String username) throws EntException { + String filename = this.getAvatarFilenameByUsername(username); + if (null == filename) { + return; } + String profilePicturePath = Paths.get(DEFAULT_AVATAR_PATH, filename).toString(); + fileBrowserService.deleteFile(profilePicturePath, false); } - private String getAvatarFilenameByUsername(String username) { + private String getAvatarFilenameByUsername(String username) throws EntException { + if (!fileBrowserService.exists(DEFAULT_AVATAR_PATH)) { + return null; + } List fileAttributes = fileBrowserService.browseFolder(DEFAULT_AVATAR_PATH, Boolean.FALSE); Optional fileAvatar = fileAttributes.stream().filter(bfa -> !bfa.getDirectory()) .filter(bfa -> { @@ -150,16 +139,6 @@ private String getAvatarFilenameByUsername(String username) { return fileAvatar.orElse(null); } - private void removePictureFromFilesystem(String profilePicturePath) throws EntRuntimeException { - try { - if (fileBrowserService.exists(profilePicturePath)) { - fileBrowserService.deleteFile(profilePicturePath, false); - } - } catch (EntException e) { - throw new EntRuntimeException("Error in checking file existence on the filesystem", e); - } - } - private static FileBrowserFileRequest convertToFileBrowserFileRequest(ProfileAvatarRequest request, UserDetails userDetails) { FileBrowserFileRequest fileBrowserFileRequest = new FileBrowserFileRequest(); @@ -172,15 +151,4 @@ private static FileBrowserFileRequest convertToFileBrowserFileRequest(ProfileAva return fileBrowserFileRequest; } - private Optional getProfilePictureAttribute(IUserProfile userProfile) { - return Optional.ofNullable(userProfile).map(up -> up.getAttributeByRole(SystemConstants.USER_PROFILE_ATTRIBUTE_ROLE_PROFILE_PICTURE)); - } - - private void setProfilePictureAttribute(IUserProfile userProfile, String value) { - getProfilePictureAttribute(userProfile).ifPresent(attribute -> { - MonoTextAttribute textAtt = (MonoTextAttribute) attribute; - textAtt.setText(value); - }); - } - } diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/attributeRoles.xml b/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/attributeRoles.xml index b0e9ee6029..a91d81b10a 100644 --- a/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/attributeRoles.xml +++ b/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/attributeRoles.xml @@ -23,10 +23,4 @@ Monotext,Email TEXT - - userprofile:profilepicture - The Attribute containing the profile picture file name - Monotext - TEXT - diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/model/AvatarDto.java b/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/model/AvatarDto.java index 312359ed21..148c60e131 100644 --- a/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/model/AvatarDto.java +++ b/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/model/AvatarDto.java @@ -1,3 +1,16 @@ +/* + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * 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. + */ package org.entando.entando.aps.system.services.userprofile.model; import lombok.AllArgsConstructor; @@ -16,5 +29,6 @@ public class AvatarDto { String filename; byte[] base64; String prevPath; + boolean gravatar; } diff --git a/engine/src/main/java/org/entando/entando/web/userpreferences/model/UserPreferencesDto.java b/engine/src/main/java/org/entando/entando/web/userpreferences/model/UserPreferencesDto.java index 62108c488a..566af5833b 100644 --- a/engine/src/main/java/org/entando/entando/web/userpreferences/model/UserPreferencesDto.java +++ b/engine/src/main/java/org/entando/entando/web/userpreferences/model/UserPreferencesDto.java @@ -35,6 +35,7 @@ public class UserPreferencesDto { private String defaultWidgetOwnerGroup; private List defaultWidgetJoinGroups; private Boolean disableContentMenu; + private Boolean gravatar; public UserPreferencesDto(UserPreferences userPreferences) { wizard = userPreferences.isWizard(); diff --git a/engine/src/main/java/org/entando/entando/web/userpreferences/model/UserPreferencesRequest.java b/engine/src/main/java/org/entando/entando/web/userpreferences/model/UserPreferencesRequest.java index dfb80d5be8..8e07bc59ba 100644 --- a/engine/src/main/java/org/entando/entando/web/userpreferences/model/UserPreferencesRequest.java +++ b/engine/src/main/java/org/entando/entando/web/userpreferences/model/UserPreferencesRequest.java @@ -30,6 +30,7 @@ public class UserPreferencesRequest { private String defaultWidgetOwnerGroup; private List defaultWidgetJoinGroups; private Boolean disableContentMenu; + private Boolean gravatar; @Override public String toString() { @@ -42,8 +43,9 @@ public String toString() { ", defaultContentOwnerGroup='" + defaultContentOwnerGroup + '\'' + ", defaultContentJoinGroups=" + defaultContentJoinGroups + ", defaultWidgetOwnerGroup='" + defaultWidgetOwnerGroup + '\'' + - ", defaultWidgetJoinGroups=" + defaultWidgetJoinGroups + - ", disableContentMenu=" + disableContentMenu + + ", defaultWidgetJoinGroups=" + defaultWidgetJoinGroups + '\'' + + ", disableContentMenu=" + disableContentMenu + '\'' + + ", gravatar=" + gravatar + '}'; } } diff --git a/engine/src/main/java/org/entando/entando/web/userprofile/ProfileController.java b/engine/src/main/java/org/entando/entando/web/userprofile/ProfileController.java index c201cdad57..f44aaedf05 100644 --- a/engine/src/main/java/org/entando/entando/web/userprofile/ProfileController.java +++ b/engine/src/main/java/org/entando/entando/web/userprofile/ProfileController.java @@ -37,7 +37,6 @@ import org.entando.entando.web.common.model.SimpleRestResponse; import org.entando.entando.web.entity.validator.EntityValidator; import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; -import org.entando.entando.web.userprofile.model.ProfileAvatarResponse; import org.entando.entando.web.userprofile.validator.ProfileAvatarValidator; import org.entando.entando.web.userprofile.validator.ProfileValidator; import org.springframework.http.HttpStatus; @@ -45,6 +44,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.validation.BindingResult; import org.springframework.validation.MapBindingResult; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -79,7 +79,6 @@ public class ProfileController { public static final String PROTECTED_FOLDER = "protectedFolder"; - public static final String PREV_PATH = "prevPath"; @RestAccessControl(permission = {Permission.MANAGE_USER_PROFILES, Permission.MANAGE_USERS}) @@ -173,7 +172,7 @@ public ResponseEntity> updateUserProfile(@PathVari @PutMapping(value = "/myUserProfile", produces = MediaType.APPLICATION_JSON_VALUE) @RestAccessControl(permission = Permission.ENTER_BACKEND) public ResponseEntity> updateMyUserProfile(@RequestAttribute("user") UserDetails user, - @Valid @RequestBody EntityDto bodyRequest, BindingResult bindingResult) { + /*@Valid*/ @RequestBody EntityDto bodyRequest, BindingResult bindingResult) { logger.debug("Update profile for the logged user {} -> {}", user.getUsername(), bodyRequest); profileValidator.validateBodyName(user.getUsername(), bodyRequest, bindingResult); if (bindingResult.hasErrors()) { @@ -185,7 +184,7 @@ public ResponseEntity> updateMyUserProfile(@Reques } return new ResponseEntity<>(new SimpleRestResponse<>(response), HttpStatus.OK); } - + @GetMapping(path = "/userProfiles/avatar", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity, Map>> getAvatar( @RequestAttribute("user") UserDetails userDetails) { @@ -197,37 +196,33 @@ public ResponseEntity, Map>> ge result.put("isDirectory", false); result.put("path", avatarData.getCurrentPath()); result.put("filename", avatarData.getFilename()); + result.put("useGravatar", avatarData.isGravatar()); result.put("base64", avatarData.getBase64()); Map metadata = new HashMap<>(); metadata.put(PREV_PATH, avatarData.getPrevPath()); return new ResponseEntity<>(new RestResponse<>(result, metadata), HttpStatus.OK); } - @PostMapping(path = "/userProfiles/avatar", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity> addAvatar( - @Valid @RequestBody ProfileAvatarRequest request, + public ResponseEntity>> addAvatar( + @Validated @RequestBody ProfileAvatarRequest request, @RequestAttribute("user") UserDetails user, BindingResult bindingResult) { - // validate input dto to check for consistency of input - profileAvatarValidator.validate(request, bindingResult); + profileAvatarValidator.validate(request, user, bindingResult); if (bindingResult.hasErrors()) { throw new ValidationGenericException(bindingResult); } - // update the profile picture saving the received image in the file system, eventually deleting the previous - // existing image String pictureFileName = avatarService.updateAvatar(request, user, bindingResult); - if (bindingResult.hasErrors()) { throw new ValidationGenericException(bindingResult); } - return new ResponseEntity<>(new SimpleRestResponse<>(new ProfileAvatarResponse(pictureFileName)), - HttpStatus.OK); + Map response = null != pictureFileName ? Map.of("filename", pictureFileName) : Map.of("useGravatar", true); + return new ResponseEntity<>(new SimpleRestResponse<>(response), HttpStatus.OK); } @DeleteMapping(path = "/userProfiles/avatar") - public ResponseEntity deleteAvatar(@RequestAttribute("user") UserDetails user) { + public ResponseEntity>> deleteAvatar(@RequestAttribute("user") UserDetails user) { avatarService.deleteAvatar(user, new MapBindingResult(new HashMap<>(), "user")); Map payload = new HashMap<>(); payload.put("username", user.getUsername()); diff --git a/engine/src/main/java/org/entando/entando/web/userprofile/model/ProfileAvatarRequest.java b/engine/src/main/java/org/entando/entando/web/userprofile/model/ProfileAvatarRequest.java index c13caabfd9..6487c041ea 100644 --- a/engine/src/main/java/org/entando/entando/web/userprofile/model/ProfileAvatarRequest.java +++ b/engine/src/main/java/org/entando/entando/web/userprofile/model/ProfileAvatarRequest.java @@ -13,24 +13,19 @@ */ package org.entando.entando.web.userprofile.model; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotEmpty; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -/** - * @author E.Santoboni - */ @NoArgsConstructor @AllArgsConstructor @Getter @Setter public class ProfileAvatarRequest { - - @NotBlank(message = "avatar.filename.notBlank") + private String filename; - @NotEmpty(message = "fileBrowser.base64.notBlank") private byte[] base64; + private boolean useGravatar; + } diff --git a/engine/src/main/java/org/entando/entando/web/userprofile/model/ProfileAvatarResponse.java b/engine/src/main/java/org/entando/entando/web/userprofile/model/ProfileAvatarResponse.java deleted file mode 100644 index 06b7368a48..0000000000 --- a/engine/src/main/java/org/entando/entando/web/userprofile/model/ProfileAvatarResponse.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2018-Present Entando Inc. (http://www.entando.com) All rights reserved. - * - * 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. - */ -package org.entando.entando.web.userprofile.model; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -/** - * @author E.Santoboni - */ -@NoArgsConstructor -@AllArgsConstructor -@Getter -@Setter -public class ProfileAvatarResponse { - - private String filename; - -} diff --git a/engine/src/main/java/org/entando/entando/web/userprofile/validator/ProfileAvatarValidator.java b/engine/src/main/java/org/entando/entando/web/userprofile/validator/ProfileAvatarValidator.java index f3a6fa9f1f..4164ca1f37 100644 --- a/engine/src/main/java/org/entando/entando/web/userprofile/validator/ProfileAvatarValidator.java +++ b/engine/src/main/java/org/entando/entando/web/userprofile/validator/ProfileAvatarValidator.java @@ -13,12 +13,20 @@ */ package org.entando.entando.web.userprofile.validator; +import com.agiletec.aps.system.SystemConstants; +import com.agiletec.aps.system.services.user.UserDetails; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.UncheckedIOException; +import java.util.Optional; import javax.imageio.ImageIO; +import lombok.AllArgsConstructor; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; +import org.entando.entando.aps.system.services.userprofile.IUserProfileManager; +import org.entando.entando.ent.exception.EntException; +import org.entando.entando.ent.exception.EntRuntimeException; +import org.entando.entando.web.common.RestErrorCodes; import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; @@ -26,33 +34,60 @@ import org.springframework.validation.Validator; @Component +@AllArgsConstructor public class ProfileAvatarValidator implements Validator { public static final String ERRCODE_INVALID_FILE_NAME = "1"; public static final String ERRCODE_INVALID_FILE_TYPE = "2"; + public static final String ERRCODE_MISSING_EMAIL_ATTRIBUTE = "3"; + + private IUserProfileManager userProfileManager; @Override public boolean supports(@NonNull Class paramClass) { return (ProfileAvatarRequest.class.equals(paramClass)); } - + @Override public void validate(@NonNull Object target, @NonNull Errors errors) { ProfileAvatarRequest request = (ProfileAvatarRequest) target; - String filename = request.getFilename(); - if (StringUtils.isEmpty(FilenameUtils.getExtension(filename))) { + if (StringUtils.isBlank(filename)) { + errors.rejectValue("filename", RestErrorCodes.NOT_BLANK, new String[]{}, + "avatar.filename.notBlank"); + } else if (StringUtils.isEmpty(FilenameUtils.getExtension(filename))) { errors.rejectValue("filename", ERRCODE_INVALID_FILE_NAME, new String[]{filename}, "fileBrowser.filename.invalidFilename"); return; } - - try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(request.getBase64())) { - if (ImageIO.read(byteArrayInputStream) == null) { - errors.rejectValue("base64", ERRCODE_INVALID_FILE_TYPE, "fileBrowser.file.invalidType"); + byte[] base64 = request.getBase64(); + if (null == base64) { + errors.rejectValue("base64", RestErrorCodes.NOT_EMPTY, new String[]{}, + "fileBrowser.base64.notBlank"); + } else { + try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(request.getBase64())) { + if (ImageIO.read(byteArrayInputStream) == null) { + errors.rejectValue("base64", ERRCODE_INVALID_FILE_TYPE, "fileBrowser.file.invalidType"); + } + } catch (IOException e) { + throw new UncheckedIOException(e); } - } catch (IOException e) { - throw new UncheckedIOException(e); + } + } + + public void validate(@NonNull Object target, UserDetails user, @NonNull Errors errors) { + ProfileAvatarRequest request = (ProfileAvatarRequest) target; + if (!request.isUseGravatar()) { + this.validate(target, errors); + return; + } + try { + Optional.ofNullable(this.userProfileManager.getProfile(user.getUsername())) + .map(up -> up.getAttributeByRole(SystemConstants.USER_PROFILE_ATTRIBUTE_ROLE_MAIL)).ifPresentOrElse(up -> { + }, () -> errors.rejectValue("useGravatar", ERRCODE_MISSING_EMAIL_ATTRIBUTE, new String[]{}, + "avatar.emailAttribute.missing")); + } catch (EntException e) { + throw new EntRuntimeException("Error validating user avatar", e); } } diff --git a/engine/src/main/resources/liquibase/changeSetPort.xml b/engine/src/main/resources/liquibase/changeSetPort.xml index 95140223cb..88cf7b8418 100644 --- a/engine/src/main/resources/liquibase/changeSetPort.xml +++ b/engine/src/main/resources/liquibase/changeSetPort.xml @@ -26,6 +26,7 @@ - - - \ No newline at end of file + + + + diff --git a/engine/src/main/resources/liquibase/port/20240112000000_user_preference_avatar.xml b/engine/src/main/resources/liquibase/port/20240112000000_user_preference_avatar.xml new file mode 100644 index 0000000000..58d41a0804 --- /dev/null +++ b/engine/src/main/resources/liquibase/port/20240112000000_user_preference_avatar.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/engine/src/main/resources/liquibase/port/clob/production/sysconfig_3.xml b/engine/src/main/resources/liquibase/port/clob/production/sysconfig_3.xml index 8786c502a8..9e1d5a2425 100644 --- a/engine/src/main/resources/liquibase/port/clob/production/sysconfig_3.xml +++ b/engine/src/main/resources/liquibase/port/clob/production/sysconfig_3.xml @@ -1,28 +1,23 @@ - - - - - true - - - userprofile:fullname - - - - - true - - - userprofile:email - - - + + + + + true + - userprofile:profilepicture + userprofile:fullname - - + + + true + + + userprofile:email + + + + \ No newline at end of file diff --git a/engine/src/main/resources/liquibase/port/clob/test/sysconfig_3.xml b/engine/src/main/resources/liquibase/port/clob/test/sysconfig_3.xml index a0c63d7a78..574c0f381a 100644 --- a/engine/src/main/resources/liquibase/port/clob/test/sysconfig_3.xml +++ b/engine/src/main/resources/liquibase/port/clob/test/sysconfig_3.xml @@ -1,171 +1,194 @@ - - - - - true - - - userprofile:fullname - - - - - true - - - - userprofile:email - - - - - - - - - - - - - true - - - userprofile:firstname - - - - - true - - - userprofile:surname - - - - - true - - - userprofile:email - - - + + + + + true + - userprofile:profilepicture + userprofile:fullname - - - - - - true - - - true - - - true - - - - - 25/11/2026 - - - - true + + + true + + + + userprofile:email + + + + + + + + + + + + + true + + + userprofile:firstname + + + + + true + + + userprofile:surname + + + + + true + + + userprofile:email + + + + + + + + + true + + + + + true + + + + + true + + + + + + 25/11/2026 + + + + + true + - - - true + + + + true + - - - true - - - true - - - true - - - true - - - - true - 15 - 30 - - - - - true - - - - true - 50 - 300 - - - - true - - jacms:title - - - - - true - 15 - 30 - - - - - true - - - true - - - true - - - - - - - 10/10/2030 - - - - - - - - - - #entity.getAttribute('Number').value)]]> - - - - - - - - - - - - - true - - - - + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + 15 + 30 + + + + + + true + + + + + true + 50 + 300 + + + + + true + + + jacms:title + + + + + true + 15 + 30 + + + + + + true + + + + + true + + + + + true + + + + + + + + 10/10/2030 + + + + + + + + + + #entity.getAttribute('Number').value)]]> + + + + + + + + + + + + + true + + + + \ No newline at end of file diff --git a/engine/src/main/resources/rest/messages.properties b/engine/src/main/resources/rest/messages.properties index 6c2f39a945..9db69e923f 100644 --- a/engine/src/main/resources/rest/messages.properties +++ b/engine/src/main/resources/rest/messages.properties @@ -397,3 +397,4 @@ components.usage.type.invalid=Requested occurrence for position ''{0}'' - Invali # Avatar avatar.filename.notBlank=''fileName'' is required +avatar.emailAttribute.missing=Missing email attribute in current user diff --git a/engine/src/test/java/org/entando/entando/aps/system/services/userprofile/AvatarServiceTest.java b/engine/src/test/java/org/entando/entando/aps/system/services/userprofile/AvatarServiceTest.java index 281a97cc47..7fff096693 100644 --- a/engine/src/test/java/org/entando/entando/aps/system/services/userprofile/AvatarServiceTest.java +++ b/engine/src/test/java/org/entando/entando/aps/system/services/userprofile/AvatarServiceTest.java @@ -1,3 +1,16 @@ +/* + * Copyright 2024-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * 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. + */ package org.entando.entando.aps.system.services.userprofile; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -7,23 +20,20 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import com.agiletec.aps.system.SystemConstants; -import com.agiletec.aps.system.common.entity.model.attribute.MonoTextAttribute; import com.agiletec.aps.system.services.user.UserDetails; import java.util.List; import org.entando.entando.aps.system.exception.ResourceNotFoundException; import org.entando.entando.aps.system.exception.RestServerError; import org.entando.entando.aps.system.services.storage.IFileBrowserService; import org.entando.entando.aps.system.services.storage.model.BasicFileAttributeViewDto; +import org.entando.entando.aps.system.services.userpreferences.IUserPreferencesManager; import org.entando.entando.aps.system.services.userprofile.model.AvatarDto; -import org.entando.entando.aps.system.services.userprofile.model.IUserProfile; -import org.entando.entando.aps.system.services.userprofile.model.UserProfile; import org.entando.entando.ent.exception.EntException; import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; @@ -31,222 +41,110 @@ @ExtendWith(MockitoExtension.class) class AvatarServiceTest { - - @Mock - private IUserProfileManager userProfileManager; + @Mock private IFileBrowserService fileBrowserService; + @Mock + private IUserPreferencesManager userPreferencesManager; IAvatarService avatarService; @BeforeEach void init() { - avatarService = new AvatarService(fileBrowserService, userProfileManager); + avatarService = new AvatarService(fileBrowserService, userPreferencesManager); } @Test void shouldGetAvatarDataReturnAvatarInfo() throws EntException { - IUserProfile profile = this.buildValidUserProfile("username", "image.png"); - when(userProfileManager.getProfile(any())).thenReturn(profile); + UserDetails user = Mockito.mock(UserDetails.class); + when(user.getUsername()).thenReturn("username_test"); + when(userPreferencesManager.isUserGravatarEnabled(user.getUsername())).thenReturn(false); + BasicFileAttributeViewDto dto = this.createMockFileAttributeDto("username_test", "png"); + when(fileBrowserService.exists("static/profile")).thenReturn(true); + when(fileBrowserService.browseFolder("static/profile", false)).thenReturn(List.of(dto)); when(fileBrowserService.getFileStream(any(), any())).thenReturn(new byte[0]); - - AvatarDto avatarData = avatarService.getAvatarData(mock(UserDetails.class)); - - assertEquals("image.png", avatarData.getFilename()); - assertEquals("static/profile/image.png", avatarData.getCurrentPath()); - } - - @Test - void shouldGetAvatarDataThrowResourceServerError() throws EntException { - when(userProfileManager.getProfile(any())).thenThrow(EntException.class); - assertThrows(RestServerError.class, () -> avatarService.getAvatarData(mock(UserDetails.class))); + AvatarDto avatarData = avatarService.getAvatarData(user); + assertEquals("username_test.png", avatarData.getFilename()); + assertEquals("static/profile/username_test.png", avatarData.getCurrentPath()); + Assertions.assertFalse(avatarData.isGravatar()); } @Test - void shouldGetAvatarDataThrowResourceNotFoundExceptionIfNoImageIsPresent() throws EntException { - when(userProfileManager.getProfile(any())).thenReturn(new UserProfile()); - assertThrows(ResourceNotFoundException.class, () -> avatarService.getAvatarData(mock(UserDetails.class))); + void shouldGetAvatarDataReturnAvatarInfoWithGravatarEnabled() throws EntException { + UserDetails user = Mockito.mock(UserDetails.class); + when(user.getUsername()).thenReturn("username_test"); + when(userPreferencesManager.isUserGravatarEnabled(user.getUsername())).thenReturn(true); + AvatarDto avatarData = avatarService.getAvatarData(user); + verify(fileBrowserService, Mockito.times(0)).browseFolder(any(), any()); + verify(fileBrowserService, Mockito.times(0)).getFileStream(any(), any()); + Assertions.assertNull(avatarData.getFilename()); + Assertions.assertNull(avatarData.getCurrentPath()); + Assertions.assertTrue(avatarData.isGravatar()); } @Test - void shouldGetAvatarDataThrowResourceNotFoundExceptionIfImageInProfileAttributeIsEmpty() throws EntException { - IUserProfile profile = this.buildValidUserProfile("username", ""); - when(userProfileManager.getProfile(any())).thenReturn(profile); + void shouldGetAvatarDataThrowResourceNotFoundExceptionIfNoImageIsPresent() throws EntException { assertThrows(ResourceNotFoundException.class, () -> avatarService.getAvatarData(mock(UserDetails.class))); } - - @Test - void shouldUpdateAvatarWithNullProfile() throws EntException { - when(userProfileManager.getProfile("username")).thenReturn(null); - UserDetails userDetails = mock(UserDetails.class); - when(userDetails.getUsername()).thenReturn("username"); - when(fileBrowserService.browseFolder(Mockito.anyString(), Mockito.eq(false))).thenReturn(List.of()); - avatarService.updateAvatar(mock(ProfileAvatarRequest.class), userDetails, - mock(BindingResult.class)); - verify(fileBrowserService, Mockito.times(0)).deleteFile(any(), any()); - verify(userProfileManager, Mockito.times(0)).addProfile(Mockito.eq("username"), Mockito.any(IUserProfile.class)); - } - - @Test - void shouldUpdateAvatarDeletePreviousProfilePictureIfPresent() throws EntException { - IUserProfile profile = this.buildValidUserProfile("username", "prevImage.png"); - when(userProfileManager.getProfile(any())).thenReturn(profile); - //pretend image exists on filesystem - when(fileBrowserService.exists(any())).thenReturn(true); - avatarService.updateAvatar(mock(ProfileAvatarRequest.class), mock(UserDetails.class), - mock(BindingResult.class)); - verify(fileBrowserService, Mockito.times(1)).deleteFile(any(), any()); - } - + @Test void shouldUpdateAvatarAddProfilePictureFromTheRequest() throws EntException { - when(userProfileManager.getProfile(any())).thenReturn(new UserProfile()); - BasicFileAttributeViewDto dtoDirectory = new BasicFileAttributeViewDto(); dtoDirectory.setDirectory(true); dtoDirectory.setName("folder"); - BasicFileAttributeViewDto dto = new BasicFileAttributeViewDto(); - dto.setDirectory(false); - dto.setName("test_username.jpg"); + BasicFileAttributeViewDto dto = this.createMockFileAttributeDto("test_username", "jpg"); + when(fileBrowserService.exists("static/profile")).thenReturn(true); when(fileBrowserService.browseFolder(Mockito.anyString(), Mockito.eq(false))).thenReturn(List.of(dtoDirectory, dto)); UserDetails userDetails = mock(UserDetails.class); when(userDetails.getUsername()).thenReturn("test_username"); - - avatarService.updateAvatar(mock(ProfileAvatarRequest.class),userDetails, mock(BindingResult.class)); + avatarService.updateAvatar(mock(ProfileAvatarRequest.class), userDetails, mock(BindingResult.class)); + verify(userPreferencesManager, Mockito.times(1)).updateUserGravatarPreference("test_username", false); + verify(fileBrowserService, Mockito.times(1)).deleteFile(any(), any()); verify(fileBrowserService, Mockito.times(1)).addFile(any(), any()); } - - @Test - void shouldUpdateAvatarUserProfileAndRenameProfilePictureWithUserName() throws EntException { - // set previous profile picture - IUserProfile profile = this.buildValidUserProfile("user1", "prevImage.png"); - when(userProfileManager.getProfile(any())).thenReturn(profile); - // set POST request DTO - ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest(); - profileAvatarRequest.setFilename("image.png"); - profileAvatarRequest.setBase64(new byte[0]); - // set user details to return desired username - UserDetails userDetails = mock(UserDetails.class); - when(userDetails.getUsername()).thenReturn("user1"); - - avatarService.updateAvatar(profileAvatarRequest, userDetails, mock(BindingResult.class)); - - ArgumentCaptor captorProfile = ArgumentCaptor.forClass(IUserProfile.class); - verify(userProfileManager, Mockito.times(1)).updateProfile(Mockito.eq("user1"), captorProfile.capture()); - assertEquals("user1.png", captorProfile.getValue() - .getAttributeByRole(SystemConstants.USER_PROFILE_ATTRIBUTE_ROLE_PROFILE_PICTURE).getValue()); - } - + @Test - void shouldUpdateAvatarUserProfileAndSetRenamedProfilePictureIfNoPreviousPictureWasPresent() throws EntException { - // set previous profile picture - IUserProfile profile = this.buildValidUserProfile("user1", null); - when(userProfileManager.getProfile(any())).thenReturn(profile); - // set POST request DTO - ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest(); - profileAvatarRequest.setFilename("image.png"); - profileAvatarRequest.setBase64(new byte[0]); - // set user details to return desired username + void shouldUpdateAvatarWithGravatar() throws EntException { + ProfileAvatarRequest request = new ProfileAvatarRequest(null, null, true); UserDetails userDetails = mock(UserDetails.class); - when(userDetails.getUsername()).thenReturn("user1"); - - avatarService.updateAvatar(profileAvatarRequest, userDetails, mock(BindingResult.class)); - - ArgumentCaptor captorProfile = ArgumentCaptor.forClass(IUserProfile.class); - verify(userProfileManager, Mockito.times(1)).updateProfile(Mockito.eq("user1"), captorProfile.capture()); - assertEquals("user1.png", captorProfile.getValue() - .getAttributeByRole(SystemConstants.USER_PROFILE_ATTRIBUTE_ROLE_PROFILE_PICTURE).getValue()); + when(userDetails.getUsername()).thenReturn("test_username_gravatar"); + when(fileBrowserService.exists("static/profile")).thenReturn(false); + avatarService.updateAvatar(request, userDetails, mock(BindingResult.class)); + verify(userPreferencesManager, Mockito.times(1)).updateUserGravatarPreference("test_username_gravatar", true); + verify(fileBrowserService, Mockito.times(0)).deleteFile(any(), any()); + verify(fileBrowserService, Mockito.times(0)).addFile(any(), any()); } - + @Test - void shouldDeleteAvatarFromFilesystemAndResetUserProfilePictureAttribute() throws EntException { - // set previous profile picture - IUserProfile profile = this.buildValidUserProfile("user1", "user1.png"); - when(userProfileManager.getProfile(any())).thenReturn(profile); - - // set user details to return desired username + void shouldDeleteAvatar() throws EntException { UserDetails userDetails = mock(UserDetails.class); when(userDetails.getUsername()).thenReturn("user1"); - - //pretend image exists on filesystem - when(fileBrowserService.exists(any())).thenReturn(true); - + BasicFileAttributeViewDto dto = this.createMockFileAttributeDto("user1", "png"); + when(fileBrowserService.exists("static/profile")).thenReturn(true); + when(fileBrowserService.browseFolder(Mockito.anyString(), Mockito.eq(false))).thenReturn(List.of(dto)); avatarService.deleteAvatar(userDetails, mock(BindingResult.class)); - - ArgumentCaptor captorProfile = ArgumentCaptor.forClass(IUserProfile.class); - verify(userProfileManager, Mockito.times(1)).updateProfile(Mockito.eq("user1"), captorProfile.capture()); - assertEquals("", captorProfile.getValue() - .getAttributeByRole(SystemConstants.USER_PROFILE_ATTRIBUTE_ROLE_PROFILE_PICTURE).getValue()); verify(fileBrowserService, Mockito.times(1)).deleteFile(any(), any()); + verify(userPreferencesManager, Mockito.times(1)).updateUserGravatarPreference("user1", false); } - - - @Test - void shouldDeleteAvatarDoNothingAndRunSmoothlyIfUserImageIsNotSetInTheProfile() throws EntException { - // set previous profile picture - IUserProfile profile = this.buildValidUserProfile("user1", ""); - when(userProfileManager.getProfile(any())).thenReturn(profile); - - // set user details to return desired username - UserDetails userDetails = mock(UserDetails.class); - when(userDetails.getUsername()).thenReturn("user1"); - - avatarService.deleteAvatar(userDetails, mock(BindingResult.class)); - - ArgumentCaptor captorProfile = ArgumentCaptor.forClass(IUserProfile.class); - verify(userProfileManager, Mockito.times(1)).updateProfile(Mockito.eq("user1"), captorProfile.capture()); - assertEquals("", captorProfile.getValue() - .getAttributeByRole(SystemConstants.USER_PROFILE_ATTRIBUTE_ROLE_PROFILE_PICTURE).getValue()); - verify(fileBrowserService, Mockito.times(0)).deleteFile(any(), any()); - } - + @Test - void shouldDeleteAvatarThrowExceptionIfProfilePictureCheckImageGoesInError() throws EntException { - // set previous profile picture - IUserProfile profile = this.buildValidUserProfile("user1", "user1.png"); - when(userProfileManager.getProfile(any())).thenReturn(profile); - + void shouldDeleteAvatarThrowException() throws EntException { // set user details to return desired username UserDetails userDetails = mock(UserDetails.class); when(userDetails.getUsername()).thenReturn("user1"); - - //pretend fileBrowserService.exists goes in error - when(fileBrowserService.exists(any())).thenThrow(EntException.class); - + BasicFileAttributeViewDto dto = this.createMockFileAttributeDto("user1", "jpg"); + when(fileBrowserService.exists("static/profile")).thenReturn(true); + when(fileBrowserService.browseFolder(Mockito.anyString(), Mockito.eq(false))).thenReturn(List.of(dto)); + Mockito.doThrow(RuntimeException.class).when(fileBrowserService).deleteFile(Mockito.any(), Mockito.eq(false)); assertThrows(RestServerError.class, () -> avatarService.deleteAvatar(userDetails, mock(BindingResult.class))); } - - @Test - void shouldUpdateAvatarThrowExceptionIfProfilePictureCheckImageGoesInError() throws EntException { - // set previous profile picture - IUserProfile profile = this.buildValidUserProfile("user1", "user1.png"); - when(userProfileManager.getProfile(any())).thenReturn(profile); - - // set user details to return desired username - UserDetails userDetails = mock(UserDetails.class); - when(userDetails.getUsername()).thenReturn("user1"); - - // set POST request DTO - ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest(); - profileAvatarRequest.setFilename("image.png"); - profileAvatarRequest.setBase64(new byte[0]); - - //pretend fileBrowserService.exists goes in error - when(fileBrowserService.exists(any())).thenThrow(EntException.class); - - assertThrows(RestServerError.class, - () -> avatarService.updateAvatar(profileAvatarRequest, userDetails, mock(BindingResult.class))); - } - private IUserProfile buildValidUserProfile(String username, String attributeValue) { - IUserProfile profile = Mockito.mock(IUserProfile.class); - Mockito.lenient().when(profile.getId()).thenReturn(username); - MonoTextAttribute attribute = new MonoTextAttribute(); - attribute.setName("profilepicture"); - attribute.setText(attributeValue); - when(profile.getAttributeByRole(SystemConstants.USER_PROFILE_ATTRIBUTE_ROLE_PROFILE_PICTURE)).thenReturn(attribute); - return profile; + private BasicFileAttributeViewDto createMockFileAttributeDto(String username, String fileExtention) { + BasicFileAttributeViewDto dto = new BasicFileAttributeViewDto(); + dto.setDirectory(Boolean.FALSE); + dto.setName(username + "." + fileExtention); + return dto; } } diff --git a/engine/src/test/java/org/entando/entando/aps/system/services/userprofile/UserProfileManagerIntegrationTest.java b/engine/src/test/java/org/entando/entando/aps/system/services/userprofile/UserProfileManagerIntegrationTest.java index 7d6791e045..1afa81bad6 100644 --- a/engine/src/test/java/org/entando/entando/aps/system/services/userprofile/UserProfileManagerIntegrationTest.java +++ b/engine/src/test/java/org/entando/entando/aps/system/services/userprofile/UserProfileManagerIntegrationTest.java @@ -279,7 +279,7 @@ private void verifyRecordOrder(List records, String[] order) { @Test void testLoadRoles() throws Exception { List roles = this.profileManager.getAttributeRoles(); - assertEquals(8, roles.size()); + assertEquals(7, roles.size()); AttributeRole role1 = this.profileManager.getAttributeRole("userprofile:surname"); assertNotNull(role1); assertEquals(1, role1.getAllowedAttributeTypes().size()); diff --git a/engine/src/test/java/org/entando/entando/web/userpreferences/UserPreferencesControllerIntegrationTest.java b/engine/src/test/java/org/entando/entando/web/userpreferences/UserPreferencesControllerIntegrationTest.java index 965434a258..f7f396d151 100644 --- a/engine/src/test/java/org/entando/entando/web/userpreferences/UserPreferencesControllerIntegrationTest.java +++ b/engine/src/test/java/org/entando/entando/web/userpreferences/UserPreferencesControllerIntegrationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-Present Entando Inc. (http://www.entando.com) All rights reserved. + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. * * 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 @@ -33,7 +33,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.result.MockMvcResultHandlers; class UserPreferencesControllerIntegrationTest extends AbstractControllerIntegrationTest { diff --git a/engine/src/test/java/org/entando/entando/web/userprofile/12_POST_valid.json b/engine/src/test/java/org/entando/entando/web/userprofile/12_POST_valid.json deleted file mode 100644 index 7f34fde71a..0000000000 --- a/engine/src/test/java/org/entando/entando/web/userprofile/12_POST_valid.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "id": "new_user_2", - "typeCode":"OTH", - "typeDescription":"Other user profile", - "description":"Profile of user with profilepicture", - "mainGroup":"free", - "groups":[], - "attributes": [ - { - "code": "firstname", - "value": "Eric" - },{ - "code": "surname", - "value": "Brown" - },{ - "code": "email", - "value": "eric.brown@entando.com" - },{ - "code": "profilepicture", - "value": "picture.png" - } - ] -} diff --git a/engine/src/test/java/org/entando/entando/web/userprofile/12_PUT_valid.json b/engine/src/test/java/org/entando/entando/web/userprofile/12_PUT_valid.json deleted file mode 100644 index e444d38177..0000000000 --- a/engine/src/test/java/org/entando/entando/web/userprofile/12_PUT_valid.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "id": "new_user_2", - "typeCode": "OTH", - "typeDescription": "Type for test OTH", - "description": "Profile of user", - "mainGroup": "free", - "groups": [ - "group1", - "group2" - ], - "attributes": [ - { - "code": "profilepicture", - "value": "picture2.png" - } - ] -} diff --git a/engine/src/test/java/org/entando/entando/web/userprofile/ProfileTypeControllerIntegrationTest.java b/engine/src/test/java/org/entando/entando/web/userprofile/ProfileTypeControllerIntegrationTest.java index 84f612b530..326f81e77e 100644 --- a/engine/src/test/java/org/entando/entando/web/userprofile/ProfileTypeControllerIntegrationTest.java +++ b/engine/src/test/java/org/entando/entando/web/userprofile/ProfileTypeControllerIntegrationTest.java @@ -376,7 +376,7 @@ void testGetUserProfileAttributeType_1() throws Exception { result.andExpect(jsonPath("$.payload.code", is("Monotext"))); result.andExpect(jsonPath("$.payload.multilingual", is(false))); result.andExpect(jsonPath("$.payload.dateFilterSupported", is(false))); - result.andExpect(jsonPath("$.payload.allowedRoles", Matchers.hasSize(7))); + result.andExpect(jsonPath("$.payload.allowedRoles", Matchers.hasSize(6))); result.andExpect(jsonPath("$.payload.simple", is(true))); result.andExpect(jsonPath("$.errors", Matchers.hasSize(0))); result.andExpect(jsonPath("$.metaData.size()", is(0))); @@ -417,7 +417,7 @@ void testGetUserProfileAttributeType_3() throws Exception { result.andExpect(jsonPath("$.payload.assignedRoles.size()", is(2))); result.andExpect(jsonPath("$.payload.assignedRoles.userprofile:fullname", is("fullname"))); result.andExpect(jsonPath("$.payload.assignedRoles.userprofile:email", is("email"))); - result.andExpect(jsonPath("$.payload.allowedRoles", Matchers.hasSize(7))); + result.andExpect(jsonPath("$.payload.allowedRoles", Matchers.hasSize(6))); result.andExpect(jsonPath("$.payload.dateFilterSupported", is(false))); result.andExpect(jsonPath("$.payload.simple", is(true))); result.andExpect(jsonPath("$.errors", Matchers.hasSize(0))); diff --git a/engine/src/test/java/org/entando/entando/web/userprofile/UserProfileControllerIntegrationTest.java b/engine/src/test/java/org/entando/entando/web/userprofile/UserProfileControllerIntegrationTest.java index 4dd91d152d..2fd400b765 100644 --- a/engine/src/test/java/org/entando/entando/web/userprofile/UserProfileControllerIntegrationTest.java +++ b/engine/src/test/java/org/entando/entando/web/userprofile/UserProfileControllerIntegrationTest.java @@ -16,7 +16,6 @@ import com.agiletec.aps.system.SystemConstants; import com.agiletec.aps.system.common.entity.IEntityTypesConfigurer; import com.agiletec.aps.system.common.entity.model.attribute.ListAttribute; -import com.agiletec.aps.system.common.entity.model.attribute.MonoTextAttribute; import com.agiletec.aps.system.services.group.Group; import com.agiletec.aps.system.services.role.Permission; import com.agiletec.aps.system.services.user.IUserManager; @@ -26,7 +25,6 @@ import com.agiletec.aps.util.FileTextReader; import org.entando.entando.aps.system.common.entity.model.attribute.EmailAttribute; import org.entando.entando.aps.system.services.userprofile.IUserProfileManager; -import org.entando.entando.aps.system.services.userprofile.IUserProfileService; import org.entando.entando.aps.system.services.userprofile.model.IUserProfile; import org.entando.entando.web.AbstractControllerIntegrationTest; import org.entando.entando.web.utils.OAuth2TestUtils; @@ -48,13 +46,22 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import com.agiletec.aps.system.common.entity.model.attribute.ITextAttribute; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.io.IOUtils; +import org.entando.entando.aps.system.services.userpreferences.IUserPreferencesManager; +import org.entando.entando.aps.system.services.userpreferences.UserPreferences; +import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; +import org.hamcrest.CoreMatchers; +import org.springframework.core.io.ClassPathResource; + class UserProfileControllerIntegrationTest extends AbstractControllerIntegrationTest { @Autowired - private IUserProfileService userProfileService; + private IUserProfileManager userProfileManager; @Autowired - private IUserProfileManager userProfileManager; + private IUserPreferencesManager userPreferencesManager; @Autowired private IUserManager userManager; @@ -494,43 +501,95 @@ void testPostMyProfileOk() throws Exception { } } } - + @Test - void testAddUserProfileWithProfilePicture() throws Exception { + void shouldPostFileAvatarReturn200OnRightInput() throws Exception { + UserPreferences userPreferences = this.userPreferencesManager.getUserPreferences("jack_bauer"); + Assertions.assertNull(userPreferences); try { String accessToken = this.createAccessToken(); - - this.executeProfilePost("12_POST_valid.json", accessToken, status().isOk()).andDo(resultPrint()) - .andExpect(jsonPath("$.payload.id", is("new_user_2"))) - .andExpect(jsonPath("$.errors.size()", is(0))) - .andExpect(jsonPath("$.metaData.size()", is(0))); - - IUserProfile profile = this.userProfileManager.getProfile("new_user_2"); - Assertions.assertNotNull(profile); - MonoTextAttribute profilePicture = (MonoTextAttribute) profile.getAttribute("profilepicture"); - Assertions.assertEquals("picture.png", profilePicture.getText()); - - executeProfileGet("new_user_2", accessToken, status().isOk()) - .andExpect(jsonPath("$.payload.id", is("new_user_2"))) - .andExpect(jsonPath("$.payload.typeCode", is("OTH"))) - .andExpect(jsonPath("$.payload.attributes[0].value", is("Eric"))) - .andExpect(jsonPath("$.payload.attributes[1].value", is("Brown"))) - .andExpect(jsonPath("$.payload.attributes[2].value", is("eric.brown@entando.com"))) - .andExpect(jsonPath("$.payload.attributes[3].value", is("picture.png"))); - - executeProfilePut("12_PUT_valid.json", "new_user_2", accessToken, status().isOk()) - .andExpect(jsonPath("$.payload.id", is("new_user_2"))) - .andExpect(jsonPath("$.payload.typeCode", is("OTH"))) - .andExpect(jsonPath("$.payload.attributes[0].value", is("Eric"))) - .andExpect(jsonPath("$.payload.attributes[1].value", is("Brown"))) - .andExpect(jsonPath("$.payload.attributes[2].value", is("eric.brown@entando.com"))) - .andExpect(jsonPath("$.payload.attributes[3].value", is("picture2.png"))); + ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("myFile.png", + IOUtils.toByteArray(new ClassPathResource("userprofile/image.png").getInputStream()), false); + ResultActions result = mockMvc.perform( + post("/userProfiles/avatar") + .content(new ObjectMapper().writeValueAsString(profileAvatarRequest)) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header("Authorization", "Bearer " + accessToken)); + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.payload.filename").value("jack_bauer.png")); + userPreferences = this.userPreferencesManager.getUserPreferences("jack_bauer"); + Assertions.assertNotNull(userPreferences); + Assertions.assertFalse(userPreferences.isGravatar()); } finally { - this.userProfileManager.deleteProfile("new_user_2"); - Assertions.assertNull(this.userProfileManager.getProfile("new_user_2")); + this.userPreferencesManager.deleteUserPreferences("jack_bauer"); } } - + + @Test + void shouldPostDeleteGravatarReturn200() throws Exception { + UserPreferences userPreferences = this.userPreferencesManager.getUserPreferences("jack_bauer"); + Assertions.assertNull(userPreferences); + try { + IUserProfile profile = this.userProfileManager.getDefaultProfileType(); + ITextAttribute emailAttribute = (ITextAttribute) profile.getAttributeByRole(SystemConstants.USER_PROFILE_ATTRIBUTE_ROLE_MAIL); + emailAttribute.setText("jack_bauer@jack_bauer.com", "it"); + profile.setId("jack_bauer"); + this.userProfileManager.addProfile("jack_bauer", profile); + String accessToken = this.createAccessToken(); + ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest(null, null, true); + ResultActions resultPost = mockMvc.perform( + post("/userProfiles/avatar") + .content(new ObjectMapper().writeValueAsString(profileAvatarRequest)) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header("Authorization", "Bearer " + accessToken)); + resultPost.andExpect(status().isOk()) + .andExpect(jsonPath("$.payload.useGravatar").value(true)); + userPreferences = this.userPreferencesManager.getUserPreferences("jack_bauer"); + Assertions.assertNotNull(userPreferences); + Assertions.assertTrue(userPreferences.isGravatar()); + + ResultActions resultGet = mockMvc.perform( + get("/userProfiles/avatar") + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header("Authorization", "Bearer " + accessToken)); + resultGet.andExpect(status().isOk()) + .andExpect(jsonPath("$.payload.useGravatar").value("true")); + + ResultActions resultDelete = mockMvc.perform( + delete("/userProfiles/avatar") + .header("Authorization", "Bearer " + accessToken)); + resultDelete.andExpect(status().isOk()) + .andExpect(jsonPath("$.payload.username").value("jack_bauer")) + .andExpect(jsonPath("$.errors.size()", CoreMatchers.is(0))) + .andExpect(jsonPath("$.metaData.size()", CoreMatchers.is(0))); + userPreferences = this.userPreferencesManager.getUserPreferences("jack_bauer"); + Assertions.assertNotNull(userPreferences); + Assertions.assertFalse(userPreferences.isGravatar()); + } finally { + this.userPreferencesManager.deleteUserPreferences("jack_bauer"); + this.userProfileManager.deleteProfile("jack_bauer"); + } + } + + @Test + void shouldReturnErrorOnPostGravatarWithNullProfile() throws Exception { + try { + String accessToken = this.createAccessToken(); + ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest(null, null, true); + ResultActions result = mockMvc.perform( + post("/userProfiles/avatar") + .content(new ObjectMapper().writeValueAsString(profileAvatarRequest)) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header("Authorization", "Bearer " + accessToken)); + result.andExpect(status().isBadRequest()); + UserPreferences userPreferences = this.userPreferencesManager.getUserPreferences("jack_bauer"); + Assertions.assertNull(userPreferences); + } finally { + this.userPreferencesManager.deleteUserPreferences("jack_bauer"); + this.userProfileManager.deleteProfile("jack_bauer"); + } + } + private String createAccessToken() throws Exception { UserDetails user = new OAuth2TestUtils.UserBuilder("jack_bauer", "0x24") .withAuthorization(Group.FREE_GROUP_NAME, "manageUserProfile", Permission.MANAGE_USER_PROFILES) @@ -604,5 +663,11 @@ private ResultActions executeProfileTypePost(String fileName, String accessToken result.andDo(resultPrint()).andExpect(expected); return result; } - + + + + + + + } diff --git a/engine/src/test/java/org/entando/entando/web/userprofile/UserProfileControllerTest.java b/engine/src/test/java/org/entando/entando/web/userprofile/UserProfileControllerTest.java index 3f38250d51..088af0dc7a 100644 --- a/engine/src/test/java/org/entando/entando/web/userprofile/UserProfileControllerTest.java +++ b/engine/src/test/java/org/entando/entando/web/userprofile/UserProfileControllerTest.java @@ -14,7 +14,6 @@ package org.entando.entando.web.userprofile; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -28,6 +27,7 @@ import com.agiletec.aps.system.services.user.UserDetails; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.stream.Stream; +import org.apache.commons.io.IOUtils; import org.entando.entando.aps.system.exception.ResourceNotFoundException; import org.entando.entando.aps.system.services.entity.model.EntityDto; import org.entando.entando.aps.system.services.userprofile.IAvatarService; @@ -51,6 +51,7 @@ import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.stubbing.Answer; +import org.springframework.core.io.ClassPathResource; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -71,7 +72,6 @@ class UserProfileControllerTest extends AbstractControllerTest { @Mock private IUserProfileManager userProfileManager; - @Mock private ProfileAvatarValidator profileAvatarValidator; @Mock @@ -79,6 +79,7 @@ class UserProfileControllerTest extends AbstractControllerTest { @BeforeEach public void setUp() throws Exception { + profileAvatarValidator = new ProfileAvatarValidator(userProfileManager); ProfileController controller = new ProfileController(userProfileService, profileValidator, profileAvatarValidator, userManager, userProfileManager, avatarService); @@ -188,17 +189,14 @@ void shouldGetAvatarReturn200AndWellFormedResponseIfImageExists() throws Excepti @Test void shouldPostAvatarReturn400OnIllegalInput() throws Exception { String accessToken = this.createAccessToken(); - Answer ans = invocation -> { Object[] args = invocation.getArguments(); ((BindingResult) args[1]).rejectValue("filename", "1", new String[]{"fileName_without_extension"}, "fileBrowser.filename.invalidFilename"); return null; }; - doAnswer(ans).when(profileAvatarValidator).validate(any(), any()); ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("fileName_without_extension", - new byte[1]); - + new byte[1], false); ResultActions result = mockMvc.perform( post("/userProfiles/avatar") .content(new ObjectMapper().writeValueAsString(profileAvatarRequest)) @@ -210,9 +208,9 @@ void shouldPostAvatarReturn400OnIllegalInput() throws Exception { @Test void shouldPostAvatarReturn200OnRightInput() throws Exception { String accessToken = this.createAccessToken(); - ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("myFile.png", new byte[1]); + ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("myFile.png", + IOUtils.toByteArray(new ClassPathResource("userprofile/image.png").getInputStream()), false); when(avatarService.updateAvatar(any(), any(), any())).thenReturn("jack_bauer.png"); - ResultActions result = mockMvc.perform( post("/userProfiles/avatar") .content(new ObjectMapper().writeValueAsString(profileAvatarRequest)) @@ -225,17 +223,15 @@ void shouldPostAvatarReturn200OnRightInput() throws Exception { @Test void shouldPostAvatarReturn400OnFileServiceAddFailureIfFileAlreadyPresent() throws Exception { String accessToken = this.createAccessToken(); - Answer ans = invocation -> { Object[] args = invocation.getArguments(); ((BindingResult) args[2]).reject("2", new String[]{"static/profile/jack-bauer.png", "false"}, "fileBrowser.file.exists"); return null; }; - doAnswer(ans).when(avatarService).updateAvatar(any(), any(), any()); + Mockito.lenient().doAnswer(ans).when(avatarService).updateAvatar(any(), any(), any()); ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("image.png", - new byte[1]); - + new byte[1], false); ResultActions result = mockMvc.perform( post("/userProfiles/avatar") .content(new ObjectMapper().writeValueAsString(profileAvatarRequest)) @@ -249,7 +245,6 @@ void shouldPostAvatarReturn400OnFileServiceAddFailureIfFileAlreadyPresent() thro @MethodSource("provideValuesFor400") void shouldPostAvatarReturn400(String request, String expectedErrorCode) throws Exception { String accessToken = this.createAccessToken(); - ResultActions result = mockMvc.perform( post("/userProfiles/avatar") .content(request) @@ -258,8 +253,7 @@ void shouldPostAvatarReturn400(String request, String expectedErrorCode) throws result.andExpect(status().isBadRequest()) .andExpect(jsonPath("$.errors[0].code").value(expectedErrorCode)); } - - + @Test void shouldDeleteAvatarReturn200() throws Exception { String accessToken = this.createAccessToken(); @@ -271,16 +265,15 @@ void shouldDeleteAvatarReturn200() throws Exception { .andExpect(jsonPath("$.errors.size()", CoreMatchers.is(0))) .andExpect(jsonPath("$.metaData.size()", CoreMatchers.is(0))); } - - + private static Stream provideValuesFor400() { return Stream.of( - Arguments.of("{\"filenam\":\"image.png\",\"base64\":\"AA==\"}", "NotBlank"), - Arguments.of("{\"base64\":\"AA==\"}", "NotBlank"), - Arguments.of("{\"filename\":\"\",\"base64\":\"AA==\"}", "NotBlank"), - Arguments.of("{\"filename\":\"image.png\",\"base6\":\"AA==\"}", "NotEmpty"), - Arguments.of("{\"filename\":\"image.png\"}", "NotEmpty"), - Arguments.of("{\"filename\":\"image.png\",\"base64\":\"\"}", "NotEmpty") + Arguments.of("{\"filenam\":\"image.png\",\"base64\":\"AA==\"}", "52"), + Arguments.of("{\"base64\":\"AA==\"}", "52"), + Arguments.of("{\"filename\":\"\",\"base64\":\"AA==\"}", "52"), + Arguments.of("{\"filename\":\"image.png\",\"base6\":\"AA==\"}", "53"), + Arguments.of("{\"filename\":\"image.png\"}", "53"), + Arguments.of("{\"filename\":\"image.png\",\"base64\":\"\"}", "2") ); } @@ -314,4 +307,5 @@ private String createAccessToken() throws Exception { UserDetails user = new OAuth2TestUtils.UserBuilder("jack_bauer", "0x24").grantedToRoleAdmin().build(); return mockOAuthInterceptor(user); } + } diff --git a/engine/src/test/java/org/entando/entando/web/userprofile/validator/ProfileAvatarValidatorTest.java b/engine/src/test/java/org/entando/entando/web/userprofile/validator/ProfileAvatarValidatorTest.java index 1a4b382f0e..5b281291f9 100644 --- a/engine/src/test/java/org/entando/entando/web/userprofile/validator/ProfileAvatarValidatorTest.java +++ b/engine/src/test/java/org/entando/entando/web/userprofile/validator/ProfileAvatarValidatorTest.java @@ -19,71 +19,122 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; +import com.agiletec.aps.system.SystemConstants; +import com.agiletec.aps.system.services.user.UserDetails; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import javax.imageio.ImageIO; import org.apache.commons.io.IOUtils; +import org.entando.entando.aps.system.services.userprofile.IUserProfileManager; +import org.entando.entando.aps.system.services.userprofile.model.IUserProfile; +import org.entando.entando.ent.exception.EntException; +import org.entando.entando.ent.exception.EntRuntimeException; import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.core.io.ClassPathResource; import org.springframework.validation.BeanPropertyBindingResult; import org.springframework.validation.FieldError; +@ExtendWith(MockitoExtension.class) class ProfileAvatarValidatorTest { - + + @Mock + private IUserProfileManager userProfileManager; + + @InjectMocks + private ProfileAvatarValidator profileAvatarValidator; @Test void shouldSupportOnlyProfileAvatarRequest() { - assertTrue(new ProfileAvatarValidator().supports(ProfileAvatarRequest.class)); - assertFalse(new ProfileAvatarValidator().supports(Object.class)); + assertTrue(profileAvatarValidator.supports(ProfileAvatarRequest.class)); + assertFalse(profileAvatarValidator.supports(Object.class)); } @Test void shouldNotValidateFileNamesMissingExtensions() throws IOException { ProfileAvatarRequest request = new ProfileAvatarRequest("missing_extension_filename", - IOUtils.toByteArray(new ClassPathResource("userprofile/image.png").getInputStream())); + IOUtils.toByteArray(new ClassPathResource("userprofile/image.png").getInputStream()), false); BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "profileAvatarRequest"); - new ProfileAvatarValidator().validate(request, errors); + profileAvatarValidator.validate(request, Mockito.mock(UserDetails.class), errors); assertEquals(1, errors.getErrorCount()); assertEquals("fileBrowser.filename.invalidFilename", errors.getAllErrors().get(0).getDefaultMessage()); assertEquals("missing_extension_filename", ((FieldError) errors.getAllErrors().get(0)).getRejectedValue()); } + @Test + void shouldNotValidateUserInCaseOfMissingProfile() throws Exception { + ProfileAvatarRequest request = new ProfileAvatarRequest(null, null, true); + UserDetails user = Mockito.mock(UserDetails.class); + Mockito.when(user.getUsername()).thenReturn("username_test"); + Mockito.when(userProfileManager.getProfile("username_test")).thenReturn(null); + BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "profileAvatarRequest"); + profileAvatarValidator.validate(request, user, errors); + assertEquals(1, errors.getErrorCount()); + assertEquals("avatar.emailAttribute.missing", errors.getAllErrors().get(0).getDefaultMessage()); + assertEquals("useGravatar", ((FieldError) errors.getAllErrors().get(0)).getField()); + } + + @Test + void shouldNotValidateUserInCaseOfMissingEmailAttribute() throws Exception { + ProfileAvatarRequest request = new ProfileAvatarRequest(null, null, true); + UserDetails user = Mockito.mock(UserDetails.class); + Mockito.when(user.getUsername()).thenReturn("username_test"); + IUserProfile userProfile = Mockito.mock(IUserProfile.class); + Mockito.when(userProfileManager.getProfile("username_test")).thenReturn(userProfile); + Mockito.when(userProfile.getAttributeByRole(SystemConstants.USER_PROFILE_ATTRIBUTE_ROLE_MAIL)).thenReturn(null); + BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "profileAvatarRequest"); + profileAvatarValidator.validate(request, user, errors); + assertEquals(1, errors.getErrorCount()); + assertEquals("avatar.emailAttribute.missing", errors.getAllErrors().get(0).getDefaultMessage()); + assertEquals("useGravatar", ((FieldError) errors.getAllErrors().get(0)).getField()); + } + + @Test + void shouldThrowServerErrorInCaseOfErrorGettingUserProfile() throws Exception { + ProfileAvatarRequest request = new ProfileAvatarRequest(null, null, true); + UserDetails user = Mockito.mock(UserDetails.class); + Mockito.when(user.getUsername()).thenReturn("username_test"); + Mockito.when(userProfileManager.getProfile("username_test")).thenThrow(EntException.class); + BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "profileAvatarRequest"); + assertThrows(EntRuntimeException.class, () -> profileAvatarValidator.validate(request, user, errors)); + } + @Test void shouldNotValidateFilesOtherThanImages() { String notValidBase64Image = "bm90IGFuIGltYWdl"; - ProfileAvatarRequest request = new ProfileAvatarRequest("valid_filename.txt", notValidBase64Image.getBytes()); + ProfileAvatarRequest request = new ProfileAvatarRequest("valid_filename.txt", notValidBase64Image.getBytes(), false); BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "profileAvatarRequest"); - new ProfileAvatarValidator().validate(request, errors); + profileAvatarValidator.validate(request, Mockito.mock(UserDetails.class), errors); assertEquals(1, errors.getErrorCount()); assertEquals("fileBrowser.file.invalidType", errors.getAllErrors().get(0).getDefaultMessage()); assertEquals("base64", ((FieldError) errors.getAllErrors().get(0)).getField()); - } - @Test void shouldThrowUncheckedIOExceptionIfImageReadingFails() throws IOException { ProfileAvatarRequest request = new ProfileAvatarRequest("image.png", - IOUtils.toByteArray(new ClassPathResource("userprofile/image.png").getInputStream())); + IOUtils.toByteArray(new ClassPathResource("userprofile/image.png").getInputStream()), false); BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "profileAvatarRequest"); try (MockedStatic mockStatic = Mockito.mockStatic(ImageIO.class)) { mockStatic.when(() -> ImageIO.read(any(InputStream.class))).thenThrow(IOException.class); - ProfileAvatarValidator profileAvatarValidator = new ProfileAvatarValidator(); - assertThrows(UncheckedIOException.class, () -> profileAvatarValidator.validate(request, errors)); + assertThrows(UncheckedIOException.class, () -> profileAvatarValidator.validate(request, Mockito.mock(UserDetails.class), errors)); } } @Test void shouldValidateAcceptValidImageWithValidFileName() throws IOException { ProfileAvatarRequest request = new ProfileAvatarRequest("image.png", - IOUtils.toByteArray(new ClassPathResource("userprofile/image.png").getInputStream())); + IOUtils.toByteArray(new ClassPathResource("userprofile/image.png").getInputStream()), false); BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "profileAvatarRequest"); - new ProfileAvatarValidator().validate(request, errors); + profileAvatarValidator.validate(request, Mockito.mock(UserDetails.class), errors); assertTrue(errors.getAllErrors().isEmpty()); - } + } diff --git a/pom.xml b/pom.xml index ca7f4382ae..10854e5cb8 100644 --- a/pom.xml +++ b/pom.xml @@ -80,8 +80,6 @@ agile jdbc:derby:memory:testPort;create=true jdbc:derby:memory:testServ;create=true - 11 - 11 5.3.27 5.5.7 @@ -155,7 +153,7 @@ 0.8 2.18.0 4.4 - 1.18.20 + 1.18.30 3.3.0 3.0.0-M4 2.5 diff --git a/seo-plugin/README.md b/seo-plugin/README.md index a2ae27761a..bc1c641d05 100644 --- a/seo-plugin/README.md +++ b/seo-plugin/README.md @@ -15,19 +15,6 @@ entando-plugin-jpseo The SEO plugin enables some functionality inside the page configuration (new parameters and friendly url), in the content handling (new role attribute to pilot friendly url) and to extract the sitemap. -## Installation - -In order to install the SEO plugin, you must insert the following dependency in the pom.xml file of your project: - -``` - - org.entando.entando.plugins - entando-plugin-jpseo - ${entando.version} - war - -``` - ## How to use ###### Modify of web.xml diff --git a/versioning-plugin/README.md b/versioning-plugin/README.md index 637c0b188e..b3c443f772 100644 --- a/versioning-plugin/README.md +++ b/versioning-plugin/README.md @@ -19,18 +19,6 @@ This plugin adds new administration interfaces related to the content tracking; Please also be aware that a resource referenced by older versions of the content cannot be deleted to avoid inconsistency on restore. Though the plugin installation is not difficult at all, we are going to modify the system tables, so a backup of your database is highly recommended. Furthermore, you may be required to customize the scripts to your needs before installation. -**Installation** - -In order to install the Versioning Plugin, you must insert the following dependency in the pom.xml file of your project: -``` - - org.entando.entando.plugins - entando-plugin-jpversioning - ${entando.version} - war - -``` - **Configuration** You can access to Versioning Plugin’s functionality through the usual left menu: _Plugins_ -> _Versioning_ in the Administration Area.