IdentityJspBean.java
/*
* Copyright (c) 2002-2024, City of Paris
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright notice
* and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice
* and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* 3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* License 1.0
*/
package fr.paris.lutece.plugins.identitystore.web;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import fr.paris.lutece.plugins.identitystore.business.duplicates.suspicions.SuspiciousIdentityHome;
import fr.paris.lutece.plugins.identitystore.business.identity.Identity;
import fr.paris.lutece.plugins.identitystore.business.identity.IdentityAttributeHome;
import fr.paris.lutece.plugins.identitystore.business.identity.IdentityHome;
import fr.paris.lutece.plugins.identitystore.service.IdentityManagementResourceIdService;
import fr.paris.lutece.plugins.identitystore.service.identity.IdentityService;
import fr.paris.lutece.plugins.identitystore.service.search.ISearchIdentityService;
import fr.paris.lutece.plugins.identitystore.utils.Batch;
import fr.paris.lutece.plugins.identitystore.v3.csv.CsvIdentityService;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.DtoConverter;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.AttributeTreatmentType;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.BatchDto;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.IdentityDto;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.history.AttributeChange;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.history.IdentityChange;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.importing.BatchImportRequest;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.search.SearchAttribute;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.util.Constants;
import fr.paris.lutece.plugins.identitystore.web.exception.IdentityStoreException;
import fr.paris.lutece.portal.service.admin.AccessDeniedException;
import fr.paris.lutece.portal.service.security.AccessLogService;
import fr.paris.lutece.portal.service.security.AccessLoggerConstants;
import fr.paris.lutece.portal.service.spring.SpringContextService;
import fr.paris.lutece.portal.service.util.AppPropertiesService;
import fr.paris.lutece.portal.util.mvc.admin.annotations.Controller;
import fr.paris.lutece.portal.util.mvc.commons.annotations.Action;
import fr.paris.lutece.portal.util.mvc.commons.annotations.View;
import fr.paris.lutece.util.http.SecurityUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayOutputStream;
import java.sql.Date;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* This class provides the user interface to manage Identity features ( manage, create, modify, remove )
*/
@Controller( controllerJsp = "ManageIdentities.jsp", controllerPath = "jsp/admin/plugins/identitystore/", right = "IDENTITYSTORE_MANAGEMENT" )
public class IdentityJspBean extends ManageIdentitiesJspBean
{
/**
*
*/
private static final long serialVersionUID = 6053504380426222888L;
// Templates
private static final String TEMPLATE_SEARCH_IDENTITIES = "/admin/plugins/identitystore/search_identities.html";
private static final String TEMPLATE_VIEW_IDENTITY = "/admin/plugins/identitystore/view_identity.html";
private static final String TEMPLATE_VIEW_IDENTITY_HISTORY = "/admin/plugins/identitystore/view_identity_change_history.html";
// Parameters
private static final String PARAMETER_ID_IDENTITY = "id";
// Properties for page titles
private static final String PROPERTY_PAGE_TITLE_MANAGE_IDENTITIES = "identitystore.manage_identities.pageTitle";
private static final String PROPERTY_PAGE_TITLE_VIEW_IDENTITY = "identitystore.create_identity.pageTitle";
private static final String PROPERTY_PAGE_TITLE_VIEW_CHANGE_HISTORY = "identitystore.view_change_history.pageTitle";
// Markers
private static final String MARK_IDENTITY_LIST = "identity_list";
private static final String MARK_IDENTITY = "identity";
private static final String MARK_IDENTITY_IS_SUSPICIOUS = "identity_is_suspicious";
private static final String MARK_IDENTITY_CHANGE_LIST = "identity_change_list";
private static final String MARK_ATTRIBUTES_CHANGE_LIST = "attributes_change_list";
private static final String MARK_HAS_CREATE_ROLE = "createIdentityRole";
private static final String MARK_HAS_MODIFY_ROLE = "modifyIdentityRole";
private static final String MARK_HAS_DELETE_ROLE = "deleteIdentityRole";
private static final String MARK_HAS_VIEW_ROLE = "viewIdentityRole";
private static final String MARK_HAS_ATTRIBUTS_HISTO_ROLE = "histoAttributsRole";
private static final String JSP_MANAGE_IDENTITIES = "jsp/admin/plugins/identitystore/ManageIdentities.jsp";
// Views
private static final String VIEW_MANAGE_IDENTITIES = "manageIdentitys";
private static final String VIEW_IDENTITY = "viewIdentity";
private static final String VIEW_IDENTITY_HISTORY = "viewIdentityHistory";
// Actions
private static final String ACTION_EXPORT_IDENTITIES = "exportIdentities";
private static final String ACTION_BATCH_GENERATE_REQUESTS = "exportRequestIdentities";
// Events
private static final String DISPLAY_IDENTITY_EVENT_CODE = "DISPLAY_IDENTITY";
private static final String DISPLAY_IDENTITY_HISTORY_EVENT_CODE = "DISPLAY_HISTORY_IDENTITY";
// Datasource
private static final String DATASOURCE_DB = "db";
private static final String DATASOURCE_ES = "es";
private static final int BATCH_PARTITION_SIZE = AppPropertiesService.getPropertyInt( "identitystore.export.batch.size", 100 );
// Session variable to store working values
private Identity _identity;
private final List<IdentityDto> _identities = new ArrayList<>( );
private final ISearchIdentityService _searchIdentityServiceDB = SpringContextService.getBean( "identitystore.searchIdentityService.database" );
private final ISearchIdentityService _searchIdentityServiceES = SpringContextService.getBean( "identitystore.searchIdentityService.elasticsearch" );
@View( value = VIEW_MANAGE_IDENTITIES, defaultView = true )
public String getManageIdentitys( HttpServletRequest request )
{
_identity = null;
_identities.clear( );
final Map<String, String> queryParameters = this.getQueryParameters( request );
final List<SearchAttribute> atttributes = new ArrayList<>( );
final String cuid = queryParameters.get( QUERY_PARAM_CUID );
final String guid = queryParameters.get( QUERY_PARAM_GUID );
final String email = queryParameters.get( QUERY_PARAM_EMAIL );
final String gender = queryParameters.get( QUERY_PARAM_GENDER );
final String family_name = queryParameters.get( QUERY_PARAM_FAMILY_NAME );
final String preferred_username = queryParameters.get( QUERY_PARAM_PREFERRED_USERNAME );
final String first_name = queryParameters.get( QUERY_PARAM_FIRST_NAME );
final String birthdate = queryParameters.get( QUERY_PARAM_BIRTHDATE );
final String birthplace = queryParameters.get( QUERY_PARAM_INSEE_BIRTHPLACE_LABEL );
final String birthcountry = queryParameters.get( QUERY_PARAM_INSEE_BIRTHCOUNTRY_LABEL );
final String phone = queryParameters.get( QUERY_PARAM_PHONE );
final String datasource = Optional.ofNullable( queryParameters.get( QUERY_PARAM_DATASOURCE ) ).orElse( DATASOURCE_DB );
try
{
if ( StringUtils.isNotEmpty( cuid ) )
{
final Identity identity = IdentityHome.findMasterIdentityByCustomerId( cuid );
if ( identity != null )
{
final IdentityDto qualifiedIdentity = DtoConverter.convertIdentityToDto( identity );
_identities.add( qualifiedIdentity );
}
}
else
{
if ( StringUtils.isNotEmpty( guid ) )
{
final Identity identity = IdentityHome.findMasterIdentityByConnectionId( guid );
if ( identity != null )
{
final IdentityDto qualifiedIdentity = DtoConverter.convertIdentityToDto( identity );
_identities.add( qualifiedIdentity );
}
}
else
{
if ( StringUtils.isNotEmpty( email ) )
{
atttributes.add( new SearchAttribute( Constants.PARAM_LOGIN, email, AttributeTreatmentType.STRICT ) );
}
if ( StringUtils.isNotEmpty( gender ) )
{
atttributes.add( new SearchAttribute( Constants.PARAM_GENDER, gender, AttributeTreatmentType.STRICT ) );
}
if ( StringUtils.isNotEmpty( family_name ) )
{
atttributes.add( new SearchAttribute( Constants.PARAM_FAMILY_NAME, family_name, AttributeTreatmentType.APPROXIMATED ) );
}
if ( StringUtils.isNotEmpty( preferred_username ) )
{
atttributes.add( new SearchAttribute( Constants.PARAM_PREFERRED_USERNAME, preferred_username, AttributeTreatmentType.APPROXIMATED ) );
}
if ( StringUtils.isNotEmpty( first_name ) )
{
atttributes.add( new SearchAttribute( Constants.PARAM_FIRST_NAME, first_name, AttributeTreatmentType.APPROXIMATED ) );
}
if ( StringUtils.isNotEmpty( birthdate ) )
{
atttributes.add( new SearchAttribute( Constants.PARAM_BIRTH_DATE, birthdate, AttributeTreatmentType.STRICT ) );
}
if ( StringUtils.isNotEmpty( birthplace ) )
{
atttributes.add( new SearchAttribute( Constants.PARAM_BIRTH_PLACE, birthplace, AttributeTreatmentType.STRICT ) );
}
if ( StringUtils.isNotEmpty( birthcountry ) )
{
atttributes.add( new SearchAttribute( Constants.PARAM_BIRTH_COUNTRY, birthcountry, AttributeTreatmentType.STRICT ) );
}
if ( StringUtils.isNotEmpty( phone ) )
{
atttributes.add( new SearchAttribute( Constants.PARAM_MOBILE_PHONE, phone, AttributeTreatmentType.STRICT ) );
}
if ( CollectionUtils.isNotEmpty( atttributes ) )
{
if ( datasource.equals( DATASOURCE_DB ) )
{
_identities.addAll( _searchIdentityServiceDB.getQualifiedIdentities( atttributes, 0, false, Collections.emptyList( ) )
.getQualifiedIdentities( ) );
}
else
if ( datasource.equals( DATASOURCE_ES ) )
{
_identities.addAll( _searchIdentityServiceES.getQualifiedIdentities( atttributes, 0, false, Collections.emptyList( ) )
.getQualifiedIdentities( ) );
}
}
}
}
}
catch( Exception e )
{
addError( e.getMessage( ) );
this.clearParameters( request );
return redirectView( request, VIEW_MANAGE_IDENTITIES );
}
_identities.forEach(
qualifiedIdentity -> AccessLogService.getInstance( ).info( AccessLoggerConstants.EVENT_TYPE_READ, IdentityService.SEARCH_IDENTITY_EVENT_CODE,
getUser( ), SecurityUtil.logForgingProtect( qualifiedIdentity.getCustomerId( ) ), IdentityService.SPECIFIC_ORIGIN ) );
final Map<String, Object> model = getPaginatedListModel( request, MARK_IDENTITY_LIST, _identities, JSP_MANAGE_IDENTITIES );
model.put( MARK_HAS_CREATE_ROLE,
IdentityManagementResourceIdService.isAuthorized( IdentityManagementResourceIdService.PERMISSION_CREATE_IDENTITY, getUser( ) ) );
model.put( MARK_HAS_MODIFY_ROLE,
IdentityManagementResourceIdService.isAuthorized( IdentityManagementResourceIdService.PERMISSION_MODIFY_IDENTITY, getUser( ) ) );
model.put( MARK_HAS_DELETE_ROLE,
IdentityManagementResourceIdService.isAuthorized( IdentityManagementResourceIdService.PERMISSION_DELETE_IDENTITY, getUser( ) ) );
model.put( MARK_HAS_VIEW_ROLE,
IdentityManagementResourceIdService.isAuthorized( IdentityManagementResourceIdService.PERMISSION_VIEW_IDENTITY, getUser( ) ) );
model.put( QUERY_PARAM_CUID, cuid );
model.put( QUERY_PARAM_GUID, guid );
model.put( QUERY_PARAM_FAMILY_NAME, family_name );
model.put( QUERY_PARAM_PREFERRED_USERNAME, preferred_username );
model.put( QUERY_PARAM_FIRST_NAME, first_name );
model.put( QUERY_PARAM_EMAIL, email );
model.put( QUERY_PARAM_INSEE_BIRTHPLACE_LABEL, birthplace );
model.put( QUERY_PARAM_INSEE_BIRTHCOUNTRY_LABEL, birthcountry );
model.put( QUERY_PARAM_PHONE, phone );
model.put( QUERY_PARAM_BIRTHDATE, birthdate );
model.put( QUERY_PARAM_GENDER, gender );
model.put( QUERY_PARAM_DATASOURCE, datasource );
return getPage( PROPERTY_PAGE_TITLE_MANAGE_IDENTITIES, TEMPLATE_SEARCH_IDENTITIES, model );
}
/**
* view identity
*
* @param request
* http request
* @return The HTML form to view info
*/
@View( VIEW_IDENTITY )
public String getViewIdentity( HttpServletRequest request )
{
final String nId = request.getParameter( PARAMETER_ID_IDENTITY );
_identity = IdentityHome.findByCustomerId( nId );
final String filteredCustomerId = SecurityUtil.logForgingProtect( _identity.getCustomerId( ) );
AccessLogService.getInstance( ).info( AccessLoggerConstants.EVENT_TYPE_READ, DISPLAY_IDENTITY_EVENT_CODE, getUser( ), filteredCustomerId,
IdentityService.SPECIFIC_ORIGIN );
final Map<String, Object> model = getModel( );
model.put( MARK_IDENTITY, _identity );
model.put( MARK_IDENTITY_IS_SUSPICIOUS, SuspiciousIdentityHome.hasSuspicious( Collections.singletonList( _identity.getCustomerId( ) ) ) );
model.put( MARK_HAS_ATTRIBUTS_HISTO_ROLE,
IdentityManagementResourceIdService.isAuthorized( IdentityManagementResourceIdService.PERMISSION_ATTRIBUTS_HISTO, getUser( ) ) );
return getPage( PROPERTY_PAGE_TITLE_VIEW_IDENTITY, TEMPLATE_VIEW_IDENTITY, model );
}
/**
* Build the attribute history View
*
* @param request
* The HTTP request
* @return The page
*/
@View( value = VIEW_IDENTITY_HISTORY )
public String getIdentityHistoryView( HttpServletRequest request )
{
// here we use a LinkedHashMap to have same attributs order as in viewIdentity
final List<AttributeChange> attributeChangeList = new ArrayList<>( );
final List<IdentityChange> identityChangeList = new ArrayList<>( );
if ( _identity != null && MapUtils.isNotEmpty( _identity.getAttributes( ) ) )
{
try
{
attributeChangeList.addAll( IdentityAttributeHome.getAttributeChangeHistory( _identity.getId( ) ) );
identityChangeList.addAll( IdentityHome.findHistoryByCustomerId( _identity.getCustomerId( ) ) );
}
catch( IdentityStoreException e )
{
addError( e.getMessage( ) );
return getViewIdentity( request );
}
}
if ( _identity != null )
{
final String filteredCustomerId = SecurityUtil.logForgingProtect( _identity.getCustomerId( ) );
AccessLogService.getInstance( ).info( AccessLoggerConstants.EVENT_TYPE_READ, DISPLAY_IDENTITY_HISTORY_EVENT_CODE, getUser( ), filteredCustomerId,
IdentityService.SPECIFIC_ORIGIN );
}
final Map<String, Object> model = getModel( );
model.put( MARK_IDENTITY_CHANGE_LIST, identityChangeList );
model.put( MARK_ATTRIBUTES_CHANGE_LIST, attributeChangeList );
return getPage( PROPERTY_PAGE_TITLE_VIEW_CHANGE_HISTORY, TEMPLATE_VIEW_IDENTITY_HISTORY, model );
}
/**
* Process the data capture form of a new suspiciousidentity
*
* @param request
* The Http Request
* @return The Jsp URL of the process result
* @throws AccessDeniedException
*/
@Action( ACTION_EXPORT_IDENTITIES )
public void doExportIdentities( HttpServletRequest request )
{
try
{
final List<IdentityDto> identitiesToProcess = _identities.stream( ).filter( this::validateMinimumAttributes )
.peek( identityDto -> identityDto.setExternalCustomerId( UUID.randomUUID( ).toString( ) ) ).collect( Collectors.toList( ) );
final Batch<IdentityDto> batches = Batch.ofSize( identitiesToProcess, BATCH_PARTITION_SIZE );
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream( );
final ZipOutputStream zipOut = new ZipOutputStream( outputStream );
int i = 0;
for ( final List<IdentityDto> batch : batches )
{
final byte [ ] bytes = CsvIdentityService.instance( ).write( batch );
final ZipEntry zipEntry = new ZipEntry( "identities-" + ++i + ".csv" );
zipEntry.setSize( bytes.length );
zipOut.putNextEntry( zipEntry );
zipOut.write( bytes );
}
zipOut.closeEntry( );
zipOut.close( );
this.download( outputStream.toByteArray( ), "identities.zip", "application/zip" );
}
catch( Exception e )
{
addError( e.getMessage( ) );
redirectView( request, VIEW_MANAGE_IDENTITIES );
}
}
private boolean validateMinimumAttributes( final IdentityDto identity )
{
return this.checkAttributeExists( identity, Constants.PARAM_FAMILY_NAME ) && this.checkAttributeExists( identity, Constants.PARAM_FIRST_NAME )
&& this.checkAttributeExists( identity, Constants.PARAM_BIRTH_DATE );
}
private boolean checkAttributeExists( final IdentityDto identity, final String attributeKey )
{
return identity.getAttributes( ).stream( )
.anyMatch( attributeDto -> Objects.equals( attributeDto.getKey( ), attributeKey ) && StringUtils.isNotBlank( attributeDto.getValue( ) ) );
}
/**
* Process the data capture form of a new suspiciousidentity
*
* @param request
* The Http Request
* @return The Jsp URL of the process result
* @throws AccessDeniedException
*/
@Action( ACTION_BATCH_GENERATE_REQUESTS )
public void doGenerateBatchIdentities( HttpServletRequest request )
{
try
{
// Prepare identities for import (clear not used params)
_identities.forEach( identityDto -> {
identityDto.setExternalCustomerId( UUID.randomUUID( ).toString( ) );
identityDto.setCustomerId( null );
identityDto.setQuality( null );
identityDto.setExpiration( null );
identityDto.setMerge( null );
identityDto.setLastUpdateDate( null );
identityDto.setConnectionId( null );
identityDto.setMonParisActive( null );
identityDto.setCreationDate( null );
identityDto.setDuplicateDefinition( null );
identityDto.setSuspicious( null );
} );
final Batch<IdentityDto> batches = Batch.ofSize( _identities, BATCH_PARTITION_SIZE );
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream( );
final ZipOutputStream zipOut = new ZipOutputStream( outputStream );
int i = 0;
final String reference = UUID.randomUUID( ).toString( );
final Date today = new Date( LocalDate.now( ).toEpochDay( ) );
final ObjectMapper mapper = new ObjectMapper( );
mapper.enable( SerializationFeature.INDENT_OUTPUT );
mapper.disable( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES );
for ( final List<IdentityDto> batch : batches )
{
final BatchImportRequest batchImportRequest = new BatchImportRequest( );
batchImportRequest.setBatch( new BatchDto( ) );
batchImportRequest.getBatch( ).setReference( reference );
batchImportRequest.getBatch( ).setComment( "Batch exporté depuis identity store" );
batchImportRequest.getBatch( ).setDate( today );
batchImportRequest.getBatch( ).setUser( getUser( ).getEmail( ) );
batchImportRequest.getBatch( ).setAppCode( "TEST" );
batchImportRequest.getBatch( ).setIdentities( batch );
final ZipEntry zipEntry = new ZipEntry( "identities-" + ++i + ".json" );
final byte [ ] bytes = mapper.writeValueAsBytes( batchImportRequest );
zipEntry.setSize( bytes.length );
zipOut.putNextEntry( zipEntry );
zipOut.write( bytes );
}
zipOut.closeEntry( );
zipOut.close( );
this.download( outputStream.toByteArray( ), "identity_requests.zip", "application/zip" );
}
catch( Exception e )
{
addError( e.getMessage( ) );
redirectView( request, VIEW_MANAGE_IDENTITIES );
}
}
}