diff --git a/core/src/test/resources/org/fao/geonet/api/Messages.properties b/core/src/test/resources/org/fao/geonet/api/Messages.properties index 8c09a2d1398..f8bf6b86e79 100644 --- a/core/src/test/resources/org/fao/geonet/api/Messages.properties +++ b/core/src/test/resources/org/fao/geonet/api/Messages.properties @@ -178,6 +178,9 @@ api.exception.unsatisfiedRequestParameter.description=Unsatisfied request parame exception.maxUploadSizeExceeded=Maximum upload size of {0} exceeded. exception.maxUploadSizeExceeded.description=The request was rejected because its size ({0}) exceeds the configured maximum ({1}). exception.maxUploadSizeExceededUnknownSize.description=The request was rejected because its size exceeds the configured maximum ({0}). +exception.notAllowed.cannotEdit=Operation not allowed. User needs to be able to edit the resource. +exception.notAllowed.cannotView=Operation not allowed. User needs to be able to view the resource. +exception.notAllowed.mustBeProfileOrOwner=Operation not allowed. User must be ''{0}'' or the owner of the resource. exception.resourceNotFound.metadata=Metadata not found exception.resourceNotFound.metadata.description=Metadata with UUID ''{0}'' not found. exception.resourceNotFound.resource=Metadata resource ''{0}'' not found @@ -242,7 +245,6 @@ api.metadata.share.errorMetadataNotApproved=The metadata '%s' it's not approved, api.metadata.share.ErrorUserNotAllowedToPublish=User not allowed to publish the metadata %s. %s api.metadata.share.strategy.groupOwnerOnly=You need to be administrator, or reviewer of the metadata group. api.metadata.share.strategy.reviewerInGroup=You need to be administrator, or reviewer of the metadata group or reviewer with edit privilege on the metadata. -api.metadata.status.errorGetStatusNotAllowed=Only the owner of the metadata can get the status. User is not the owner of the metadata. api.metadata.status.errorSetStatusNotAllowed=Only the owner of the metadata can set the status of this record. User is not the owner of the metadata. feedback_subject_userFeedback=User feedback diff --git a/core/src/test/resources/org/fao/geonet/api/Messages_fre.properties b/core/src/test/resources/org/fao/geonet/api/Messages_fre.properties index db0fc9f14e1..575315c2b72 100644 --- a/core/src/test/resources/org/fao/geonet/api/Messages_fre.properties +++ b/core/src/test/resources/org/fao/geonet/api/Messages_fre.properties @@ -173,6 +173,9 @@ api.exception.unsatisfiedRequestParameter.description=Param\u00E8tre de demande exception.maxUploadSizeExceeded=La taille maximale du t\u00E9l\u00E9chargement de {0} a \u00E9t\u00E9 exc\u00E9d\u00E9e. exception.maxUploadSizeExceeded.description=La demande a \u00E9t\u00E9 refus\u00E9e car sa taille ({0}) exc\u00E8de le maximum configur\u00E9 ({1}). exception.maxUploadSizeExceededUnknownSize.description=La demande a \u00E9t\u00E9 refus\u00E9e car sa taille exc\u00E8de le maximum configur\u00E9 ({0}). +exception.notAllowed.cannotEdit=Op\u00E9ration non autoris\u00E9e. L'utilisateur doit pouvoir modifier la ressource. +exception.notAllowed.cannotView=Op\u00E9ration non autoris\u00E9e. L'utilisateur doit pouvoir visualiser la ressource. +exception.notAllowed.mustBeProfileOrOwner=Op\u00E9ration non autoris\u00E9e. L''utilisateur doit être ''{0}'' ou le propri\u00E9taire de la ressource. exception.resourceNotFound.metadata=Fiches introuvables exception.resourceNotFound.metadata.description=La fiche ''{0}'' est introuvable. exception.resourceNotFound.resource=Ressource ''{0}'' introuvable @@ -235,7 +238,6 @@ api.metadata.share.errorMetadataNotApproved=La fiche '%s' n'est pas approuv\u00E api.metadata.share.ErrorUserNotAllowedToPublish=L'utilisateur n'est pas autoris\u00E9 \u00E0 publier la fiche %s. %s api.metadata.share.strategy.groupOwnerOnly=Vous devez \u00EAtre administrateur ou relecteur du groupe de la fiche. api.metadata.share.strategy.reviewerInGroup=Vous devez \u00EAtre administrateur ou relecteur du groupe de la fiche ou relecteur avec un privil\u00E8ge de modification sur les fiches. -api.metadata.status.errorGetStatusNotAllowed=Seul le propri\u00E9taire des m\u00E9tadonn\u00E9es peut obtenir le statut de cet enregistrement. L'utilisateur n'est pas le propri\u00E9taire des m\u00E9tadonn\u00E9es api.metadata.status.errorSetStatusNotAllowed=Seul le propri\u00E9taire des m\u00E9tadonn\u00E9es peut d\u00E9finir le statut de cet enregistrement. L'utilisateur n'est pas le propri\u00E9taire des m\u00E9tadonn\u00E9es feedback_subject_userFeedback=Commentaire de l'utilisateur diff --git a/services/src/main/java/org/fao/geonet/api/records/MetadataWorkflowApi.java b/services/src/main/java/org/fao/geonet/api/records/MetadataWorkflowApi.java index ce1fd6fa888..582fafccb37 100644 --- a/services/src/main/java/org/fao/geonet/api/records/MetadataWorkflowApi.java +++ b/services/src/main/java/org/fao/geonet/api/records/MetadataWorkflowApi.java @@ -58,6 +58,8 @@ import org.fao.geonet.kernel.metadata.StatusChangeType; import org.fao.geonet.kernel.search.EsSearchManager; import org.fao.geonet.kernel.search.IndexingMode; +import org.fao.geonet.kernel.search.Translator; +import org.fao.geonet.kernel.search.TranslatorFactory; import org.fao.geonet.kernel.setting.SettingManager; import org.fao.geonet.kernel.setting.Settings; import org.fao.geonet.languages.FeedbackLanguages; @@ -82,6 +84,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import java.text.MessageFormat; import java.util.*; import java.util.stream.Collectors; @@ -160,6 +163,9 @@ public class MetadataWorkflowApi { @Autowired RoleHierarchy roleHierarchy; + @Autowired + TranslatorFactory translatorFactory; + // The restore function currently supports these states static final StatusValue.Events[] supportedRestoreStatuses = StatusValue.Events.getSupportedRestoreStatuses(); @@ -180,13 +186,13 @@ public List getRecordStatusHistory( @RequestParam(required = false, defaultValue = "true") Boolean approved, HttpServletRequest request) throws Exception { ServiceContext context = ApiUtils.createServiceContext(request); - + ResourceBundle messages = ApiUtils.getMessagesResourceBundle(request.getLocales()); AbstractMetadata metadata; try { metadata = ApiUtils.canViewRecord(metadataUuid, approved, request); } catch (SecurityException e) { Log.debug(API.LOG_MODULE_NAME, e.getMessage(), e); - throw new NotAllowedException(ApiParams.API_RESPONSE_NOT_ALLOWED_CAN_VIEW); + throw new NotAllowedException(messages.getString("exception.notAllowed.cannotView")); } String sortField = SortUtils.createPath(MetadataStatus_.changeDate); @@ -212,12 +218,13 @@ public List getRecordStatusHistoryByType( @RequestParam(required = false, defaultValue = "true") Boolean approved, HttpServletRequest request) throws Exception { ServiceContext context = ApiUtils.createServiceContext(request); + ResourceBundle messages = ApiUtils.getMessagesResourceBundle(request.getLocales()); AbstractMetadata metadata; try { metadata = ApiUtils.canViewRecord(metadataUuid, approved, request); } catch (SecurityException e) { Log.debug(API.LOG_MODULE_NAME, e.getMessage(), e); - throw new NotAllowedException(ApiParams.API_RESPONSE_NOT_ALLOWED_CAN_VIEW); + throw new NotAllowedException(messages.getString("exception.notAllowed.cannotView")); } String sortField = SortUtils.createPath(MetadataStatus_.changeDate); @@ -233,7 +240,7 @@ public List getRecordStatusHistoryByType( @io.swagger.v3.oas.annotations.Operation(summary = "Get last workflow status for a record", description = "") @RequestMapping(value = "/{metadataUuid}/status/workflow/last", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE}) - @PreAuthorize("hasAuthority('Editor')") + @PreAuthorize("hasAuthority('RegisteredUser')") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Record status."), @ApiResponse(responseCode = "403", description = ApiParams.API_RESPONSE_NOT_ALLOWED_CAN_EDIT)}) @ResponseStatus(HttpStatus.OK) @@ -243,15 +250,28 @@ public MetadataWorkflowStatusResponse getStatus( @Parameter(description = "Use approved version or not", example = "true") @RequestParam(required = false, defaultValue = "true") Boolean approved, HttpServletRequest request) throws Exception { - AbstractMetadata metadata = ApiUtils.canEditRecord(metadataUuid, approved, request); + AbstractMetadata metadata = ApiUtils.getRecord(metadataUuid); Locale locale = languageUtils.parseAcceptLanguage(request.getLocales()); ResourceBundle messages = ApiUtils.getMessagesResourceBundle(request.getLocales()); ServiceContext context = ApiUtils.createServiceContext(request, locale.getISO3Language()); - // --- only allow the owner of the record to set its status + // If the user does not own the record check if they meet the minimum profile if (!accessManager.isOwner(context, String.valueOf(metadata.getId()))) { - throw new SecurityException( - messages.getString("api.metadata.status.errorGetStatusNotAllowed")); + Profile userProfile = context.getUserSession().getProfile(); + String minimumAllowedProfileName = StringUtils.defaultIfBlank( + settingManager.getValue(Settings.METADATA_HISTORY_ACCESS_LEVEL), + Profile.Editor.toString() + ); + Profile minimumAllowedProfile = Profile.valueOf(minimumAllowedProfileName); + + if (!minimumAllowedProfile.getProfileAndAllParents().contains(userProfile)) { + // If the user profile is not at least the minimum profile, then the user is not allowed to view record workflow status + String message = getMustBeProfileOrOwnerMessage(minimumAllowedProfileName, messages); + Log.debug(API.LOG_MODULE_NAME, message); + throw new NotAllowedException(message); + } + + checkUserCanSeeHistory(minimumAllowedProfile, metadataUuid, messages, request); } MetadataStatus recordStatus = metadataStatus.getStatus(metadata.getId()); @@ -720,12 +740,18 @@ public List getWorkflowStatusByType( Integer size, HttpServletRequest request) throws Exception { ServiceContext context = ApiUtils.createServiceContext(request); + ResourceBundle messages = ApiUtils.getMessagesResourceBundle(request.getLocales()); - Profile profile = context.getUserSession().getProfile(); - String allowedProfileLevel = org.apache.commons.lang.StringUtils.defaultIfBlank(settingManager.getValue(Settings.METADATA_HISTORY_ACCESS_LEVEL), Profile.Editor.toString()); - Profile allowedAccessLevelProfile = Profile.valueOf(allowedProfileLevel); + Profile userProfile = context.getUserSession().getProfile(); + String minimumAllowedProfileName = StringUtils.defaultIfBlank( + settingManager.getValue(Settings.METADATA_HISTORY_ACCESS_LEVEL), + Profile.Editor.toString() + ); + Profile minimumAllowedProfile = Profile.valueOf(minimumAllowedProfileName); + boolean isMinimumAllowedProfile = minimumAllowedProfile.getProfileAndAllParents().contains(userProfile); + String mustBeProfileOrOwnerMessage = getMustBeProfileOrOwnerMessage(minimumAllowedProfileName, messages); - if (profile != Profile.Administrator) { + if (userProfile != Profile.Administrator) { if (CollectionUtils.isEmpty(recordIdentifier) && CollectionUtils.isEmpty(uuid)) { throw new NotAllowedException( @@ -734,30 +760,24 @@ public List getWorkflowStatusByType( if (!CollectionUtils.isEmpty(recordIdentifier)) { for (Integer recordId : recordIdentifier) { - try { - if (allowedAccessLevelProfile == Profile.RegisteredUser) { - ApiUtils.canViewRecord(String.valueOf(recordId), request); - } else { - ApiUtils.canEditRecord(String.valueOf(recordId), request); - } - - } catch (SecurityException e) { - throw new NotAllowedException(ApiParams.API_RESPONSE_NOT_ALLOWED_CAN_EDIT); + // Handle record not found + AbstractMetadata metadata = ApiUtils.getRecord(String.valueOf(recordId)); + if (!isMinimumAllowedProfile && !accessManager.isOwner(context, metadata.getSourceInfo())) { + Log.debug(API.LOG_MODULE_NAME, mustBeProfileOrOwnerMessage); + throw new NotAllowedException(mustBeProfileOrOwnerMessage); } + checkUserCanSeeHistory(minimumAllowedProfile, String.valueOf(recordId), messages, request); } } if (!CollectionUtils.isEmpty(uuid)) { for (String recordId : uuid) { - try { - if (allowedAccessLevelProfile == Profile.RegisteredUser) { - ApiUtils.canViewRecord(recordId, request); - } else { - ApiUtils.canEditRecord(recordId, request); - } - - } catch (SecurityException e) { - throw new NotAllowedException(ApiParams.API_RESPONSE_NOT_ALLOWED_CAN_EDIT); + // Handle record not found + AbstractMetadata metadata = ApiUtils.getRecord(recordId); + if (!isMinimumAllowedProfile && !accessManager.isOwner(context, metadata.getSourceInfo())) { + Log.debug(API.LOG_MODULE_NAME, mustBeProfileOrOwnerMessage); + throw new NotAllowedException(mustBeProfileOrOwnerMessage); } + checkUserCanSeeHistory(minimumAllowedProfile, recordId, messages, request); } } } @@ -1245,6 +1265,8 @@ private MetadataStatus getMetadataStatus(String uuidOrInternalId, int statusId, private String getValidatedStateText(MetadataStatus metadataStatus, State state, HttpServletRequest request, HttpSession httpSession) throws Exception { + ResourceBundle messages = ApiUtils.getMessagesResourceBundle(request.getLocales()); + if (!StatusValueType.event.equals(metadataStatus.getStatusValue().getType()) || !ArrayUtils.contains(supportedRestoreStatuses, StatusValue.Events.fromId(metadataStatus.getStatusValue().getId()))) { throw new NotAllowedException("Unsupported action on status type '" + metadataStatus.getStatusValue().getType() @@ -1281,7 +1303,7 @@ private String getValidatedStateText(MetadataStatus metadataStatus, State state, ApiUtils.canEditRecord(metadataStatus.getUuid(), request); } catch (SecurityException e) { Log.debug(API.LOG_MODULE_NAME, e.getMessage(), e); - throw new NotAllowedException(ApiParams.API_RESPONSE_NOT_ALLOWED_CAN_VIEW); + throw new NotAllowedException(messages.getString("exception.notAllowed.cannotView")); } catch (ResourceNotFoundException e) { // If metadata record does not exists then it was deleted so // we will only allow the administrator, owner to view the contents @@ -1363,4 +1385,47 @@ private void changeMetadataStatus(ServiceContext context, AbstractMetadata metad sa.onStatusChange(listOfStatusChange, true); } + /** + * Constructs a message indicating that the user must have a specific profile or be the owner to perform an action. + * + * @param messages The resource bundle containing localized messages. + * @param minimumAllowedProfileName The name of the minimum allowed profile. + * @return A formatted message indicating the required profile or ownership. + */ + private String getMustBeProfileOrOwnerMessage(String minimumAllowedProfileName, ResourceBundle messages) { + Translator jsonLocTranslator = translatorFactory.getTranslator("apploc:", messages.getLocale().getISO3Language()); + return MessageFormat.format( + messages.getString("exception.notAllowed.mustBeProfileOrOwner"), + jsonLocTranslator.translate(minimumAllowedProfileName) + ); + } + + /** + * Checks if the user has the necessary permissions to view the history of a record. + * + * @param minimumAllowedProfile The minimum profile required to view the history. + * @param recordId The ID of the record. + * @param messages The resource bundle containing localized messages. + * @param request The HTTP request object. + * @throws Exception If the user does not have the necessary permissions. + */ + private void checkUserCanSeeHistory(Profile minimumAllowedProfile, String recordId, ResourceBundle messages, HttpServletRequest request) throws Exception { + if (minimumAllowedProfile == Profile.RegisteredUser) { + // If the minimum profile is RegisteredUser, then the user must be able to view the record + try { + ApiUtils.canViewRecord(recordId, request); + } catch (SecurityException e) { + Log.debug(API.LOG_MODULE_NAME, e.getMessage(), e); + throw new NotAllowedException(messages.getString("exception.notAllowed.cannotView")); + } + } else if (minimumAllowedProfile == Profile.Editor) { + // If the minimum profile is Editor, then the user must be able to edit the record + try { + ApiUtils.canEditRecord(recordId, request); + } catch (SecurityException e) { + Log.debug(API.LOG_MODULE_NAME, e.getMessage(), e); + throw new NotAllowedException(messages.getString("exception.notAllowed.cannotEdit")); + } + } + } } diff --git a/web/src/main/webapp/WEB-INF/classes/org/fao/geonet/api/Messages.properties b/web/src/main/webapp/WEB-INF/classes/org/fao/geonet/api/Messages.properties index c2e89994dce..b8c9e0f1d70 100644 --- a/web/src/main/webapp/WEB-INF/classes/org/fao/geonet/api/Messages.properties +++ b/web/src/main/webapp/WEB-INF/classes/org/fao/geonet/api/Messages.properties @@ -184,6 +184,9 @@ api.exception.unsatisfiedRequestParameter.description=Unsatisfied request parame exception.maxUploadSizeExceeded=Maximum upload size of {0} exceeded. exception.maxUploadSizeExceeded.description=The request was rejected because its size ({0}) exceeds the configured maximum ({1}). exception.maxUploadSizeExceededUnknownSize.description=The request was rejected because its size exceeds the configured maximum ({0}). +exception.notAllowed.cannotEdit=Operation not allowed. User needs to be able to edit the resource. +exception.notAllowed.cannotView=Operation not allowed. User needs to be able to view the resource. +exception.notAllowed.mustBeProfileOrOwner=Operation not allowed. User must be ''{0}'' or the owner of the resource. exception.resourceNotFound.metadata=Metadata not found exception.resourceNotFound.metadata.description=Metadata with UUID ''{0}'' not found. exception.resourceNotFound.resource=Metadata resource ''{0}'' not found @@ -250,7 +253,6 @@ api.metadata.share.errorMetadataNotApproved=The metadata '%s' is not approved, c api.metadata.share.ErrorUserNotAllowedToPublish=User not allowed to publish the metadata %s. %s api.metadata.share.strategy.groupOwnerOnly=You need to be administrator, or reviewer of the metadata group. api.metadata.share.strategy.reviewerInGroup=You need to be administrator, or reviewer of the metadata group or reviewer with edit privilege on the metadata. -api.metadata.status.errorGetStatusNotAllowed=Only the owner of the metadata can get the status. User is not the owner of the metadata. api.metadata.status.errorSetStatusNotAllowed=Only the owner of the metadata can set the status of this record. User is not the owner of the metadata. feedback_subject_userFeedback=User feedback diff --git a/web/src/main/webapp/WEB-INF/classes/org/fao/geonet/api/Messages_dut.properties b/web/src/main/webapp/WEB-INF/classes/org/fao/geonet/api/Messages_dut.properties index d42721e4b0d..54ebe1ac825 100644 --- a/web/src/main/webapp/WEB-INF/classes/org/fao/geonet/api/Messages_dut.properties +++ b/web/src/main/webapp/WEB-INF/classes/org/fao/geonet/api/Messages_dut.properties @@ -184,6 +184,9 @@ api.exception.unsatisfiedRequestParameter.description=Niet-voldane verzoeksparam exception.maxUploadSizeExceeded=Maximale uploadgrootte van {0} is overschreden. exception.maxUploadSizeExceeded.description=Het verzoek werd afgewezen omdat de grootte ({0}) het ingestelde maximum ({1}) overschrijdt. exception.maxUploadSizeExceededUnknownSize.description=Het verzoek afgewezen omdat de grootte het ingestelde maximum ({0}) overschrijdt. +exception.notAllowed.cannotEdit=Bewerking niet toegestaan. De gebruiker moet de bron kunnen bewerken. +exception.notAllowed.cannotView=Actie niet toegestaan. Gebruiker moet de bron kunnen bekijken. +exception.notAllowed.mustBeProfileOrOwner=Actie niet toegestaan. Gebruiker moet ''{0}'' of de eigenaar van de bron zijn. exception.resourceNotFound.metadata=Metadata niet gevonden exception.resourceNotFound.metadata.description=Metadata met UUID ''{0}'' niet gevonden. exception.resourceNotFound.resource=Metadatabron ''{0}'' niet gevonden @@ -250,7 +253,6 @@ api.metadata.share.errorMetadataNotApproved=Het metadata record '%s' is niet goe api.metadata.share.ErrorUserNotAllowedToPublish=De gebruiker mag metadata record %s niet publiceren. %s api.metadata.share.strategy.groupOwnerOnly=Je moet een beheerder zijn of een reviewer van de metadata groep. api.metadata.share.strategy.reviewerInGroup=Je moet een beheerder zijn of een reviewer van de metadata groep, of een reviewer met privileges om de metadata te bewerken. -api.metadata.status.errorGetStatusNotAllowed=Alleen de eigenaar van de metadata kan de status opvragen. De gebruiker is geen eigenaar van de metadata. api.metadata.status.errorSetStatusNotAllowed=Alleen de eigenaar van de metadata kan de status wijzigen. De gebruiker is geen eigenaar van de metadata. feedback_subject_userFeedback=Gebruikersfeedback diff --git a/web/src/main/webapp/WEB-INF/classes/org/fao/geonet/api/Messages_fre.properties b/web/src/main/webapp/WEB-INF/classes/org/fao/geonet/api/Messages_fre.properties index a52fec3dc97..5aa133b5323 100644 --- a/web/src/main/webapp/WEB-INF/classes/org/fao/geonet/api/Messages_fre.properties +++ b/web/src/main/webapp/WEB-INF/classes/org/fao/geonet/api/Messages_fre.properties @@ -179,6 +179,9 @@ api.exception.unsatisfiedRequestParameter.description=Param\u00E8tre de demande exception.maxUploadSizeExceeded=La taille maximale du t\u00E9l\u00E9chargement de {0} a \u00E9t\u00E9 exc\u00E9d\u00E9e. exception.maxUploadSizeExceeded.description=La demande a \u00E9t\u00E9 refus\u00E9e car sa taille ({0}) exc\u00E8de le maximum configur\u00E9 ({1}). exception.maxUploadSizeExceededUnknownSize.description=La demande a \u00E9t\u00E9 refus\u00E9e car sa taille exc\u00E8de le maximum configur\u00E9 ({0}). +exception.notAllowed.cannotEdit=Op\u00E9ration non autoris\u00E9e. L'utilisateur doit pouvoir modifier la ressource. +exception.notAllowed.cannotView=Op\u00E9ration non autoris\u00E9e. L'utilisateur doit pouvoir visualiser la ressource. +exception.notAllowed.mustBeProfileOrOwner=Op\u00E9ration non autoris\u00E9e. L''utilisateur doit être ''{0}'' ou le propri\u00E9taire de la ressource. exception.resourceNotFound.metadata=Fiches introuvables exception.resourceNotFound.metadata.description=La fiche ''{0}'' est introuvable. exception.resourceNotFound.resource=Ressource ''{0}'' introuvable @@ -243,7 +246,6 @@ api.metadata.share.errorMetadataNotApproved=La fiche '%s' n'est pas approuv\u00E api.metadata.share.ErrorUserNotAllowedToPublish=L'utilisateur n'est pas autoris\u00E9 \u00E0 publier la fiche %s. %s api.metadata.share.strategy.groupOwnerOnly=Vous devez \u00EAtre administrateur ou relecteur du groupe de la fiche. api.metadata.share.strategy.reviewerInGroup=Vous devez \u00EAtre administrateur ou relecteur du groupe de la fiche ou relecteur avec un privil\u00E8ge de modification sur les fiches. -api.metadata.status.errorGetStatusNotAllowed=Seul le propri\u00E9taire des m\u00E9tadonn\u00E9es peut obtenir le statut de cet enregistrement. L'utilisateur n'est pas le propri\u00E9taire des m\u00E9tadonn\u00E9es api.metadata.status.errorSetStatusNotAllowed=Seul le propri\u00E9taire des m\u00E9tadonn\u00E9es peut d\u00E9finir le statut de cet enregistrement. L'utilisateur n'est pas le propri\u00E9taire des m\u00E9tadonn\u00E9es feedback_subject_userFeedback=Commentaire de l'utilisateur