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.attribute;
35
36 import java.text.ParseException;
37 import java.util.ArrayList;
38 import java.util.Comparator;
39 import java.util.Date;
40 import java.util.HashMap;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.Optional;
44 import java.util.function.Function;
45 import java.util.regex.Pattern;
46 import java.util.stream.Collectors;
47
48 import org.apache.commons.lang3.StringUtils;
49 import org.apache.commons.lang3.time.DateUtils;
50
51 import fr.paris.lutece.plugins.geocodes.business.City;
52 import fr.paris.lutece.plugins.geocodes.business.Country;
53 import fr.paris.lutece.plugins.geocodes.service.GeoCodesService;
54 import fr.paris.lutece.plugins.identitystore.business.attribute.AttributeKey;
55 import fr.paris.lutece.plugins.identitystore.cache.IdentityAttributeValidationCache;
56 import fr.paris.lutece.plugins.identitystore.service.contract.AttributeCertificationDefinitionService;
57 import fr.paris.lutece.plugins.identitystore.service.identity.IdentityAttributeNotFoundException;
58 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.AttributeChangeStatus;
59 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.AttributeDto;
60 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.AttributeStatus;
61 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.ChangeResponse;
62 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.IdentityDto;
63 import fr.paris.lutece.plugins.identitystore.v3.web.rs.util.Constants;
64 import fr.paris.lutece.plugins.identitystore.v3.web.rs.util.ResponseStatusFactory;
65 import fr.paris.lutece.plugins.identitystore.web.exception.IdentityStoreException;
66 import fr.paris.lutece.portal.service.spring.SpringContextService;
67 import fr.paris.lutece.portal.service.util.AppPropertiesService;
68
69
70
71
72 public class IdentityAttributeValidationService
73 {
74
75 private final IdentityAttributeValidationCache _cache = SpringContextService.getBean( "identitystore.identityAttributeValidationCache" );
76 private final int pivotCertificationLevelThreshold = AppPropertiesService
77 .getPropertyInt( "identitystore.identity.attribute.pivot.certification.level.threshold", 400 );
78 private static IdentityAttributeValidationService _instance;
79
80 public static IdentityAttributeValidationService instance( )
81 {
82 if ( _instance == null )
83 {
84 _instance = new IdentityAttributeValidationService( );
85 _instance._cache.refresh( );
86 }
87 return _instance;
88 }
89
90 public boolean validateAttribute( final String key, final String value) throws IdentityAttributeNotFoundException {
91 return _cache.get( key ).matcher( value ).matches( );
92 }
93
94
95
96
97
98
99
100
101
102
103 public void validateIdentityAttributeValues( final IdentityDto identity, final ChangeResponse response ) throws IdentityAttributeNotFoundException
104 {
105 final List<AttributeStatus> attrStatusList = new ArrayList<>( );
106 if ( identity != null )
107 {
108 for ( final AttributeDto attribute : identity.getAttributes( ) )
109 {
110 if ( StringUtils.isNotBlank( attribute.getValue( ) ) )
111 {
112 final Pattern validationPattern = _cache.get( attribute.getKey( ) );
113 if ( validationPattern != null )
114 {
115 if ( !validationPattern.matcher( attribute.getValue( ) ).matches( ) )
116 {
117 attrStatusList.add( this.buildAttributeValueValidationErrorStatus( attribute.getKey( ) ) );
118 }
119 }
120 }
121 }
122 }
123 if ( !attrStatusList.isEmpty( ) )
124 {
125 response.setStatus( ResponseStatusFactory.failure( ).setAttributeStatuses( attrStatusList )
126 .setMessage( "Some attribute values are not passing validation. Please check in the attribute statuses for details." )
127 .setMessageKey( Constants.PROPERTY_REST_ERROR_FAIL_ATTRIBUTE_VALIDATION ) );
128 }
129 }
130
131
132
133
134
135
136
137
138 private AttributeStatus buildAttributeValueValidationErrorStatus( final String attrStrKey ) throws IdentityAttributeNotFoundException
139 {
140 final AttributeKey attributeKey = IdentityAttributeService.instance( ).getAttributeKey( attrStrKey );
141 final AttributeStatus attributeStatus = new AttributeStatus( );
142 attributeStatus.setKey( attrStrKey );
143 attributeStatus.setStatus( AttributeChangeStatus.INVALID_VALUE );
144 attributeStatus.setMessage( attributeKey.getValidationErrorMessage( ) );
145 attributeStatus.setMessageKey( attributeKey.getValidationErrorMessageKey( ) );
146
147 return attributeStatus;
148 }
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164 public void validatePivotAttributesIntegrity( final IdentityDto existingIdentityDto, final String clientCode, final IdentityDto identity,
165 boolean geocodesCheck, final ChangeResponse response ) throws IdentityStoreException
166 {
167
168
169 final List<String> pivotKeys = IdentityAttributeService.instance( ).getPivotAttributeKeys( ).stream( ).map( AttributeKey::getKeyName )
170 .collect( Collectors.toList( ) );
171
172
173 final Map<String, AttributeDto> pivotUpdatedAttrs = identity.getAttributes( ).stream( ).filter( a -> pivotKeys.contains( a.getKey( ) ) )
174 .peek( a -> a.setCertificationLevel( AttributeCertificationDefinitionService.instance( ).getLevelAsInteger( a.getCertifier( ), a.getKey( ) ) ) )
175 .collect( Collectors.toMap( AttributeDto::getKey, Function.identity( ) ) );
176
177
178 if ( pivotUpdatedAttrs.isEmpty( ) )
179 {
180 return;
181 }
182
183
184 final Map<String, AttributeDto> pivotExistingAttrs = new HashMap<>( );
185 if ( existingIdentityDto != null )
186 {
187 pivotExistingAttrs.putAll( existingIdentityDto.getAttributes( ).stream( ).filter( a -> pivotKeys.contains( a.getKey( ) ) )
188 .collect( Collectors.toMap( AttributeDto::getKey, Function.identity( ) ) ) );
189 }
190
191
192
193
194 final List<AttributeStatus> geocodeStatuses = new ArrayList<>( );
195 Date birthdate = null;
196 AttributeDto birthdateAttr = pivotUpdatedAttrs.get( Constants.PARAM_BIRTH_DATE );
197 if ( birthdateAttr == null && existingIdentityDto != null )
198 {
199 birthdateAttr = pivotExistingAttrs.get( Constants.PARAM_BIRTH_DATE );
200 }
201
202 try
203 {
204 if ( birthdateAttr != null )
205 {
206 birthdate = DateUtils.parseDate( birthdateAttr.getValue( ), "dd/MM/yyyy" );
207 }
208 }
209 catch( final ParseException e )
210 {
211 final AttributeStatus birthplaceCodeStatus = new AttributeStatus( );
212 birthplaceCodeStatus.setKey( Constants.PARAM_BIRTH_DATE );
213 birthplaceCodeStatus.setStatus( AttributeChangeStatus.INVALID_VALUE );
214 birthplaceCodeStatus.setMessageKey( Constants.PROPERTY_ATTRIBUTE_STATUS_NOT_UPDATED );
215 geocodeStatuses.add( birthplaceCodeStatus );
216 }
217
218 if ( birthdate == null )
219 {
220 pivotUpdatedAttrs.remove( Constants.PARAM_BIRTH_DATE );
221
222
223
224 pivotUpdatedAttrs.remove( Constants.PARAM_BIRTH_PLACE_CODE );
225 pivotUpdatedAttrs.remove( Constants.PARAM_BIRTH_COUNTRY_CODE );
226
227 }
228
229
230
231 if ( geocodesCheck && pivotUpdatedAttrs.containsKey( Constants.PARAM_BIRTH_PLACE_CODE ) && birthdate != null )
232 {
233 final AttributeDto birthPlaceCodeAttr = pivotUpdatedAttrs.get( Constants.PARAM_BIRTH_PLACE_CODE );
234 if ( StringUtils.isNotBlank( birthPlaceCodeAttr.getValue( ) ) )
235 {
236 final Optional<City> city = GeoCodesService.getInstance( ).getCityByDateAndCode( birthdate, birthPlaceCodeAttr.getValue( ) );
237 if ( city == null || !city.isPresent( ) )
238 {
239 pivotUpdatedAttrs.remove( Constants.PARAM_BIRTH_PLACE_CODE );
240 final AttributeStatus birthplaceCodeStatus = new AttributeStatus( );
241 birthplaceCodeStatus.setKey( Constants.PARAM_BIRTH_PLACE_CODE );
242 birthplaceCodeStatus.setStatus( AttributeChangeStatus.UNKNOWN_GEOCODES_CODE );
243 birthplaceCodeStatus.setMessageKey( Constants.PROPERTY_ATTRIBUTE_STATUS_VALIDATION_ERROR_UNKNOWN_GEOCODES_CODE );
244 geocodeStatuses.add( birthplaceCodeStatus );
245 }
246 }
247 }
248
249 if ( geocodesCheck && pivotUpdatedAttrs.containsKey( Constants.PARAM_BIRTH_COUNTRY_CODE ) && birthdate != null )
250 {
251 final AttributeDto birthcountryCodeAttr = pivotUpdatedAttrs.get( Constants.PARAM_BIRTH_COUNTRY_CODE );
252 if ( StringUtils.isNotBlank( birthcountryCodeAttr.getValue( ) ) )
253 {
254
255 final Optional<Country> country = GeoCodesService.getInstance( ).getCountryByCode( birthcountryCodeAttr.getValue( ) );
256 if ( country == null || !country.isPresent( ) )
257 {
258 pivotUpdatedAttrs.remove( Constants.PARAM_BIRTH_COUNTRY_CODE );
259 final AttributeStatus birthcountryCodeStatus = new AttributeStatus( );
260 birthcountryCodeStatus.setKey( Constants.PARAM_BIRTH_COUNTRY_CODE );
261 birthcountryCodeStatus.setStatus( AttributeChangeStatus.UNKNOWN_GEOCODES_CODE );
262 birthcountryCodeStatus.setMessageKey( Constants.PROPERTY_ATTRIBUTE_STATUS_VALIDATION_ERROR_UNKNOWN_GEOCODES_CODE );
263 geocodeStatuses.add( birthcountryCodeStatus );
264 }
265 }
266 }
267
268
269
270 final Map<String, AttributeDto> pivotTargetAttrs = new HashMap<>( );
271 pivotTargetAttrs.putAll( pivotUpdatedAttrs );
272
273
274 if ( existingIdentityDto != null )
275 {
276
277
278
279 pivotTargetAttrs.putAll( pivotExistingAttrs.keySet( ).stream( )
280 .filter( a -> ( !pivotUpdatedAttrs.containsKey( a )
281 || pivotUpdatedAttrs.get( a ).getCertificationLevel( ) < pivotExistingAttrs.get( a ).getCertificationLevel( ) ) )
282 .collect( Collectors.toMap( Function.identity( ), pivotExistingAttrs::get ) ) );
283 }
284
285
286 if ( pivotTargetAttrs.containsKey( Constants.PARAM_BIRTH_COUNTRY_CODE )
287 && !pivotTargetAttrs.get( Constants.PARAM_BIRTH_COUNTRY_CODE ).getValue( ).equals( Constants.GEOCODE_MAIN_COUNTRY_CODE ) )
288 {
289 pivotKeys.remove( Constants.PARAM_BIRTH_PLACE_CODE );
290 pivotUpdatedAttrs.remove( Constants.PARAM_BIRTH_PLACE_CODE );
291 pivotTargetAttrs.remove( Constants.PARAM_BIRTH_PLACE_CODE );
292 }
293
294
295
296
297 final AttributeDto highestCertifiedPivot = pivotTargetAttrs.values( ).stream( ).max( Comparator.comparing( AttributeDto::getCertificationLevel ) )
298 .orElse( null );
299
300
301 if ( highestCertifiedPivot.getCertificationLevel( ) < pivotCertificationLevelThreshold )
302 {
303 return;
304 }
305
306
307 if ( !( ( pivotTargetAttrs.size( ) == pivotKeys.size( ) ) || ( pivotTargetAttrs.size( ) >= pivotKeys.size( )
308 && !Constants.GEOCODE_MAIN_COUNTRY_CODE.equals( pivotTargetAttrs.get( Constants.PARAM_BIRTH_COUNTRY_CODE ).getValue( ) ) ) ) )
309 {
310 response.setStatus( ResponseStatusFactory.failure( ).setMessageKey( Constants.PROPERTY_REST_ERROR_IDENTITY_ALL_PIVOT_ATTRIBUTE_SAME_CERTIFICATION )
311 .setMessage( "Above level " + pivotCertificationLevelThreshold
312 + ", all pivot attributes must be present and have the same certification level." ) );
313 response.getStatus( ).getAttributeStatuses( ).addAll( geocodeStatuses );
314 return;
315 }
316
317
318 if ( pivotTargetAttrs.values( ).stream( ).anyMatch( a -> !a.getCertifier( ).equals( highestCertifiedPivot.getCertifier( ) ) ) )
319 {
320 response.setStatus( ResponseStatusFactory.failure( ).setMessageKey( Constants.PROPERTY_REST_ERROR_IDENTITY_ALL_PIVOT_ATTRIBUTE_SAME_CERTIFICATION )
321 .setMessage( "All pivot attributes must be set and certified with the '" + highestCertifiedPivot.getCertifier( ) + "' certifier" ) );
322 response.getStatus( ).getAttributeStatuses( ).addAll( geocodeStatuses );
323 return;
324 }
325
326
327
328
329 if ( pivotTargetAttrs.values( ).stream( ).anyMatch( a -> StringUtils.isBlank( a.getValue( ) ) ) )
330 {
331 response.setStatus( ResponseStatusFactory.failure( ).setMessageKey( Constants.PROPERTY_REST_ERROR_IDENTITY_FORBIDDEN_PIVOT_ATTRIBUTE_DELETION )
332 .setMessage( "Deleting pivot attribute is forbidden for this identity." ) );
333 response.getStatus( ).getAttributeStatuses( ).addAll( geocodeStatuses );
334 }
335
336 }
337
338 }