IdentityAccountGeneratorService.java
/*
* Copyright (c) 2002-2025, 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.accountgenerator.service;
import fr.paris.lutece.plugins.accountgenerator.business.IdentityAccount;
import fr.paris.lutece.plugins.accountgenerator.business.IdentityAccountHome;
import fr.paris.lutece.plugins.accountmanagement.web.service.AccountManagementService;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.account.AccountDto;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.account.ChangeAccountResponse;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.account.GetAccountResponse;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.account.RequestClient;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.account.generator.AccountGenerationDto;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.account.generator.GeneratedAccountDto;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.AttributeDto;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.AuthorType;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.ExpirationDefinition;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.IdentityDto;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.RequestAuthor;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.crud.IdentityChangeRequest;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.crud.IdentityChangeResponse;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.util.Constants;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.util.ResponseStatusFactory;
import fr.paris.lutece.plugins.identitystore.v3.web.service.IdentityService;
import fr.paris.lutece.plugins.identitystore.web.exception.IdentityAccountException;
import fr.paris.lutece.plugins.identitystore.web.exception.IdentityStoreException;
import fr.paris.lutece.portal.service.spring.SpringContextService;
import fr.paris.lutece.portal.service.util.AppLogService;
import fr.paris.lutece.portal.service.util.AppPropertiesService;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
public class IdentityAccountGeneratorService
{
// Singleton
private static IdentityAccountGeneratorService instance;
public static IdentityAccountGeneratorService instance( )
{
if ( instance == null )
{
instance = new IdentityAccountGeneratorService( );
}
return instance;
}
// API client services
private static final IdentityService _identityService = SpringContextService.getBean( "accountgenerator.identityService" );
private static final AccountManagementService _accountManagementService = SpringContextService.getBean( "accountgenerator.accountManagementService" );
private static final GeocodesCache _geocodesCache = SpringContextService.getBean( "accountgenerator.geocodesCache" );
// Configurable client generation information
private static final String accountManagementClientId = AppPropertiesService.getProperty( "accountgenerator.accountManagement.client-id" );
private static final String accountManagementSecretId = AppPropertiesService.getProperty( "accountgenerator.accountManagement.secret-id" );
private static final String identityStoreClientCode = AppPropertiesService.getProperty( "accountgenerator.accountManagement.client-code" );
private static final String identityStoreClientName = AppPropertiesService.getProperty( "accountgenerator.accountManagement.client-name" );
// Configurable generation parameters
private static final String commonPassword = AppPropertiesService.getProperty( "accountgenerator.generation.password", "password123456789" );
private static final String commonMailSuffix = AppPropertiesService.getProperty( "accountgenerator.generation.mail.suffix", "@paris.test.fr" );
private static final String mailCertifier = AppPropertiesService.getProperty( "accountgenerator.generation.certifier.email", "MAIL" );
private static final String loginCertifier = AppPropertiesService.getProperty( "accountgenerator.generation.certifier.login", "MAIL" );
private static final String firstNameCertifier = AppPropertiesService.getProperty( "accountgenerator.generation.certifier.first_name", "FC" );
private static final String familyNameCertifier = AppPropertiesService.getProperty( "accountgenerator.generation.certifier.family-name", "FC" );
private static final String birthplaceCodeCertifier = AppPropertiesService.getProperty( "accountgenerator.generation.certifier.birthplace_code", "FC" );
private static final String birthCountryCodeCertifier = AppPropertiesService.getProperty( "accountgenerator.generation.certifier.birthcountry_code", "FC" );
private static final String birthCountryCodeValue = AppPropertiesService.getProperty( "accountgenerator.generation.value.birthcountry_code", "FC" );
private static final String genderCertifier = AppPropertiesService.getProperty( "accountgenerator.generation.certifier.gender", "FC" );
private static final String birthdateCertifier = AppPropertiesService.getProperty( "accountgenerator.generation.certifier.birthdate", "FC" );
private static final int birthdateMaxGenerationDay = AppPropertiesService.getPropertyInt( "accountgenerator.generation.value.max.birthdate.day", 15 );
private static final int birthdateMaxGenerationMonth = AppPropertiesService.getPropertyInt( "accountgenerator.generation.value.max.birthdate.month", 12 );
private static final int birthdateMaxGenerationYear = AppPropertiesService.getPropertyInt( "accountgenerator.generation.value.max.birthdate.year", 2000 );
// Generation helpers
private static final List<String> genders = List.of( "0", "1", "2" );
private static final List<String> alphabet = List.of( "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",
"U", "V", "W", "X", "Y", "Z" );
private static final Random random = new Random( );
private final LocalDate maxGenerationDate;
private static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern( "dd/MM/yyyy" );
// Class parameters
private final RequestClient client;
private final RequestAuthor author;
// Constructor is private so that only singleton pattern can use it
private IdentityAccountGeneratorService( )
{
client = new RequestClient( );
client.setClientId( accountManagementClientId );
client.setClientSecret( accountManagementSecretId );
author = new RequestAuthor( );
author.setName( identityStoreClientName );
author.setType( AuthorType.application );
maxGenerationDate = LocalDate.of( birthdateMaxGenerationYear, birthdateMaxGenerationMonth, birthdateMaxGenerationDay );
}
/**
* Create a batch of identities and accounts (optional)
*
* @param accountGenerationDto
* the parametrization DTO of the generation
* @return a list of generated identities and/or accounts
*/
public List<GeneratedAccountDto> createIdentityAccountBatch( final AccountGenerationDto accountGenerationDto )
{
final Date generationDate = new Date( );
final Timestamp now = Timestamp.from( Instant.now( ) );
final List<GeneratedAccountDto> generatedAccount = new ArrayList<>( );
if ( accountGenerationDto != null )
{
for ( int i = 1; i <= accountGenerationDto.getBatchSize( ); i++ )
{
final GeneratedAccountDto account = new GeneratedAccountDto( );
generatedAccount.add( account );
final String mail = "user.".concat( accountGenerationDto.getGenerationPattern( ) ).concat( "." )
.concat( String.valueOf( accountGenerationDto.getGenerationIncrementOffset( ) + i ) ).concat( commonMailSuffix );
account.setPassword( commonPassword );
account.setEmail( mail );
// Hold errors in order to clean data if both identity and account cannot be generated
boolean accountError = false;
boolean identityError = false;
// If requested, create a Mon Paris account
if ( accountGenerationDto.isGenerateAccount( ) )
{
// Create the account
try
{
final ChangeAccountResponse accountCreationResponse = _accountManagementService.createAccount( mail, commonPassword, client );
if ( accountCreationResponse.getResult( ) != null && Objects.equals( accountCreationResponse.getStatus( ), "OK" ) )
{
account.setGuid( accountCreationResponse.getResult( ).getUid( ) );
// Validate the account
final GetAccountResponse getAccountResponse = _accountManagementService.getAccount( account.getGuid( ), client );
final AccountDto accountDto = getAccountResponse.getResult( );
accountDto.setValidated( "true" );
_accountManagementService.modifyAccount( accountDto, client );
}
else
{
account.getStatus( ).add( "The API refused to create the account:\n" + accountCreationResponse );
accountError = true;
}
}
catch( final IdentityAccountException e )
{
account.getStatus( ).add( "An exception occurred when trying to create an account: " + e.getMessage( ) );
accountError = true;
}
}
else
{
account.getStatus( ).add( "No account was requested in this creation request" );
}
// Create an identity
if ( !accountError )
{
final IdentityChangeRequest identityChange = this.buildIdentityChangeRequest( accountGenerationDto, account.getGuid( ), mail, i, now );
try
{
final IdentityChangeResponse createIdentityResponse = _identityService.createIdentity( identityChange, identityStoreClientCode,
author );
AppLogService.info( "Identity creation request:\n" + identityChange );
if ( createIdentityResponse != null
&& createIdentityResponse.getStatus( ).getHttpCode( ) == ResponseStatusFactory.success( ).getHttpCode( ) )
{
account.setCuid( createIdentityResponse.getCustomerId( ) );
}
else
{
account.getStatus( ).add( "The API refused to create the identity:\n" + createIdentityResponse );
identityError = true;
}
}
catch( final IdentityStoreException e )
{
account.getStatus( ).add( "An exception occurred when trying to create an identity: " + e.getMessage( ) );
identityError = true;
}
// Delete account in case of identity creation fail
if ( identityError && accountGenerationDto.isGenerateAccount( ) )
{
try
{
final ChangeAccountResponse deleteAccount = _accountManagementService.deleteAccount( account.getGuid( ), client );
account.getStatus( ).add( "Tried to delete the account (due to identity creation error): " + deleteAccount );
}
catch( final IdentityAccountException e )
{
account.getStatus( )
.add( "An exception occurred when trying to delete the account (due to identity creation error): " + e.getMessage( ) );
}
}
}
else
{
account.getStatus( ).add( "No identity creation due to account creation error" );
}
}
}
// Store generated account for further treatments
final List<IdentityAccount> accounts = generatedAccount.stream( ).filter( GeneratedAccountDto::isStorable ).map( a -> {
final IdentityAccount account = new IdentityAccount( );
account.setGuid( a.getGuid( ) );
account.setCuid( a.getCuid( ) );
account.setCreationDate( generationDate );
account.setExpirationDate( this.addDays( generationDate, accountGenerationDto.getNbDaysOfValidity( ) ) );
return account;
} ).collect( Collectors.toList( ) );
IdentityAccountHome.saveAccounts( accounts );
return generatedAccount;
}
public IdentityChangeRequest buildIdentityChangeRequest( final AccountGenerationDto accountGenerationDto, final String guid, final String mail,
int iteration, final Timestamp generationDate )
{
final IdentityChangeRequest identityChangeRequest = new IdentityChangeRequest( );
final IdentityDto identityDto = new IdentityDto( );
identityDto.setMonParisActive( accountGenerationDto.isGenerateAccount( ) );
identityDto.setConnectionId( guid );
final Timestamp now = new Timestamp( System.currentTimeMillis( ) );
identityDto.setLastUpdateDate( now );
identityDto.setCreationDate( now );
final ExpirationDefinition expirationDefinition = new ExpirationDefinition( );
expirationDefinition.setExpirationDate( Timestamp.valueOf( now.toLocalDateTime( ).plusDays( accountGenerationDto.getNbDaysOfValidity( ) ) ) );
identityDto.setExpiration( expirationDefinition );
final LocalDate birthdate = this.getRandomDate( );
identityDto.getAttributes( ).add( this.buildAttribute( Constants.PARAM_GENDER, this.getRandomGender( ), genderCertifier, generationDate ) );
identityDto.getAttributes( ).add( this.buildAttribute( Constants.PARAM_FIRST_NAME,
this.getRandomAttribute( Constants.PARAM_FIRST_NAME, accountGenerationDto, iteration, " " ), firstNameCertifier, generationDate ) );
identityDto.getAttributes( ).add( this.buildAttribute( Constants.PARAM_FAMILY_NAME,
this.getRandomAttribute( Constants.PARAM_FAMILY_NAME, accountGenerationDto, iteration, " " ), familyNameCertifier, generationDate ) );
identityDto.getAttributes( )
.add( this.buildAttribute( Constants.PARAM_BIRTH_DATE, this.getLocaleDateAsString( birthdate ), birthdateCertifier, generationDate ) );
identityDto.getAttributes( )
.add( this.buildAttribute( Constants.PARAM_BIRTH_COUNTRY_CODE, birthCountryCodeValue, birthCountryCodeCertifier, generationDate ) );
identityDto.getAttributes( ).add(
this.buildAttribute( Constants.PARAM_BIRTH_PLACE_CODE, this.getRandomBirthplaceCode( birthdate ), birthplaceCodeCertifier, generationDate ) );
identityDto.getAttributes( ).add( this.buildAttribute( Constants.PARAM_EMAIL, mail, mailCertifier, generationDate ) );
identityDto.getAttributes( ).add( this.buildAttribute( Constants.PARAM_LOGIN,
this.getRandomAttribute( Constants.PARAM_LOGIN, accountGenerationDto, iteration, "-" ), loginCertifier, generationDate ) );
identityChangeRequest.setIdentity( identityDto );
return identityChangeRequest;
}
private String getRandomBirthplaceCode( final LocalDate birthdate )
{
final List<String> codesFromCache = _geocodesCache.getCodesFromCache( java.sql.Date.valueOf( birthdate ) );
return codesFromCache.get( random.nextInt( codesFromCache.size( ) ) );
}
private String getRandomAttribute( final String attributeName, final AccountGenerationDto accountGenerationDto, final int iteration,
final String separator )
{
return attributeName.replace( "_", "" ).concat( separator ).concat( accountGenerationDto.getGenerationPattern( ) ).concat( separator )
.concat( this.toLetters( accountGenerationDto.getGenerationIncrementOffset( ) + iteration ) );
}
private String toLetters( int iteration )
{
final String strValue = String.valueOf( iteration );
String strReturn = "";
for ( int i = 0; i < strValue.length( ); i++ )
{
strReturn = strReturn.concat( alphabet.get( i ) );
}
return strReturn;
}
private String getRandomGender( )
{
return genders.get( random.nextInt( genders.size( ) ) );
}
public LocalDate getRandomDate( )
{
final long randomDay = ThreadLocalRandom.current( ).nextLong( maxGenerationDate.toEpochDay( ) );
return LocalDate.ofEpochDay( randomDay );
}
public String getLocaleDateAsString( final LocalDate date )
{
return date.format( dateTimeFormatter );
}
public AttributeDto buildAttribute( final String key, final String value, final String certifier, final Timestamp certificationDate )
{
final AttributeDto attribute = new AttributeDto( );
attribute.setValue( value );
attribute.setKey( key );
attribute.setCertifier( certifier );
attribute.setCertificationDate( certificationDate );
return attribute;
}
private Date add( final Date date, final int calendarField, final int amount )
{
final Calendar c = Calendar.getInstance( );
c.setTime( date );
c.add( calendarField, amount );
return c.getTime( );
}
public Date addDays( final Date date, final int amount )
{
return this.add( date, Calendar.DAY_OF_MONTH, amount );
}
}