1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package fr.paris.lutece.plugins.identitystore.service.identity;
35
36 import fr.paris.lutece.plugins.identitystore.business.application.ClientApplication;
37 import fr.paris.lutece.plugins.identitystore.business.attribute.AttributeKey;
38 import fr.paris.lutece.plugins.identitystore.business.attribute.AttributeKeyHome;
39 import fr.paris.lutece.plugins.identitystore.business.contract.ServiceContract;
40 import fr.paris.lutece.plugins.identitystore.business.duplicates.suspicions.ExcludedIdentities;
41 import fr.paris.lutece.plugins.identitystore.business.duplicates.suspicions.SuspiciousIdentity;
42 import fr.paris.lutece.plugins.identitystore.business.duplicates.suspicions.SuspiciousIdentityHome;
43 import fr.paris.lutece.plugins.identitystore.business.identity.Identity;
44 import fr.paris.lutece.plugins.identitystore.business.identity.IdentityAttribute;
45 import fr.paris.lutece.plugins.identitystore.business.identity.IdentityAttributeHome;
46 import fr.paris.lutece.plugins.identitystore.business.identity.IdentityHome;
47 import fr.paris.lutece.plugins.identitystore.business.referentiel.RefAttributeCertificationLevel;
48 import fr.paris.lutece.plugins.identitystore.business.referentiel.RefAttributeCertificationLevelHome;
49 import fr.paris.lutece.plugins.identitystore.business.rules.duplicate.DuplicateRule;
50 import fr.paris.lutece.plugins.identitystore.business.rules.duplicate.DuplicateRuleHome;
51 import fr.paris.lutece.plugins.identitystore.business.rules.search.IdentitySearchRule;
52 import fr.paris.lutece.plugins.identitystore.business.rules.search.IdentitySearchRuleHome;
53 import fr.paris.lutece.plugins.identitystore.business.rules.search.SearchRuleType;
54 import fr.paris.lutece.plugins.identitystore.cache.IdentityDtoCache;
55 import fr.paris.lutece.plugins.identitystore.service.attribute.IdentityAttributeService;
56 import fr.paris.lutece.plugins.identitystore.service.contract.ServiceContractNotFoundException;
57 import fr.paris.lutece.plugins.identitystore.service.contract.ServiceContractService;
58 import fr.paris.lutece.plugins.identitystore.service.duplicate.IDuplicateService;
59 import fr.paris.lutece.plugins.identitystore.service.geocodes.GeocodesService;
60 import fr.paris.lutece.plugins.identitystore.service.listeners.IdentityStoreNotifyListenerService;
61 import fr.paris.lutece.plugins.identitystore.service.search.ISearchIdentityService;
62 import fr.paris.lutece.plugins.identitystore.service.user.InternalUserService;
63 import fr.paris.lutece.plugins.identitystore.utils.Batch;
64 import fr.paris.lutece.plugins.identitystore.v3.web.rs.DtoConverter;
65 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.AttributeChangeStatus;
66 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.AttributeChangeStatusType;
67 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.AttributeDto;
68 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.AttributeStatus;
69 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.AuthorType;
70 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.ChangeResponse;
71 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.IdentityDto;
72 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.QualityDefinition;
73 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.RequestAuthor;
74 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.ResponseStatus;
75 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.crud.IdentityChangeRequest;
76 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.crud.IdentityChangeResponse;
77 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.history.AttributeChangeType;
78 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.history.IdentityChangeType;
79 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.merge.IdentityMergeRequest;
80 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.merge.IdentityMergeResponse;
81 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.search.DuplicateSearchResponse;
82 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.search.IdentitySearchMessage;
83 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.search.IdentitySearchRequest;
84 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.search.IdentitySearchResponse;
85 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.search.QualifiedIdentitySearchResult;
86 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.search.SearchAttribute;
87 import fr.paris.lutece.plugins.identitystore.v3.web.rs.util.Constants;
88 import fr.paris.lutece.plugins.identitystore.v3.web.rs.util.ResponseStatusFactory;
89 import fr.paris.lutece.plugins.identitystore.web.exception.IdentityStoreException;
90 import fr.paris.lutece.portal.service.security.AccessLogService;
91 import fr.paris.lutece.portal.service.security.AccessLoggerConstants;
92 import fr.paris.lutece.portal.service.spring.SpringContextService;
93 import fr.paris.lutece.portal.service.util.AppPropertiesService;
94 import fr.paris.lutece.util.http.SecurityUtil;
95 import fr.paris.lutece.util.sql.TransactionManager;
96 import org.apache.commons.collections4.CollectionUtils;
97 import org.apache.commons.lang3.StringUtils;
98
99 import java.util.ArrayList;
100 import java.util.Arrays;
101 import java.util.Collection;
102 import java.util.Collections;
103 import java.util.Comparator;
104 import java.util.HashMap;
105 import java.util.HashSet;
106 import java.util.Iterator;
107 import java.util.List;
108 import java.util.Map;
109 import java.util.Objects;
110 import java.util.Set;
111 import java.util.stream.Collectors;
112
113 public class IdentityService
114 {
115
116 private static final String PIVOT_CERTIF_LEVEL_THRESHOLD = "identitystore.identity.attribute.update.pivot.certif.level.threshold";
117
118
119 public static final String CREATE_IDENTITY_EVENT_CODE = "CREATE_IDENTITY";
120 public static final String UPDATE_IDENTITY_EVENT_CODE = "UPDATE_IDENTITY";
121 public static final String DECERTIFY_IDENTITY_EVENT_CODE = "DECERTIFY_IDENTITY";
122 public static final String GET_IDENTITY_EVENT_CODE = "GET_IDENTITY";
123 public static final String SEARCH_IDENTITY_EVENT_CODE = "SEARCH_IDENTITY";
124 public static final String DELETE_IDENTITY_EVENT_CODE = "DELETE_IDENTITY";
125 public static final String CONSOLIDATE_IDENTITY_EVENT_CODE = "CONSOLIDATE_IDENTITY";
126 public static final String MERGE_IDENTITY_EVENT_CODE = "MERGE_IDENTITY";
127 public static final String CANCEL_MERGE_IDENTITY_EVENT_CODE = "CANCEL_MERGE_IDENTITY";
128 public static final String CANCEL_CONSOLIDATE_IDENTITY_EVENT_CODE = "CANCEL_CONSOLIDATE_IDENTITY";
129 public static final String SPECIFIC_ORIGIN = "BO";
130
131
132 private static final String PROPERTY_DUPLICATES_IMPORT_RULES_SUSPICION = "identitystore.identity.duplicates.import.rules.suspicion";
133 private static final String PROPERTY_DUPLICATES_IMPORT_RULES_STRICT = "identitystore.identity.duplicates.import.rules.strict";
134 private static final String PROPERTY_DUPLICATES_CREATION_RULES = "identitystore.identity.duplicates.creation.rules";
135 private static final String PROPERTY_DUPLICATES_UPDATE_RULES = "identitystore.identity.duplicates.update.rules";
136 private static final String PROPERTY_DUPLICATES_CHECK_DATABASE_ACTIVATED = "identitystore.identity.duplicates.check.database";
137
138
139 private final IdentityStoreNotifyListenerService _identityStoreNotifyListenerService = IdentityStoreNotifyListenerService.instance( );
140 private final ServiceContractService _serviceContractService = ServiceContractService.instance( );
141 private final IdentityAttributeService _identityAttributeService = IdentityAttributeService.instance( );
142 private final InternalUserService _internalUserService = InternalUserService.getInstance( );
143 private final IDuplicateService _duplicateServiceDatabase = SpringContextService.getBean( "identitystore.duplicateService.database" );
144 private final IDuplicateService _duplicateServiceElasticSearch = SpringContextService.getBean( "identitystore.duplicateService.elasticsearch" );
145 private final ISearchIdentityService _elasticSearchIdentityService = SpringContextService.getBean( "identitystore.searchIdentityService.elasticsearch" );
146
147
148 private final IdentityDtoCache _identityDtoCache = SpringContextService.getBean( "identitystore.identityDtoCache" );
149
150 private static IdentityService _instance;
151
152 public static IdentityService instance( )
153 {
154 if ( _instance == null )
155 {
156 _instance = new IdentityService( );
157 }
158 return _instance;
159 }
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176 public Identity create( final IdentityChangeRequest request, final RequestAuthor author, final String clientCode, final IdentityChangeResponse response )
177 throws IdentityStoreException
178 {
179 if ( !_serviceContractService.canCreateIdentity( clientCode ) )
180 {
181 response.setStatus( ResponseStatusFactory.failure( ).setMessage( "The client application is not authorized to create an identity." )
182 .setMessageKey( Constants.PROPERTY_REST_ERROR_CREATE_UNAUTHORIZED ) );
183 return null;
184 }
185
186 if ( StringUtils.isNotEmpty( request.getIdentity( ).getCustomerId( ) ) )
187 {
188 throw new IdentityStoreException( "You cannot specify a CUID when requesting for a creation" );
189 }
190
191 if ( StringUtils.isNotEmpty( request.getIdentity( ).getConnectionId( ) ) && !_serviceContractService.canModifyConnectedIdentity( clientCode ) )
192 {
193 throw new IdentityStoreException( "You cannot specify a GUID when requesting for a creation" );
194 }
195
196
197 final List<String> mandatoryAttributes = _serviceContractService.getMandatoryAttributes( clientCode,
198 AttributeKeyHome.getMandatoryForCreationAttributeKeyList( ) );
199 if ( CollectionUtils.isNotEmpty( mandatoryAttributes ) )
200 {
201 final Set<String> providedKeySet = request.getIdentity( ).getAttributes( ).stream( ).filter( a -> StringUtils.isNotBlank( a.getValue( ) ) )
202 .map( AttributeDto::getKey ).collect( Collectors.toSet( ) );
203 if ( !providedKeySet.containsAll( mandatoryAttributes ) )
204 {
205 response.setStatus( ResponseStatusFactory.failure( ).setMessage( "All mandatory attributes must be provided : " + mandatoryAttributes )
206 .setMessageKey( Constants.PROPERTY_REST_ERROR_MISSING_MANDATORY_ATTRIBUTES ) );
207 return null;
208 }
209 }
210
211
212 if ( request.getIdentity( ).getMonParisActive( ) != null && !_serviceContractService.canModifyConnectedIdentity( clientCode ) )
213 {
214 throw new IdentityStoreException( "You cannot set the 'mon_paris_active' flag when requesting for a creation" );
215 }
216
217
218 if ( StringUtils.isNotEmpty( request.getIdentity( ).getConnectionId( ) )
219 && IdentityHome.findByConnectionId( request.getIdentity( ).getConnectionId( ) ) != null )
220 {
221 throw new IdentityStoreException( "GUID is already in use." );
222 }
223
224 final Map<String, String> attributes = request.getIdentity( ).getAttributes( ).stream( ).filter( a -> StringUtils.isNotBlank( a.getValue( ) ) )
225 .collect( Collectors.toMap( AttributeDto::getKey, AttributeDto::getValue, ( a, b ) -> a ) );
226 final DuplicateSearchResponse duplicateSearchResponse = this.checkDuplicates( attributes, PROPERTY_DUPLICATES_CREATION_RULES, "" );
227 if ( duplicateSearchResponse != null && CollectionUtils.isNotEmpty( duplicateSearchResponse.getIdentities( ) ) )
228 {
229 response.setStatus( ResponseStatusFactory.conflict( ).setMessage( duplicateSearchResponse.getStatus( ).getMessage( ) )
230 .setMessageKey( duplicateSearchResponse.getStatus( ).getMessageKey( ) ) );
231 return null;
232 }
233
234 final Identity/identitystore/business/identity/Identity.html#Identity">Identity identity = new Identity( );
235 TransactionManager.beginTransaction( null );
236 try
237 {
238 identity.setMonParisActive( request.getIdentity( ).isMonParisActive( ) );
239 if ( StringUtils.isNotEmpty( request.getIdentity( ).getConnectionId( ) ) )
240 {
241 identity.setConnectionId( request.getIdentity( ).getConnectionId( ) );
242 }
243 IdentityHome.create( identity, _serviceContractService.getDataRetentionPeriodInMonths( clientCode ) );
244
245 final List<AttributeDto> attributesToCreate = request.getIdentity( ).getAttributes( );
246 final List<AttributeStatus> attrStatusList = GeocodesService.processCountryAndCityForCreate( identity, attributesToCreate, clientCode );
247 for ( final AttributeDto attributeDto : attributesToCreate )
248 {
249
250 final AttributeStatus attributeStatus = _identityAttributeService.createAttribute( attributeDto, identity, clientCode );
251 attrStatusList.add( attributeStatus );
252 }
253
254 response.setCustomerId( identity.getCustomerId( ) );
255 response.setCreationDate( identity.getCreationDate( ) );
256 final boolean incompleteCreation = attrStatusList.stream( ).anyMatch( s -> s.getStatus( ).equals( AttributeChangeStatus.NOT_CREATED ) );
257 final ResponseStatus status = incompleteCreation ? ResponseStatusFactory.incompleteSuccess( ) : ResponseStatusFactory.success( );
258 response.setStatus( status.setAttributeStatuses( attrStatusList ).setMessageKey( Constants.PROPERTY_REST_INFO_SUCCESSFUL_OPERATION ) );
259 TransactionManager.commitTransaction( null );
260
261
262 final List<AttributeStatus> createdAttributes = attrStatusList.stream( ).filter( s -> s.getStatus( ).equals( AttributeChangeStatus.CREATED ) )
263 .collect( Collectors.toList( ) );
264 for ( AttributeStatus attributeStatus : createdAttributes )
265 {
266 _identityStoreNotifyListenerService.notifyListenersAttributeChange( AttributeChangeType.CREATE, identity, attributeStatus, author, clientCode );
267 }
268
269
270 _identityStoreNotifyListenerService.notifyListenersIdentityChange( IdentityChangeType.CREATE, identity, response.getStatus( ).getType( ).name( ),
271 response.getStatus( ).getMessage( ), author, clientCode, new HashMap<>( ) );
272 AccessLogService.getInstance( ).info( AccessLoggerConstants.EVENT_TYPE_CREATE, CREATE_IDENTITY_EVENT_CODE,
273 _internalUserService.getApiUser( author, clientCode ), SecurityUtil.logForgingProtect( identity.getCustomerId( ) ), SPECIFIC_ORIGIN );
274 }
275 catch( Exception e )
276 {
277 response.setStatus(
278 ResponseStatusFactory.failure( ).setMessage( e.getMessage( ) ).setMessageKey( Constants.PROPERTY_REST_ERROR_DURING_TREATMENT ) );
279 TransactionManager.rollBack( null );
280 }
281
282 return identity;
283 }
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316 public Identity update( final String customerId, final IdentityChangeRequest request, final RequestAuthor author, final String clientCode,
317 final IdentityChangeResponse response ) throws IdentityStoreException
318 {
319 if ( !_serviceContractService.canUpdateIdentity( clientCode ) )
320 {
321 response.setStatus( ResponseStatusFactory.failure( ).setMessage( "The client application is not authorized to update an identity." )
322 .setMessageKey( Constants.PROPERTY_REST_ERROR_UPDATE_UNAUTHORIZED ) );
323 response.setCustomerId( customerId );
324 return null;
325 }
326
327 final Identity identity = IdentityHome.findByCustomerId( customerId );
328
329
330 if ( identity == null )
331 {
332 response.setStatus( ResponseStatusFactory.notFound( ).setMessage( "No matching identity could be found" )
333 .setMessageKey( Constants.PROPERTY_REST_ERROR_NO_MATCHING_IDENTITY ) );
334 return null;
335 }
336
337
338 if ( !Objects.equals( identity.getLastUpdateDate( ), request.getIdentity( ).getLastUpdateDate( ) ) )
339 {
340 response.setStatus(
341 ResponseStatusFactory.conflict( ).setMessage( "This identity has been updated recently, please load the latest data before updating." )
342 .setMessageKey( Constants.PROPERTY_REST_ERROR_UPDATE_CONFLICT ) );
343 response.setCustomerId( identity.getCustomerId( ) );
344 return identity;
345 }
346
347
348 if ( identity.isMerged( ) )
349 {
350 final Identity masterIdentity = IdentityHome.findMasterIdentityByCustomerId( request.getIdentity( ).getCustomerId( ) );
351 response.setStatus(
352 ResponseStatusFactory.conflict( ).setMessage( "Cannot update a merged Identity. Master identity customerId is provided in the response." )
353 .setMessageKey( Constants.PROPERTY_REST_ERROR_FORBIDDEN_UPDATE_ON_MERGED_IDENTITY ) );
354 response.setCustomerId( masterIdentity.getCustomerId( ) );
355 return identity;
356 }
357
358
359 if ( identity.isDeleted( ) )
360 {
361 response.setStatus( ResponseStatusFactory.conflict( ).setMessage( "Cannot update a deleted Identity." )
362 .setMessageKey( Constants.PROPERTY_REST_ERROR_FORBIDDEN_UPDATE_ON_DELETED_IDENTITY ) );
363 response.setCustomerId( identity.getCustomerId( ) );
364 return identity;
365 }
366
367
368 if ( request.getIdentity( ).getMonParisActive( ) != null && !_serviceContractService.canModifyConnectedIdentity( clientCode ) )
369 {
370 response.setStatus(
371 ResponseStatusFactory.conflict( ).setMessage( "The client application is not authorized to update the 'mon_paris_active' flag." )
372 .setMessageKey( Constants.PROPERTY_REST_ERROR_FORBIDDEN_MON_PARIS_ACTIVE_UPDATE ) );
373 response.setCustomerId( identity.getCustomerId( ) );
374 return null;
375 }
376
377
378 if ( doesRequestContainsAttributeValueChangesImpactingRules( request, identity, PROPERTY_DUPLICATES_UPDATE_RULES ) )
379 {
380
381 final Map<String, String> attributes = request.getIdentity( ).getAttributes( ).stream( ).filter( a -> StringUtils.isNotBlank( a.getValue( ) ) )
382 .collect( Collectors.toMap( AttributeDto::getKey, AttributeDto::getValue, ( a, b ) -> a ) );
383
384 identity.getAttributes( ).forEach( ( key, attr ) -> attributes.putIfAbsent( key, attr.getValue( ) ) );
385
386 request.getIdentity( ).getAttributes( ).stream( ).filter( a -> StringUtils.isBlank( a.getValue( ) ) )
387 .forEach( a -> attributes.remove( a.getKey( ) ) );
388
389
390 final DuplicateSearchResponse duplicateSearchResponse = this.checkDuplicates( attributes, PROPERTY_DUPLICATES_UPDATE_RULES, customerId );
391 if ( duplicateSearchResponse != null && !duplicateSearchResponse.getIdentities( ).isEmpty( ) )
392 {
393 response.setStatus( ResponseStatusFactory.conflict( ).setMessage( duplicateSearchResponse.getStatus( ).getMessage( ) )
394 .setMessageKey( duplicateSearchResponse.getStatus( ).getMessageKey( ) ) );
395 return null;
396 }
397 }
398
399
400 TransactionManager.beginTransaction( null );
401 try
402 {
403 if ( _serviceContractService.canModifyConnectedIdentity( clientCode )
404 && !StringUtils.equalsIgnoreCase( identity.getConnectionId( ), request.getIdentity( ).getConnectionId( ) )
405 && request.getIdentity( ).getConnectionId( ) != null )
406 {
407 final Identity byConnectionId = IdentityHome.findByConnectionId( request.getIdentity( ).getConnectionId( ) );
408 if ( byConnectionId != null )
409 {
410 response.setStatus( ResponseStatusFactory.conflict( )
411 .setMessage(
412 "An identity already exists with the given connection ID. The customer ID of that identity is provided in the response." )
413 .setMessageKey( Constants.PROPERTY_REST_ERROR_CONFLICT_CONNECTION_ID ) );
414 response.setCustomerId( byConnectionId.getCustomerId( ) );
415 TransactionManager.rollBack( null );
416 return null;
417 }
418 else
419 {
420 identity.setConnectionId( request.getIdentity( ).getConnectionId( ) );
421 IdentityHome.update( identity );
422 }
423 }
424
425
426
427 final List<AttributeStatus> attrStatusList = this.updateIdentity( request.getIdentity( ), clientCode, response, identity );
428 if ( ResponseStatusFactory.unauthorized( ).equals( response.getStatus( ) ) )
429 {
430 response.setCustomerId( identity.getCustomerId( ) );
431 TransactionManager.rollBack( null );
432 return null;
433 }
434
435 response.setCustomerId( identity.getCustomerId( ) );
436 response.setConnectionId( identity.getConnectionId( ) );
437 response.setCreationDate( identity.getCreationDate( ) );
438 response.setLastUpdateDate( identity.getLastUpdateDate( ) );
439
440 final boolean allAttributesCreatedOrUpdated = attrStatusList.stream( ).map( AttributeStatus::getStatus )
441 .allMatch( status -> status.getType( ) == AttributeChangeStatusType.SUCCESS );
442 final ResponseStatus status = allAttributesCreatedOrUpdated ? ResponseStatusFactory.success( ) : ResponseStatusFactory.incompleteSuccess( );
443
444 final String msgKey;
445 if ( Collections.disjoint( AttributeChangeStatus.getSuccessStatuses( ),
446 attrStatusList.stream( ).map( AttributeStatus::getStatus ).collect( Collectors.toList( ) ) ) )
447 {
448
449 msgKey = Constants.PROPERTY_REST_INFO_NO_ATTRIBUTE_CHANGE;
450 }
451 else
452 {
453 msgKey = Constants.PROPERTY_REST_INFO_SUCCESSFUL_OPERATION;
454 }
455 response.setStatus( status.setAttributeStatuses( attrStatusList ).setMessageKey( msgKey ) );
456 TransactionManager.commitTransaction( null );
457
458
459 for ( final AttributeStatus attributeStatus : attrStatusList )
460 {
461 _identityStoreNotifyListenerService.notifyListenersAttributeChange( AttributeChangeType.UPDATE, identity, attributeStatus, author, clientCode );
462 }
463
464
465 _identityStoreNotifyListenerService.notifyListenersIdentityChange( IdentityChangeType.UPDATE, identity, response.getStatus( ).getType( ).name( ),
466 response.getStatus( ).getMessage( ), author, clientCode, new HashMap<>( ) );
467 AccessLogService.getInstance( ).info( AccessLoggerConstants.EVENT_TYPE_MODIFY, UPDATE_IDENTITY_EVENT_CODE,
468 _internalUserService.getApiUser( author, clientCode ), SecurityUtil.logForgingProtect( identity.getCustomerId( ) ), SPECIFIC_ORIGIN );
469 }
470 catch( Exception e )
471 {
472 response.setStatus(
473 ResponseStatusFactory.failure( ).setMessage( e.getMessage( ) ).setMessageKey( Constants.PROPERTY_REST_ERROR_DURING_TREATMENT ) );
474 TransactionManager.rollBack( null );
475 }
476
477 return identity;
478 }
479
480
481
482
483
484
485
486
487
488
489
490 private boolean doesRequestContainsAttributeValueChangesImpactingRules( final IdentityChangeRequest request, final Identity identity,
491 final String duplicateRulesProperty )
492 {
493 final Set<String> checkedAttributeKeys = Arrays.stream( AppPropertiesService.getProperty( duplicateRulesProperty ).split( "," ) )
494 .map( DuplicateRuleHome::findByCode ).flatMap( rule -> rule.getCheckedAttributes( ).stream( ) ).map( AttributeKey::getKeyName )
495 .collect( Collectors.toSet( ) );
496 return request.getIdentity( ).getAttributes( ).stream( ).filter( a -> checkedAttributeKeys.contains( a.getKey( ) ) ).anyMatch( a -> {
497 if ( StringUtils.isNotBlank( a.getValue( ) ) )
498 {
499 return !identity.getAttributes( ).containsKey( a.getKey( ) )
500 || !Objects.equals( identity.getAttributes( ).get( a.getKey( ) ).getValue( ), a.getValue( ) );
501 }
502 else
503 {
504 return identity.getAttributes( ).containsKey( a.getKey( ) );
505 }
506 } );
507 }
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536 public Identity merge( final IdentityMergeRequest request, final RequestAuthor author, final String clientCode, final IdentityMergeResponse response )
537 {
538 final Identity primaryIdentity = IdentityHome.findByCustomerId( request.getPrimaryCuid( ) );
539 if ( primaryIdentity == null )
540 {
541 response.setStatus( ResponseStatusFactory.failure( ).setMessage( "Could not find primary identity with customer_id " + request.getPrimaryCuid( ) )
542 .setMessageKey( Constants.PROPERTY_REST_ERROR_PRIMARY_IDENTITY_NOT_FOUND ) );
543 return null;
544 }
545
546 if ( primaryIdentity.isDeleted( ) )
547 {
548 response.setStatus(
549 ResponseStatusFactory.failure( ).setMessage( "Primary identity found with customer_id " + request.getPrimaryCuid( ) + " is deleted" )
550 .setMessageKey( Constants.PROPERTY_REST_ERROR_PRIMARY_IDENTITY_DELETED ) );
551 return null;
552 }
553
554 if ( primaryIdentity.isMerged( ) )
555 {
556 response.setStatus(
557 ResponseStatusFactory.failure( ).setMessage( "Primary identity found with customer_id " + request.getPrimaryCuid( ) + " is merged" )
558 .setMessageKey( Constants.PROPERTY_REST_ERROR_PRIMARY_IDENTITY_MERGED ) );
559 return null;
560 }
561
562 if ( !Objects.equals( primaryIdentity.getLastUpdateDate( ), request.getPrimaryLastUpdateDate( ) ) )
563 {
564 response.setStatus(
565 ResponseStatusFactory.failure( ).setMessage( "The primary identity has been updated recently, please load the latest data before merging." )
566 .setMessageKey( Constants.PROPERTY_REST_ERROR_PRIMARY_IDENTITY_UPDATE_CONFLICT ) );
567 return null;
568 }
569
570 final Identity secondaryIdentity = IdentityHome.findByCustomerId( request.getSecondaryCuid( ) );
571 if ( secondaryIdentity == null )
572 {
573 response.setStatus(
574 ResponseStatusFactory.failure( ).setMessage( "Could not find secondary identity with customer_id " + request.getSecondaryCuid( ) )
575 .setMessageKey( Constants.PROPERTY_REST_ERROR_SECONDARY_IDENTITY_NOT_FOUND ) );
576 return null;
577 }
578
579 if ( secondaryIdentity.isDeleted( ) )
580 {
581 response.setStatus(
582 ResponseStatusFactory.failure( ).setMessage( "Secondary identity found with customer_id " + request.getSecondaryCuid( ) + " is deleted" )
583 .setMessageKey( Constants.PROPERTY_REST_ERROR_SECONDARY_IDENTITY_DELETED ) );
584 return null;
585 }
586
587 if ( secondaryIdentity.isMerged( ) )
588 {
589 response.setStatus(
590 ResponseStatusFactory.failure( ).setMessage( "Secondary identity found with customer_id " + request.getSecondaryCuid( ) + " is merged" )
591 .setMessageKey( Constants.PROPERTY_REST_ERROR_SECONDARY_IDENTITY_MERGED ) );
592 return null;
593 }
594
595 if ( !Objects.equals( secondaryIdentity.getLastUpdateDate( ), request.getSecondaryLastUpdateDate( ) ) )
596 {
597 response.setStatus( ResponseStatusFactory.failure( )
598 .setMessage( "The secondary identity has been updated recently, please load the latest data before merging." )
599 .setMessageKey( Constants.PROPERTY_REST_ERROR_SECONDARY_IDENTITY_UPDATE_CONFLICT ) );
600 return null;
601 }
602
603 TransactionManager.beginTransaction( null );
604 try
605 {
606 final List<AttributeStatus> attrStatusList = new ArrayList<>( );
607 if ( request.getIdentity( ) != null )
608 {
609 attrStatusList.addAll( this.updateIdentity( request.getIdentity( ), clientCode, response, primaryIdentity ) );
610 if ( ResponseStatusFactory.unauthorized( ).equals( response.getStatus( ) ) )
611 {
612 response.setCustomerId( primaryIdentity.getCustomerId( ) );
613 TransactionManager.rollBack( null );
614 return null;
615 }
616 }
617
618
619 secondaryIdentity.setMerged( true );
620 secondaryIdentity.setMasterIdentityId( primaryIdentity.getId( ) );
621 IdentityHome.merge( secondaryIdentity );
622 IdentityAttributeHome.removeAllAttributes( secondaryIdentity.getId( ) );
623
624 response.setCustomerId( primaryIdentity.getCustomerId( ) );
625 response.setConnectionId( primaryIdentity.getConnectionId( ) );
626 response.setLastUpdateDate( primaryIdentity.getLastUpdateDate( ) );
627
628 final boolean allAttributesCreatedOrUpdated = attrStatusList.stream( ).map( AttributeStatus::getStatus )
629 .allMatch( status -> status.getType( ) == AttributeChangeStatusType.SUCCESS );
630 final ResponseStatus status = allAttributesCreatedOrUpdated ? ResponseStatusFactory.success( ) : ResponseStatusFactory.incompleteSuccess( );
631
632 final String msgKey;
633 if ( Collections.disjoint( AttributeChangeStatus.getSuccessStatuses( ),
634 attrStatusList.stream( ).map( AttributeStatus::getStatus ).collect( Collectors.toList( ) ) ) )
635 {
636
637 msgKey = Constants.PROPERTY_REST_INFO_NO_ATTRIBUTE_CHANGE;
638 }
639 else
640 {
641 msgKey = Constants.PROPERTY_REST_INFO_SUCCESSFUL_OPERATION;
642 }
643
644 response.setStatus( status.setAttributeStatuses( attrStatusList ).setMessageKey( msgKey ) );
645 TransactionManager.commitTransaction( null );
646
647
648 for ( AttributeStatus attributeStatus : attrStatusList )
649 {
650 _identityStoreNotifyListenerService.notifyListenersAttributeChange( AttributeChangeType.MERGE, primaryIdentity, attributeStatus, author,
651 clientCode );
652 }
653
654
655 final Map<String, String> primaryMetadata = new HashMap<>( );
656 primaryMetadata.put( Constants.METADATA_MERGED_MASTER_IDENTITY_CUID, primaryIdentity.getCustomerId( ) );
657 primaryMetadata.put( Constants.METADATA_DUPLICATE_RULE_CODE, request.getDuplicateRuleCode( ) );
658 _identityStoreNotifyListenerService.notifyListenersIdentityChange( IdentityChangeType.MERGED, secondaryIdentity,
659 response.getStatus( ).getType( ).name( ), response.getStatus( ).getType( ).name( ), author, clientCode, primaryMetadata );
660
661 AccessLogService.getInstance( ).info( AccessLoggerConstants.EVENT_TYPE_MODIFY, CONSOLIDATE_IDENTITY_EVENT_CODE,
662 _internalUserService.getApiUser( author, clientCode ), SecurityUtil.logForgingProtect( primaryIdentity.getCustomerId( ) ),
663 SPECIFIC_ORIGIN );
664
665 final Map<String, String> secondaryMetadata = new HashMap<>( );
666 secondaryMetadata.put( Constants.METADATA_MERGED_CHILD_IDENTITY_CUID, secondaryIdentity.getCustomerId( ) );
667 secondaryMetadata.put( Constants.METADATA_DUPLICATE_RULE_CODE, request.getDuplicateRuleCode( ) );
668 _identityStoreNotifyListenerService.notifyListenersIdentityChange( IdentityChangeType.CONSOLIDATED, primaryIdentity,
669 response.getStatus( ).getType( ).name( ), response.getStatus( ).getType( ).name( ), author, clientCode, secondaryMetadata );
670
671 AccessLogService.getInstance( ).info( AccessLoggerConstants.EVENT_TYPE_MODIFY, MERGE_IDENTITY_EVENT_CODE,
672 _internalUserService.getApiUser( author, clientCode ), SecurityUtil.logForgingProtect( secondaryIdentity.getCustomerId( ) ),
673 SPECIFIC_ORIGIN );
674 }
675 catch( Exception e )
676 {
677 response.setStatus(
678 ResponseStatusFactory.failure( ).setMessage( e.getMessage( ) ).setMessageKey( Constants.PROPERTY_REST_ERROR_DURING_TREATMENT ) );
679 TransactionManager.rollBack( null );
680 }
681
682 return primaryIdentity;
683 }
684
685
686
687
688
689
690
691
692
693
694
695
696
697 public void cancelMerge( final IdentityMergeRequest request, final RequestAuthor author, final String clientCode, final IdentityMergeResponse response )
698 {
699 final Identity primaryIdentity = IdentityHome.findByCustomerId( request.getPrimaryCuid( ) );
700 if ( primaryIdentity == null )
701 {
702 response.setStatus( ResponseStatusFactory.failure( ).setMessage( "Could not find primary identity with customer_id " + request.getPrimaryCuid( ) )
703 .setMessageKey( Constants.PROPERTY_REST_ERROR_PRIMARY_IDENTITY_NOT_FOUND ) );
704 return;
705 }
706
707 if ( !Objects.equals( primaryIdentity.getLastUpdateDate( ), request.getPrimaryLastUpdateDate( ) ) )
708 {
709 response.setStatus( ResponseStatusFactory.failure( )
710 .setMessage( "The primary identity has been updated recently, please load the latest data before canceling merge." )
711 .setMessageKey( Constants.PROPERTY_REST_ERROR_PRIMARY_IDENTITY_UPDATE_CONFLICT ) );
712 return;
713 }
714
715 final Identity secondaryIdentity = IdentityHome.findByCustomerId( request.getSecondaryCuid( ) );
716 if ( secondaryIdentity == null )
717 {
718 response.setStatus(
719 ResponseStatusFactory.failure( ).setMessage( "Could not find secondary identity with customer_id " + request.getSecondaryCuid( ) )
720 .setMessageKey( Constants.PROPERTY_REST_ERROR_SECONDARY_IDENTITY_NOT_FOUND ) );
721 return;
722 }
723
724 if ( !secondaryIdentity.isMerged( ) )
725 {
726 response.setStatus(
727 ResponseStatusFactory.failure( ).setMessage( "Secondary identity found with customer_id " + request.getSecondaryCuid( ) + " is not merged" )
728 .setMessageKey( Constants.PROPERTY_REST_ERROR_SECONDARY_IDENTITY_NOT_MERGED ) );
729 return;
730 }
731
732 if ( !Objects.equals( secondaryIdentity.getMasterIdentityId( ), primaryIdentity.getId( ) ) )
733 {
734 response.setStatus( ResponseStatusFactory.failure( )
735 .setMessage( "Secondary identity found with customer_id " + request.getSecondaryCuid( )
736 + " is not merged to Primary identity found with customer ID " + request.getPrimaryCuid( ) )
737 .setMessageKey( Constants.PROPERTY_REST_ERROR_IDENTITIES_NOT_MERGED_TOGETHER ) );
738 return;
739 }
740
741
742
743
744
745
746
747
748
749
750 TransactionManager.beginTransaction( null );
751 try
752 {
753
754 IdentityHome.cancelMerge( secondaryIdentity );
755 response.setStatus( ResponseStatusFactory.success( ).setMessageKey( Constants.PROPERTY_REST_INFO_SUCCESSFUL_OPERATION ) );
756 TransactionManager.commitTransaction( null );
757
758
759 final Map<String, String> secondaryMetadata = new HashMap<>( );
760 secondaryMetadata.put( Constants.METADATA_UNMERGED_MASTER_CUID, primaryIdentity.getCustomerId( ) );
761 _identityStoreNotifyListenerService.notifyListenersIdentityChange( IdentityChangeType.MERGE_CANCELLED, secondaryIdentity,
762 response.getStatus( ).getType( ).name( ), response.getStatus( ).getType( ).name( ), author, clientCode, secondaryMetadata );
763 AccessLogService.getInstance( ).info( AccessLoggerConstants.EVENT_TYPE_MODIFY, CANCEL_MERGE_IDENTITY_EVENT_CODE,
764 _internalUserService.getApiUser( author, clientCode ), SecurityUtil.logForgingProtect( secondaryIdentity.getCustomerId( ) ),
765 SPECIFIC_ORIGIN );
766
767 final Map<String, String> primaryMetadata = new HashMap<>( );
768 primaryMetadata.put( Constants.METADATA_UNMERGED_CHILD_CUID, secondaryIdentity.getCustomerId( ) );
769 _identityStoreNotifyListenerService.notifyListenersIdentityChange( IdentityChangeType.CONSOLIDATION_CANCELLED, primaryIdentity,
770 response.getStatus( ).getType( ).name( ), response.getStatus( ).getType( ).name( ), author, clientCode, primaryMetadata );
771 AccessLogService.getInstance( ).info( AccessLoggerConstants.EVENT_TYPE_MODIFY, CANCEL_CONSOLIDATE_IDENTITY_EVENT_CODE,
772 _internalUserService.getApiUser( author, clientCode ), SecurityUtil.logForgingProtect( primaryIdentity.getCustomerId( ) ),
773 SPECIFIC_ORIGIN );
774 }
775 catch( Exception e )
776 {
777 response.setStatus(
778 ResponseStatusFactory.failure( ).setMessage( e.getMessage( ) ).setMessageKey( Constants.PROPERTY_REST_ERROR_DURING_TREATMENT ) );
779 TransactionManager.rollBack( null );
780 }
781 }
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798 public Identity importIdentity( final IdentityChangeRequest identityChangeRequest, final RequestAuthor author, final String clientCode,
799 final IdentityChangeResponse response ) throws IdentityStoreException
800 {
801 final Map<String, String> attributes = identityChangeRequest.getIdentity( ).getAttributes( ).stream( )
802 .collect( Collectors.toMap( AttributeDto::getKey, AttributeDto::getValue ) );
803
804 final DuplicateSearchResponse certitudeDuplicates = this.checkDuplicates( attributes, PROPERTY_DUPLICATES_IMPORT_RULES_STRICT, "" );
805 if ( certitudeDuplicates != null && CollectionUtils.isNotEmpty( certitudeDuplicates.getIdentities( ) ) )
806 {
807 if ( certitudeDuplicates.getIdentities( ).size( ) == 1 )
808 {
809 final IdentityDto strictDuplicate = certitudeDuplicates.getIdentities( ).get( 0 );
810 identityChangeRequest.getIdentity( ).setLastUpdateDate( strictDuplicate.getLastUpdateDate( ) );
811 return this.update( strictDuplicate.getCustomerId( ), identityChangeRequest, author, clientCode, response );
812 }
813 else
814 {
815 response.setStatus( ResponseStatusFactory.conflict( ).setMessage( certitudeDuplicates.getStatus( ).getMessage( ) )
816 .setMessageKey( certitudeDuplicates.getStatus( ).getMessageKey( ) ) );
817 }
818 }
819 else
820 {
821 final DuplicateSearchResponse suspicionDuplicates = this.checkDuplicates( attributes, PROPERTY_DUPLICATES_IMPORT_RULES_SUSPICION, "" );
822 if ( suspicionDuplicates != null && CollectionUtils.isNotEmpty( suspicionDuplicates.getIdentities( ) ) )
823 {
824 response.setStatus( ResponseStatusFactory.conflict( ).setMessage( suspicionDuplicates.getStatus( ).getMessage( ) )
825 .setMessageKey( suspicionDuplicates.getStatus( ).getMessageKey( ) ) );
826 }
827 else
828 {
829 return this.create( identityChangeRequest, author, clientCode, response );
830 }
831 }
832
833 return null;
834 }
835
836 public IdentityDto getQualifiedIdentity( final String customerId ) throws IdentityStoreException
837 {
838 final Identity identity = IdentityHome.findByCustomerId( customerId );
839 final IdentityDto qualifiedIdentity = DtoConverter.convertIdentityToDto( identity );
840 IdentityQualityService.instance( ).computeQuality( qualifiedIdentity );
841 return qualifiedIdentity;
842 }
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860 public void search( final IdentitySearchRequest request, final RequestAuthor author, final IdentitySearchResponse response, final String clientCode )
861 throws IdentityStoreException
862 {
863 AccessLogService.getInstance( ).info( AccessLoggerConstants.EVENT_TYPE_READ, SEARCH_IDENTITY_EVENT_CODE,
864 _internalUserService.getApiUser( author, clientCode ), SecurityUtil.logForgingProtect( request.toString( ) ), SPECIFIC_ORIGIN );
865 final List<SearchAttribute> providedAttributes = request.getSearch( ).getAttributes( );
866 final Set<String> providedKeys = commonKeytoKey( providedAttributes.stream( ).map( SearchAttribute::getKey ).collect( Collectors.toSet( ) ) );
867
868 boolean hasRequirements = false;
869 final List<IdentitySearchRule> searchRules = IdentitySearchRuleHome.findAll( );
870 final Iterator<IdentitySearchRule> iterator = searchRules.iterator( );
871 while ( !hasRequirements && iterator.hasNext( ) )
872 {
873 final IdentitySearchRule searchRule = iterator.next( );
874 final Set<String> requiredKeys = searchRule.getAttributes( ).stream( ).map( AttributeKey::getKeyName ).collect( Collectors.toSet( ) );
875 if ( searchRule.getType( ) == SearchRuleType.AND )
876 {
877 if ( providedKeys.containsAll( requiredKeys ) )
878 {
879 hasRequirements = true;
880 }
881 }
882 else
883 if ( searchRule.getType( ) == SearchRuleType.OR )
884 {
885 if ( providedKeys.stream( ).anyMatch( requiredKeys::contains ) )
886 {
887 hasRequirements = true;
888 }
889 }
890 }
891
892 if ( !hasRequirements )
893 {
894 final StringBuilder sb = new StringBuilder( );
895 final Iterator<IdentitySearchRule> ruleIt = searchRules.iterator( );
896 while ( ruleIt.hasNext( ) )
897 {
898 final IdentitySearchRule rule = ruleIt.next( );
899 sb.append( "( " );
900 final Iterator<AttributeKey> attrIt = rule.getAttributes( ).iterator( );
901 while ( attrIt.hasNext( ) )
902 {
903 final AttributeKey attr = attrIt.next( );
904 sb.append( attr.getKeyName( ) ).append( " " );
905 if ( attrIt.hasNext( ) )
906 {
907 sb.append( rule.getType( ).name( ) ).append( " " );
908 }
909 }
910 sb.append( ")" );
911 if ( ruleIt.hasNext( ) )
912 {
913 sb.append( " OR " );
914 }
915 }
916 final IdentitySearchMessage alert = new IdentitySearchMessage( );
917 alert.setAttributeName( sb.toString( ) );
918 alert.setMessage( "Please provide those required attributes to be able to search identities." );
919 response.getAlerts( ).add( alert );
920 response.setStatus( ResponseStatusFactory.failure( ).setMessageKey( Constants.PROPERTY_REST_ERROR_MISSING_MANDATORY_ATTRIBUTES ) );
921 return;
922 }
923
924 final QualifiedIdentitySearchResult result = _elasticSearchIdentityService.getQualifiedIdentities( providedAttributes, request.getMax( ),
925 request.isConnected( ), Collections.emptyList( ) );
926 if ( CollectionUtils.isNotEmpty( result.getQualifiedIdentities( ) ) )
927 {
928 final List<IdentityDto> filteredIdentities = this.getEnrichedIdentities( request.getSearch( ).getAttributes( ), clientCode,
929 result.getQualifiedIdentities( ) );
930 response.setIdentities( filteredIdentities );
931 if ( CollectionUtils.isNotEmpty( response.getIdentities( ) ) )
932 {
933 response.setStatus( ResponseStatusFactory.ok( ).setMessageKey( Constants.PROPERTY_REST_INFO_SUCCESSFUL_OPERATION ) );
934 for ( final IdentityDto identity : response.getIdentities( ) )
935 {
936 AccessLogService.getInstance( ).info( AccessLoggerConstants.EVENT_TYPE_READ, SEARCH_IDENTITY_EVENT_CODE,
937 _internalUserService.getApiUser( author, clientCode ), SecurityUtil.logForgingProtect( identity.getCustomerId( ) ),
938 SPECIFIC_ORIGIN );
939 if ( author.getType( ).equals( AuthorType.agent ) )
940 {
941
942 _identityStoreNotifyListenerService.notifyListenersIdentityChange( IdentityChangeType.READ,
943 DtoConverter.convertDtoToIdentity( identity ), response.getStatus( ).getType( ).name( ), response.getStatus( ).getMessage( ),
944 author, clientCode, new HashMap<>( ) );
945 }
946 }
947 }
948 else
949 {
950 response.setStatus( ResponseStatusFactory.noResult( ).setMessageKey( Constants.PROPERTY_REST_ERROR_NO_IDENTITY_FOUND ) );
951 }
952 }
953 else
954 {
955 response.setStatus( ResponseStatusFactory.noResult( ).setMessageKey( Constants.PROPERTY_REST_ERROR_NO_IDENTITY_FOUND ) );
956 }
957 }
958
959
960
961
962
963
964
965 private Set<String> commonKeytoKey( Set<String> providedAttributes )
966 {
967 Set<String> returnKeys = new HashSet<>( );
968
969 for ( String attribute : providedAttributes )
970 {
971 List<AttributeKey> keys = IdentityAttributeService.instance( ).getCommonAttributeKeys( attribute );
972 if ( keys != null && !keys.isEmpty( ) )
973 {
974 for ( AttributeKey key : keys )
975 {
976 returnKeys.add( key.getKeyName( ) );
977 }
978 }
979 else
980 {
981 returnKeys.add( attribute );
982 }
983 }
984 return returnKeys;
985 }
986
987
988
989
990
991
992
993
994
995
996
997
998
999 public void search( final String customerId, final String connectionId, final IdentitySearchResponse response, final String clientCode,
1000 final RequestAuthor author ) throws IdentityStoreException
1001 {
1002 AccessLogService.getInstance( ).info( AccessLoggerConstants.EVENT_TYPE_READ, GET_IDENTITY_EVENT_CODE, _internalUserService.getApiUser( clientCode ),
1003 SecurityUtil.logForgingProtect( StringUtils.isNotBlank( customerId ) ? customerId : connectionId ), SPECIFIC_ORIGIN );
1004
1005 final ServiceContract serviceContract = _serviceContractService.getActiveServiceContract( clientCode );
1006 if ( serviceContract == null )
1007 {
1008 throw new ServiceContractNotFoundException( "No active service contract could be found for clientCode = " + clientCode );
1009 }
1010 final IdentityDto identityDto = StringUtils.isNotBlank( customerId ) ? _identityDtoCache.getByCustomerId( customerId, serviceContract )
1011 : _identityDtoCache.getByConnectionId( connectionId, serviceContract );
1012 if ( identityDto == null )
1013 {
1014
1015
1016 if ( StringUtils.isNotBlank( customerId ) && !IdentityHome.findHistoryByCustomerId( customerId ).isEmpty( ) )
1017 {
1018 response.setStatus( ResponseStatusFactory.notFound( ).setMessageKey( Constants.PROPERTY_REST_ERROR_IDENTITY_DELETED ) );
1019 }
1020 else
1021 {
1022 response.setStatus( ResponseStatusFactory.notFound( ).setMessageKey( Constants.PROPERTY_REST_ERROR_NO_IDENTITY_FOUND ) );
1023 }
1024 }
1025 else
1026 {
1027 response.setIdentities( Collections.singletonList( identityDto ) );
1028 response.setStatus( ResponseStatusFactory.ok( ).setMessageKey( Constants.PROPERTY_REST_INFO_SUCCESSFUL_OPERATION ) );
1029
1030 if ((StringUtils.isNotBlank(customerId) && !identityDto.getCustomerId().equals(customerId)) ||
1031 (StringUtils.isNotBlank(connectionId) && !identityDto.getConnectionId().equals(connectionId))) {
1032 final IdentitySearchMessage alert = new IdentitySearchMessage();
1033 alert.setMessage("Le CUID ou GUID demandé correspond à une identité rapprochée. Cette réponse contient l'identité consilidée.");
1034 response.getAlerts().add(alert);
1035 }
1036 if ( author != null )
1037 {
1038 AccessLogService.getInstance( ).info( AccessLoggerConstants.EVENT_TYPE_READ, SEARCH_IDENTITY_EVENT_CODE,
1039 _internalUserService.getApiUser( author, clientCode ), SecurityUtil.logForgingProtect( identityDto.getCustomerId( ) ),
1040 SPECIFIC_ORIGIN );
1041 }
1042 if ( author != null && author.getType( ).equals( AuthorType.agent ) )
1043 {
1044
1045 _identityStoreNotifyListenerService.notifyListenersIdentityChange( IdentityChangeType.READ, DtoConverter.convertDtoToIdentity( identityDto ),
1046 response.getStatus( ).getType( ).name( ), response.getStatus( ).getMessage( ), author, clientCode, new HashMap<>( ) );
1047 }
1048 }
1049 }
1050
1051
1052
1053
1054
1055
1056
1057
1058 public List<IdentityDto> search( final List<String> customerIds, final List<String> attributes )
1059 {
1060 try
1061 {
1062 final QualifiedIdentitySearchResult result = _elasticSearchIdentityService.getQualifiedIdentities( customerIds, attributes );
1063 if ( result != null && CollectionUtils.isNotEmpty( result.getQualifiedIdentities( ) ) )
1064 {
1065 result.getQualifiedIdentities( ).forEach( identityDto -> {
1066 IdentityQualityService.instance( ).computeQuality( identityDto );
1067 identityDto.getQuality( ).setScoring( 1D );
1068 identityDto.getQuality( ).setCoverage( 1 );
1069 } );
1070 return result.getQualifiedIdentities( );
1071 }
1072 }
1073 catch( final IdentityStoreException e )
1074 {
1075
1076 }
1077 return new ArrayList<>( );
1078 }
1079
1080
1081
1082
1083
1084
1085
1086
1087 public IdentityDto search( final String customerId )
1088 {
1089 try
1090 {
1091 final QualifiedIdentitySearchResult result = _elasticSearchIdentityService.getQualifiedIdentities( customerId, Collections.emptyList( ) );
1092 if ( result != null && CollectionUtils.isNotEmpty( result.getQualifiedIdentities( ) ) )
1093 {
1094 final IdentityDto identityDto = result.getQualifiedIdentities( ).get( 0 );
1095 IdentityQualityService.instance( ).computeQuality( identityDto );
1096 identityDto.getQuality( ).setScoring( 1D );
1097 identityDto.getQuality( ).setCoverage( 1 );
1098 return identityDto;
1099 }
1100 }
1101 catch( final IdentityStoreException e )
1102 {
1103
1104 }
1105 return null;
1106 }
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122 private List<IdentityDto> getEnrichedIdentities( final List<SearchAttribute> searchAttributes, final String clientCode, final List<IdentityDto> identities )
1123 throws ServiceContractNotFoundException
1124 {
1125 final ServiceContract serviceContract = _serviceContractService.getActiveServiceContract( clientCode );
1126 final Comparator<QualityDefinition> qualityComparator = Comparator.comparing( QualityDefinition::getScoring )
1127 .thenComparingDouble( QualityDefinition::getQuality ).reversed( );
1128 final Comparator<IdentityDto> identityComparator = Comparator.comparing( IdentityDto::getQuality, qualityComparator );
1129 return identities.stream( ).filter( IdentityDto::isNotMerged )
1130 .peek( identity -> IdentityQualityService.instance( ).enrich( searchAttributes, identity, serviceContract, null ) ).sorted( identityComparator )
1131 .collect( Collectors.toList( ) );
1132 }
1133
1134 private List<AttributeStatus> updateIdentity( final IdentityDto requestIdentity, final String clientCode, final ChangeResponse response,
1135 final Identity identity ) throws IdentityStoreException
1136 {
1137 final List<AttributeStatus> attrStatusList = new ArrayList<>( );
1138
1139
1140 final Map<Boolean, List<AttributeDto>> sortedAttributes = requestIdentity.getAttributes( ).stream( )
1141 .collect( Collectors.partitioningBy( a -> identity.getAttributes( ).containsKey( a.getKey( ) ) ) );
1142 final List<AttributeDto> existingWritableAttributes = CollectionUtils.isNotEmpty( sortedAttributes.get( true ) ) ? sortedAttributes.get( true )
1143 : new ArrayList<>( );
1144 final List<AttributeDto> newWritableAttributes = CollectionUtils.isNotEmpty( sortedAttributes.get( false ) ) ? sortedAttributes.get( false )
1145 : new ArrayList<>( );
1146
1147
1148 if ( identity.isConnected( ) && !_serviceContractService.canModifyConnectedIdentity( clientCode ) )
1149 {
1150 this.connectedIdentityUpdateCheck( requestIdentity, identity, existingWritableAttributes, newWritableAttributes, response );
1151 if ( ResponseStatusFactory.unauthorized( ).equals( response.getStatus( ) ) )
1152 {
1153 return attrStatusList;
1154 }
1155 }
1156
1157
1158 attrStatusList.addAll( GeocodesService.processCountryAndCityForUpdate( identity, newWritableAttributes, existingWritableAttributes, clientCode ) );
1159
1160
1161 for ( final AttributeDto attributeToWrite : newWritableAttributes )
1162 {
1163 final AttributeStatus attributeStatus = _identityAttributeService.createAttribute( attributeToWrite, identity, clientCode );
1164 attrStatusList.add( attributeStatus );
1165 }
1166
1167
1168 for ( final AttributeDto attributeToUpdate : existingWritableAttributes )
1169 {
1170 final AttributeStatus attributeStatus = _identityAttributeService.updateAttribute( attributeToUpdate, identity, clientCode );
1171 attrStatusList.add( attributeStatus );
1172 }
1173
1174 boolean monParisUpdated = false;
1175 if ( requestIdentity.getMonParisActive( ) != null && requestIdentity.getMonParisActive( ) != identity.isMonParisActive( ) )
1176 {
1177 monParisUpdated = true;
1178 identity.setMonParisActive( requestIdentity.isMonParisActive( ) );
1179 }
1180
1181 if ( monParisUpdated || !Collections.disjoint( AttributeChangeStatus.getSuccessStatuses( ),
1182 attrStatusList.stream( ).map( AttributeStatus::getStatus ).collect( Collectors.toList( ) ) ) )
1183 {
1184
1185 IdentityHome.update( identity );
1186 }
1187
1188 return attrStatusList;
1189 }
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214 private void connectedIdentityUpdateCheck( final IdentityDto requestIdentity, final Identity identity, final List<AttributeDto> existingWritableAttributes,
1215 final List<AttributeDto> newWritableAttributes, final ChangeResponse response )
1216 {
1217
1218 final Map<String, AttributeKey> allAttributesByKey = AttributeKeyHome.getAttributeKeysList( false ).stream( )
1219 .collect( Collectors.toMap( AttributeKey::getKeyName, a -> a ) );
1220
1221
1222 final boolean requestOnNonPivot = requestIdentity.getAttributes( ).stream( ).map( a -> allAttributesByKey.get( a.getKey( ) ) )
1223 .anyMatch( a -> !a.getPivot( ) );
1224 if ( requestOnNonPivot )
1225 {
1226 response.setStatus( ResponseStatusFactory.unauthorized( ).setMessage( "Identity is connected, updating non 'pivot' attributes is forbidden." )
1227 .setMessageKey( Constants.PROPERTY_REST_ERROR_CONNECTED_IDENTITY_FORBIDDEN_UPDATE_NON_PIVOT ) );
1228 return;
1229 }
1230
1231
1232 final boolean newAttrSelfDeclare = newWritableAttributes.stream( )
1233 .map( a -> RefAttributeCertificationLevelHome.findByProcessusAndAttributeKeyName( a.getCertifier( ), a.getKey( ) ) )
1234 .anyMatch( c -> Integer.parseInt( c.getRefCertificationLevel( ).getLevel( ) ) <= 100 );
1235 if ( newAttrSelfDeclare )
1236 {
1237 response.setStatus( ResponseStatusFactory.unauthorized( )
1238 .setMessage( "Identity is connected, adding 'pivot' attributes with self-declarative certification level is forbidden." )
1239 .setMessageKey( Constants.PROPERTY_REST_ERROR_CONNECTED_IDENTITY_FORBIDDEN_PIVOT_SELF_DECLARE ) );
1240 return;
1241 }
1242
1243
1244 final boolean lesserWantedLvl = existingWritableAttributes.stream( )
1245 .map( a -> RefAttributeCertificationLevelHome.findByProcessusAndAttributeKeyName( a.getCertifier( ), a.getKey( ) ) ).anyMatch( wantedCertif -> {
1246 final int wantedLvl = Integer.parseInt( wantedCertif.getRefCertificationLevel( ).getLevel( ) );
1247
1248 final IdentityAttribute existingAttr = identity.getAttributes( ).get( wantedCertif.getAttributeKey( ).getKeyName( ) );
1249 final RefAttributeCertificationLevel existingCertif = RefAttributeCertificationLevelHome.findByProcessusAndAttributeKeyName(
1250 existingAttr.getCertificate( ).getCertifierCode( ), existingAttr.getAttributeKey( ).getKeyName( ) );
1251 final int existingLvl = Integer.parseInt( existingCertif.getRefCertificationLevel( ).getLevel( ) );
1252
1253 return wantedLvl < existingLvl;
1254 } );
1255 if ( lesserWantedLvl )
1256 {
1257 response.setStatus( ResponseStatusFactory.unauthorized( )
1258 .setMessage( "Identity is connected, updating existing 'pivot' attributes with lesser certification level is forbidden." )
1259 .setMessageKey( Constants.PROPERTY_REST_ERROR_CONNECTED_IDENTITY_FORBIDDEN_UPDATE_PIVOT_LESSER_CERTIFICATION ) );
1260 return;
1261 }
1262
1263
1264 final int threshold = AppPropertiesService.getPropertyInt( PIVOT_CERTIF_LEVEL_THRESHOLD, 400 );
1265 final boolean breakingThreshold = identity.getAttributes( ).values( ).stream( ).filter( a -> a.getAttributeKey( ).getPivot( ) )
1266 .map( a -> RefAttributeCertificationLevelHome.findByProcessusAndAttributeKeyName( a.getCertificate( ).getCertifierCode( ),
1267 a.getAttributeKey( ).getKeyName( ) ) )
1268 .anyMatch( c -> Integer.parseInt( c.getRefCertificationLevel( ).getLevel( ) ) >= threshold )
1269 || requestIdentity.getAttributes( ).stream( )
1270 .map( a -> RefAttributeCertificationLevelHome.findByProcessusAndAttributeKeyName( a.getCertifier( ), a.getKey( ) ) )
1271 .anyMatch( c -> Integer.parseInt( c.getRefCertificationLevel( ).getLevel( ) ) >= threshold );
1272 if ( breakingThreshold )
1273 {
1274
1275 final List<String> pivotAttributeKeys = allAttributesByKey.values( ).stream( ).filter( AttributeKey::getPivot ).map( AttributeKey::getKeyName )
1276 .collect( Collectors.toList( ) );
1277
1278
1279 @SuppressWarnings( "unchecked" )
1280 final Collection<String> unionOfExistingAndRequestedPivotKeys = CollectionUtils.union(
1281 requestIdentity.getAttributes( ).stream( ).map( AttributeDto::getKey ).collect( Collectors.toSet( ) ),
1282 identity.getAttributes( ).values( ).stream( ).map( IdentityAttribute::getAttributeKey ).filter( AttributeKey::getPivot )
1283 .map( AttributeKey::getKeyName ).collect( Collectors.toSet( ) ) );
1284 if ( !CollectionUtils.isEqualCollection( pivotAttributeKeys, unionOfExistingAndRequestedPivotKeys ) )
1285 {
1286 response.setStatus( ResponseStatusFactory.unauthorized( )
1287 .setMessage( "Identity is connected, and at least one 'pivot' attribute is, or has been requested to be, certified above level "
1288 + threshold + ". In that case, all 'pivot' attributes must be set, and certified with level greater or equal to " + threshold
1289 + "." )
1290 .setMessageKey( Constants.PROPERTY_REST_ERROR_CONNECTED_IDENTITY_FORBIDDEN_PIVOT_CERTIFICATION_UNDER_THRESHOLD ) );
1291 return;
1292 }
1293
1294
1295 final boolean lesserThanThreshold = pivotAttributeKeys.stream( ).map( key -> {
1296 final AttributeDto requested = requestIdentity.getAttributes( ).stream( ).filter( a -> a.getKey( ).equals( key ) ).findFirst( ).orElse( null );
1297 final IdentityAttribute existing = identity.getAttributes( ).get( key );
1298 int requestedLvl = 0;
1299 int existingLvl = 0;
1300 if ( requested != null )
1301 {
1302 requestedLvl = Integer.parseInt( RefAttributeCertificationLevelHome.findByProcessusAndAttributeKeyName( requested.getCertifier( ), key )
1303 .getRefCertificationLevel( ).getLevel( ) );
1304 }
1305 if ( existing != null )
1306 {
1307 existingLvl = Integer.parseInt(
1308 RefAttributeCertificationLevelHome.findByProcessusAndAttributeKeyName( existing.getCertificate( ).getCertifierCode( ), key )
1309 .getRefCertificationLevel( ).getLevel( ) );
1310 }
1311 return Math.max( requestedLvl, existingLvl );
1312 } ).anyMatch( lvl -> lvl < threshold );
1313
1314 if ( lesserThanThreshold )
1315 {
1316 response.setStatus( ResponseStatusFactory.unauthorized( )
1317 .setMessage( "Identity is connected, and at least one 'pivot' attribute is, or has been requested to be, certified above level "
1318 + threshold + ". In that case, all 'pivot' attributes must be set, and certified with level greater or equal to " + threshold
1319 + "." )
1320 .setMessageKey( Constants.PROPERTY_REST_ERROR_CONNECTED_IDENTITY_FORBIDDEN_PIVOT_CERTIFICATION_UNDER_THRESHOLD ) );
1321 }
1322 }
1323 }
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336 public Batch<String> getCUIDsBatchForPotentialDuplicate(final DuplicateRule rule, final int batchSize, final boolean includeSuspicions )
1337 {
1338 final List<Integer> attributes = rule.getCheckedAttributes( ).stream( ).map( AttributeKey::getId ).collect( Collectors.toList( ) );
1339 final List<String> customerIdsList = IdentityHome.findByAttributeExisting( attributes, rule.getNbFilledAttributes( ), true, !includeSuspicions, rule.getPriority() );
1340 if ( customerIdsList.isEmpty( ) )
1341 {
1342 return Batch.ofSize( Collections.emptyList( ), 0 );
1343 }
1344 return Batch.ofSize( customerIdsList, batchSize );
1345 }
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357 public void deleteRequest( final String customerId, final String clientCode, final RequestAuthor author, final IdentityChangeResponse response )
1358 throws IdentityStoreException
1359 {
1360 if ( !_serviceContractService.canDeleteIdentity( clientCode ) )
1361 {
1362 response.setStatus(
1363 ResponseStatusFactory.failure( ).setMessage( "The client application is not authorized to request the deletion of an identity." )
1364 .setMessageKey( Constants.PROPERTY_REST_ERROR_DELETE_UNAUTHORIZED ) );
1365 response.setCustomerId( customerId );
1366 return;
1367 }
1368
1369
1370 Identity identity = IdentityHome.findByCustomerId( customerId );
1371 if ( identity == null )
1372 {
1373 response.setStatus(
1374 ResponseStatusFactory.notFound( ).setMessage( "Identity not found." ).setMessageKey( Constants.PROPERTY_REST_ERROR_IDENTITY_NOT_FOUND ) );
1375 response.setCustomerId( customerId );
1376 return;
1377 }
1378 if ( identity.isDeleted( ) )
1379 {
1380 response.setStatus( ResponseStatusFactory.failure( ).setMessage( "Identity allready in deleted state." )
1381 .setMessageKey( Constants.PROPERTY_REST_ERROR_IDENTITY_ALREADY_DELETED ) );
1382 response.setCustomerId( customerId );
1383
1384 return;
1385 }
1386 if ( identity.isMerged( ) )
1387 {
1388 response.setStatus( ResponseStatusFactory.failure( ).setMessage( "Identity in merged state can not be deleted." )
1389 .setMessageKey( Constants.PROPERTY_REST_ERROR_FORBIDDEN_DELETE_ON_MERGED_IDENTITY ) );
1390 response.setCustomerId( customerId );
1391 return;
1392 }
1393
1394 TransactionManager.beginTransaction( null );
1395 try
1396 {
1397
1398 IdentityHome.softRemove( customerId );
1399 response.setStatus( ResponseStatusFactory.success( ).setMessageKey( Constants.PROPERTY_REST_INFO_SUCCESSFUL_OPERATION ) );
1400 TransactionManager.commitTransaction( null );
1401
1402
1403 _identityStoreNotifyListenerService.notifyListenersIdentityChange( IdentityChangeType.DELETE, identity, response.getStatus( ).getType( ).name( ),
1404 response.getStatus( ).getMessage( ), author, clientCode, new HashMap<>( ) );
1405
1406 AccessLogService.getInstance( ).info( AccessLoggerConstants.EVENT_TYPE_DELETE, DELETE_IDENTITY_EVENT_CODE,
1407 _internalUserService.getApiUser( author, clientCode ), SecurityUtil.logForgingProtect( customerId ), SPECIFIC_ORIGIN );
1408 }
1409 catch( Exception e )
1410 {
1411 TransactionManager.rollBack( null );
1412 }
1413
1414 }
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427 private DuplicateSearchResponse checkDuplicates( final Map<String, String> attributes, final String ruleCodeProperty, final String customerId )
1428 throws IdentityStoreException
1429 {
1430 final List<String> ruleCodes = Arrays.stream(AppPropertiesService.getProperty( ruleCodeProperty, "" ).split( "," )).filter(StringUtils::isNotEmpty).collect(Collectors.toList());
1431 if( !ruleCodes.isEmpty( ) )
1432 {
1433 final DuplicateSearchResponse esDuplicates = _duplicateServiceElasticSearch.findDuplicates( attributes, customerId, ruleCodes,
1434 Collections.emptyList( ) );
1435 if ( esDuplicates != null )
1436 {
1437 return esDuplicates;
1438 }
1439 final boolean checkDatabase = AppPropertiesService.getPropertyBoolean( PROPERTY_DUPLICATES_CHECK_DATABASE_ACTIVATED, false );
1440 if ( checkDatabase )
1441 {
1442 return _duplicateServiceDatabase.findDuplicates( attributes, "", ruleCodes, Collections.emptyList( ) );
1443 }
1444 }
1445 return null;
1446 }
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456 public IdentityChangeResponse uncertifyIdentity( final String strCustomerId, final String strClientCode, final RequestAuthor author )
1457 {
1458 final IdentityChangeResponse response = new IdentityChangeResponse( );
1459
1460 final Identity identity = IdentityHome.findByCustomerId( strCustomerId );
1461 if ( identity == null )
1462 {
1463 response.setStatus(
1464 ResponseStatusFactory.notFound( ).setMessage( "No identity found" ).setMessageKey( Constants.PROPERTY_REST_ERROR_IDENTITY_NOT_FOUND ) );
1465 return response;
1466 }
1467
1468 TransactionManager.beginTransaction( null );
1469 try
1470 {
1471 final List<AttributeStatus> attrStatusList = new ArrayList<>( );
1472 for ( final IdentityAttribute attribute : identity.getAttributes( ).values( ) )
1473 {
1474 final AttributeStatus status = _identityAttributeService.uncertifyAttribute( attribute );
1475 attrStatusList.add( status );
1476 }
1477
1478
1479 IdentityHome.update( identity );
1480
1481 response.setLastUpdateDate( identity.getLastUpdateDate( ) );
1482 response.setStatus( ResponseStatusFactory.success( ).setAttributeStatuses( attrStatusList )
1483 .setMessageKey( Constants.PROPERTY_REST_INFO_SUCCESSFUL_OPERATION ) );
1484 TransactionManager.commitTransaction( null );
1485
1486
1487 for ( AttributeStatus attributeStatus : attrStatusList )
1488 {
1489 _identityStoreNotifyListenerService.notifyListenersAttributeChange( AttributeChangeType.UPDATE, identity, attributeStatus, author,
1490 strClientCode );
1491 }
1492
1493
1494 _identityStoreNotifyListenerService.notifyListenersIdentityChange( IdentityChangeType.UPDATE, identity, response.getStatus( ).getType( ).name( ),
1495 response.getStatus( ).getMessage( ), author, strClientCode, new HashMap<>( ) );
1496
1497 AccessLogService.getInstance( ).info( AccessLoggerConstants.EVENT_TYPE_MODIFY, DECERTIFY_IDENTITY_EVENT_CODE,
1498 _internalUserService.getApiUser( author, strClientCode ), SecurityUtil.logForgingProtect( strCustomerId ), SPECIFIC_ORIGIN );
1499 }
1500 catch( final Exception e )
1501 {
1502 response.setStatus(
1503 ResponseStatusFactory.failure( ).setMessage( e.getMessage( ) ).setMessageKey( Constants.PROPERTY_REST_ERROR_DURING_TREATMENT ) );
1504 TransactionManager.rollBack( null );
1505 }
1506
1507 return response;
1508 }
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525 public void delete( final String customerId )
1526 {
1527 final int identityId = IdentityHome.findIdByCustomerId( customerId );
1528 if ( identityId != -1 )
1529 {
1530 final List<Identity> mergedIdentities = IdentityHome.findMergedIdentities( identityId );
1531 TransactionManager.beginTransaction( null );
1532 try
1533 {
1534
1535 for ( final Identity mergedIdentity : mergedIdentities )
1536 {
1537 SuspiciousIdentityHome.remove( mergedIdentity.getCustomerId( ) );
1538 SuspiciousIdentityHome.removeExcludedIdentities( mergedIdentity.getCustomerId( ) );
1539 IdentityHome.deleteAttributeHistory( mergedIdentity.getId( ) );
1540 IdentityHome.hardRemove( mergedIdentity.getId( ) );
1541 }
1542
1543 SuspiciousIdentityHome.remove( customerId );
1544 SuspiciousIdentityHome.removeExcludedIdentities( customerId );
1545 IdentityHome.deleteAttributeHistory( identityId );
1546 IdentityHome.hardRemove( identityId );
1547
1548 TransactionManager.commitTransaction( null );
1549 }
1550 catch( final Exception e )
1551 {
1552 TransactionManager.rollBack( null );
1553 throw e;
1554 }
1555 }
1556 }
1557
1558 }