ServiceContractService.java
/*
* Copyright (c) 2002-2024, City of Paris
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright notice
* and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice
* and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* 3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* License 1.0
*/
package fr.paris.lutece.plugins.identitystore.service.contract;
import fr.paris.lutece.plugins.identitystore.business.application.ClientApplication;
import fr.paris.lutece.plugins.identitystore.business.application.ClientApplicationHome;
import fr.paris.lutece.plugins.identitystore.business.attribute.AttributeKey;
import fr.paris.lutece.plugins.identitystore.business.contract.AttributeCertification;
import fr.paris.lutece.plugins.identitystore.business.contract.AttributeRequirement;
import fr.paris.lutece.plugins.identitystore.business.contract.AttributeRight;
import fr.paris.lutece.plugins.identitystore.business.contract.ServiceContract;
import fr.paris.lutece.plugins.identitystore.business.contract.ServiceContractHome;
import fr.paris.lutece.plugins.identitystore.business.referentiel.RefAttributeCertificationProcessus;
import fr.paris.lutece.plugins.identitystore.cache.ActiveServiceContractCache;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.DtoConverter;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.AttributeChangeStatus;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.AttributeDto;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.AttributeStatus;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.IdentityDto;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.contract.ServiceContractDto;
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.dto.exporting.IdentityExportRequest;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.exporting.IdentityExportResponse;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.merge.IdentityMergeRequest;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.merge.IdentityMergeResponse;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.search.IdentitySearchMessage;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.search.IdentitySearchRequest;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.search.SearchAttribute;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.util.Constants;
import fr.paris.lutece.plugins.identitystore.web.exception.ClientAuthorizationException;
import fr.paris.lutece.plugins.identitystore.web.exception.IdentityStoreException;
import fr.paris.lutece.plugins.identitystore.web.exception.RequestFormatException;
import fr.paris.lutece.plugins.identitystore.web.exception.ResourceNotFoundException;
import fr.paris.lutece.portal.service.i18n.I18nService;
import fr.paris.lutece.portal.service.spring.SpringContextService;
import fr.paris.lutece.util.sql.TransactionManager;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ServiceContractService
{
private final ActiveServiceContractCache _cache = SpringContextService.getBean( "identitystore.activeServiceContractCache" );
private static ServiceContractService _instance;
public static ServiceContractService instance( )
{
if ( _instance == null )
{
_instance = new ServiceContractService( );
_instance._cache.refresh( );
}
return _instance;
}
private ServiceContractService( )
{
}
/**
* Get the active {@link ServiceContract} associated to the given {@link ClientApplication}
*
* @param clientCode
* code of the {@link ClientApplication} requesting the change
* @return the active {@link ServiceContract}
* @throws ResourceNotFoundException
*/
public ServiceContract getActiveServiceContract( final String clientCode ) throws ClientAuthorizationException {
return _cache.get( clientCode );
}
public List<String> getMandatoryAttributes( final ServiceContract serviceContract, final List<AttributeKey> sharedMandatoryAttributeList )
{
final List<AttributeRight> rights = serviceContract.getAttributeRights( );
return Stream
.concat( sharedMandatoryAttributeList.stream( ).map( AttributeKey::getKeyName ),
rights.stream( ).filter( AttributeRight::isMandatory ).map( ar -> ar.getAttributeKey( ).getKeyName( ) ) )
.distinct( ).collect( Collectors.toList( ) );
}
public int getDataRetentionPeriodInMonths( final String clientCode ) throws ClientAuthorizationException
{
final ServiceContract serviceContract = this.getActiveServiceContract( clientCode );
return serviceContract.getDataRetentionPeriodInMonths( );
}
private AttributeStatus buildAttributeStatus( final AttributeDto attributeDto, final AttributeChangeStatus status )
{
final AttributeStatus attributeStatus = new AttributeStatus( );
attributeStatus.setKey( attributeDto.getKey( ) );
attributeStatus.setStatus( status );
return attributeStatus;
}
/**
* Creates a new {@link ServiceContract} (if possible) and adds it to cache if active.
*
* @param serviceContract
* the service contract to create
* @param clientApplication
* the client application
*/
public ServiceContract create( final ServiceContract serviceContract, final ClientApplication clientApplication ) throws IdentityStoreException
{
TransactionManager.beginTransaction( null );
try
{
ServiceContractHome.create( serviceContract, clientApplication.getId( ) );
if ( CollectionUtils.isNotEmpty( serviceContract.getAttributeRights( ) ) )
{
ServiceContractHome.addAttributeRights( serviceContract.getAttributeRights( ), serviceContract );
}
if ( CollectionUtils.isNotEmpty( serviceContract.getAttributeRequirements( ) ) )
{
ServiceContractHome.addAttributeRequirements( serviceContract.getAttributeRequirements( ), serviceContract );
}
if ( CollectionUtils.isNotEmpty( serviceContract.getAttributeCertifications( ) ) )
{
ServiceContractHome.addAttributeCertifications( serviceContract.getAttributeCertifications( ), serviceContract );
}
serviceContract.setClientCode( clientApplication.getClientCode( ) );
if ( serviceContract.isActive( ) )
{
this._cache.put( clientApplication.getClientCode( ), serviceContract );
}
TransactionManager.commitTransaction( null );
}
catch( final Exception e )
{
TransactionManager.rollBack( null );
throw new IdentityStoreException( e.getMessage( ), Constants.PROPERTY_REST_ERROR_DURING_TREATMENT );
}
return serviceContract;
}
/**
* Update an existing {@link ServiceContract} (if possible) and adds it to cache if active.
*
* @param serviceContract
* @param clientApplication
* @return
*/
public ServiceContract update( final ServiceContract serviceContract, final ClientApplication clientApplication ) throws IdentityStoreException
{
TransactionManager.beginTransaction( null );
try
{
ServiceContractHome.update( serviceContract, clientApplication.getId( ) );
ServiceContractHome.removeAttributeRights( serviceContract );
ServiceContractHome.addAttributeRights( serviceContract.getAttributeRights( ), serviceContract );
ServiceContractHome.removeAttributeRequirements( serviceContract );
ServiceContractHome.addAttributeRequirements( serviceContract.getAttributeRequirements( ), serviceContract );
ServiceContractHome.removeAttributeCertifications( serviceContract );
ServiceContractHome.addAttributeCertifications( serviceContract.getAttributeCertifications( ), serviceContract );
serviceContract.setClientCode( clientApplication.getClientCode( ) );
if ( serviceContract.isActive( ) )
{
this._cache.deleteById( serviceContract.getId( ) );
this._cache.put( clientApplication.getClientCode( ), serviceContract );
}
TransactionManager.commitTransaction( null );
}
catch( Exception e )
{
TransactionManager.rollBack( null );
throw new IdentityStoreException( e.getMessage( ), e );
}
return serviceContract;
}
/**
* Closes an existing {@link ServiceContract} (if possible) and adds it to cache if active.
*
* @param serviceContract
* @return
*/
public ServiceContract close( final ServiceContract serviceContract ) throws IdentityStoreException
{
TransactionManager.beginTransaction( null );
try
{
ServiceContractHome.close( serviceContract );
TransactionManager.commitTransaction( null );
}
catch( Exception e )
{
TransactionManager.rollBack( null );
throw new IdentityStoreException( e.getMessage( ), e );
}
return serviceContract;
}
/**
* Deletes a {@link ServiceContract} by its id in the database
*
* @param id
*/
public void delete( final Integer id )
{
ServiceContractHome.remove( id );
_cache.deleteById( id );
}
/**
* Deletes a {@link ClientApplication} and all the related {@link ServiceContract}
*
* @param clientApplication
*/
public void deleteApplication( final ClientApplication clientApplication ) throws IdentityStoreException
{
TransactionManager.beginTransaction( null );
try
{
ClientApplicationHome.removeContracts( clientApplication );
ClientApplicationHome.remove( clientApplication );
_cache.removeKey( clientApplication.getClientCode( ) );
TransactionManager.commitTransaction( null );
}
catch( Exception e )
{
TransactionManager.rollBack( null );
throw new IdentityStoreException( e.getMessage( ), e );
}
}
/**
* Checks if the definition of a given {@link ServiceContract} is Valid. Start date and End date of the given contract shall not be in the range of Start
* date and End date of an existing contract.
*
* @param serviceContract
* @param clientApplicationId
*/
public void validateContractDefinition( final ServiceContract serviceContract, final int clientApplicationId ) throws RequestFormatException
{
final List<ServiceContract> serviceContracts = ClientApplicationHome.selectServiceContracts( clientApplicationId );
if ( serviceContracts == null || serviceContracts.isEmpty( ) )
{
return;
}
if ( serviceContract.getStartingDate( ) == null )
{
throw new RequestFormatException( "Provided Service Contract must specify a starting date",
Constants.PROPERTY_REST_ERROR_SERVICE_CONTRACT_WITHOUT_START_DATE );
}
// filter current contract in case of update
final List<ServiceContract> filteredServiceContracts = serviceContracts.stream( ).filter( c -> !Objects.equals( c.getId( ), serviceContract.getId( ) ) )
.collect( Collectors.toList( ) );
if ( filteredServiceContracts.stream( )
.anyMatch( contract -> isInRange( serviceContract.getStartingDate( ), contract.getStartingDate( ), contract.getEndingDate( ) ) ) )
{
throw new RequestFormatException( "The start date of the contract is in the range of an existing service contract",
Constants.PROPERTY_REST_ERROR_SERVICE_CONTRACT_START_DATE_CONFLICT );
}
if ( filteredServiceContracts.stream( )
.anyMatch( contract -> isInRange( serviceContract.getEndingDate( ), contract.getStartingDate( ), contract.getEndingDate( ) ) ) )
{
throw new RequestFormatException( "The end date of the contract is in the range of an existing service contract",
Constants.PROPERTY_REST_ERROR_SERVICE_CONTRACT_END_DATE_CONFLICT );
}
// TODO traiter le cas où il existe un contrat sans date de fin => soit on interdit soit on ferme le contrat automatiquement
if ( filteredServiceContracts.stream( )
.anyMatch( contract -> contract.getEndingDate( ) == null && contract.getStartingDate( ).before( serviceContract.getStartingDate( ) ) ) )
{
throw new RequestFormatException( "A contract exists with an infinite end date",
Constants.PROPERTY_REST_ERROR_NEVERENDING_SERVICE_CONTRACT_EXISTING );
}
// https://dev.lutece.paris.fr/gitlab/bild/gestion-identite/identity-management/-/issues/224
// Les processus sélectionnés pour l'écriture d'un attribut doivent avoir un level >= Niveau de certification minimum exigé s'il est présent
final List<AttributeRequirement> filledRequirements = serviceContract.getAttributeRequirements( ).stream( )
.filter( requirement -> requirement.getRefCertificationLevel( ) != null && requirement.getRefCertificationLevel( ).getLevel( ) != null )
.collect( Collectors.toList( ) );
final StringBuilder message = new StringBuilder( );
boolean hasLevelError = false;
for ( final AttributeRequirement requirement : filledRequirements )
{
final Optional<AttributeCertification> result = serviceContract.getAttributeCertifications( ).stream( )
.filter( certification -> certification.getAttributeKey( ).getId( ) == requirement.getAttributeKey( ).getId( ) ).findFirst( );
if ( result.isPresent( ) )
{
final AttributeCertification attributeCertification = result.get( );
final int minLevel = Integer.parseInt( requirement.getRefCertificationLevel( ).getLevel( ) );
for ( final RefAttributeCertificationProcessus processus : attributeCertification.getRefAttributeCertificationProcessus( ) )
{
final int processLevel = AttributeCertificationDefinitionService.instance( ).getLevelAsInteger( processus.getCode( ),
attributeCertification.getAttributeKey( ).getKeyName( ) );
if ( processLevel < minLevel )
{
hasLevelError = true;
final String [ ] params = {
processus.getLabel( ), requirement.getAttributeKey( ).getName( ), String.valueOf( processLevel ), String.valueOf( minLevel )
};
message.append(
I18nService.getLocalizedString( "identitystore.message.error.servicecontract.processus.level", params, Locale.getDefault( ) ) );
message.append( "<br>" );
}
}
}
}
if ( hasLevelError )
{
throw new RequestFormatException( message.toString( ), Constants.PROPERTY_REST_ERROR_SERVICE_CONTRACT_INSUFICIENT_PROCESSUS_LEVEL );
}
}
public boolean isInRange( final Date testedDate, final Date min, final Date max )
{
if ( testedDate != null && min != null && max != null )
{
return testedDate.getTime( ) >= min.getTime( ) && testedDate.getTime( ) <= max.getTime( );
}
return false;
}
/**
* get Client Code list From AppCode
*
* @param appCode
* the app code
* @return list of corresponding client code
*/
public List<String> getClientCodesFromAppCode( String appCode )
{
final List<ClientApplication> clientApplicationList = ClientApplicationHome.findByApplicationCode( appCode );
return clientApplicationList.stream( ).map( ClientApplication::getClientCode ).collect( Collectors.toList( ) );
}
// ====================================//
/**
* Validates that the {@link ServiceContract} associated to the {@link ClientApplication} requesting the search, is authorizing searching.
*
* @param serviceContract
* the service contract
* @throws ClientAuthorizationException
*/
public void validateGetAuthorization( final ServiceContract serviceContract ) throws ClientAuthorizationException
{
List<AttributeRight> attrList = serviceContract.getAttributeRights ( );
// at least one attribute should be readable
for ( AttributeRight right : attrList )
{
if ( right.isReadable ( ) )
{
return ;
}
}
// otherwise
throw new ClientAuthorizationException( "The service contract of the sent client code doesn't contain any readable attribute.",
Constants.PROPERTY_REST_ERROR_CLIENT_AUTHORIZATION_SEARCH );
}
/**
* Validates the {@link IdentitySearchRequest} against the {@link ServiceContract} associated to the {@link ClientApplication} requesting the search. Each
* violation is listed in the {@link ClientAuthorizationException}'s response with a status by attribute key. The following rules are verified:
* <ul>
* <li>The service contract must exist and allow to search identities</li>
* <li>The requested attributes must be searchable</li>
* </ul>
*
* @param identitySearchRequest
* {@link IdentitySearchRequest} with list of attributes
* @param serviceContract
* the service contract
* @throws ClientAuthorizationException
*/
public void validateSearchAuthorization( final IdentitySearchRequest identitySearchRequest, final ServiceContract serviceContract )
throws ClientAuthorizationException
{
this.validateGetAuthorization( serviceContract );
if ( identitySearchRequest.getSearch( ) != null )
{
final List<IdentitySearchMessage> alerts = new ArrayList<>( );
for ( final SearchAttribute searchAttribute : identitySearchRequest.getSearch( ).getAttributes( ) )
{
final Optional<AttributeRight> attributeRight = serviceContract.getAttributeRights( ).stream( )
.filter( a -> StringUtils.equals( a.getAttributeKey( ).getKeyName( ), searchAttribute.getKey( ) ) ).findFirst( );
if ( attributeRight.isPresent( ) )
{
boolean canSearchAttribute = attributeRight.get( ).isSearchable( );
if ( !canSearchAttribute )
{
final IdentitySearchMessage alert = new IdentitySearchMessage( );
alert.setAttributeName( searchAttribute.getKey( ) );
alert.setMessage( "This attribute is not searchable in service contract definition." );
alerts.add( alert );
}
}
else
{ // if key does not exist, it can be a common key for search
final List<AttributeRight> commonAttributes = serviceContract.getAttributeRights( ).stream( )
.filter( a -> StringUtils.equals( a.getAttributeKey( ).getCommonSearchKeyName( ), searchAttribute.getKey( ) ) )
.collect( Collectors.toList( ) );
if ( CollectionUtils.isNotEmpty( commonAttributes ) )
{
boolean canSearchAttribute = commonAttributes.stream( ).allMatch( a -> a.isSearchable( ) );
if ( !canSearchAttribute )
{
final IdentitySearchMessage alert = new IdentitySearchMessage( );
alert.setAttributeName( searchAttribute.getKey( ) );
alert.setMessage( "This attribute group is not searchable in service contract definition." );
alerts.add( alert );
}
}
else
{
final IdentitySearchMessage alert = new IdentitySearchMessage( );
alert.setAttributeName( searchAttribute.getKey( ) );
alert.setMessage( "This attribute does not exist in service contract definition." );
alerts.add( alert );
}
}
}
if ( CollectionUtils.isNotEmpty( alerts ) )
{
throw new ClientAuthorizationException( "The request violates service contract definition",
Constants.PROPERTY_REST_ERROR_SERVICE_CONTRACT_VIOLATION, alerts );
}
}
}
/**
* Validates the {@link IdentityChangeRequest} against the {@link ServiceContract} associated to the {@link ClientApplication} requesting the change. Each
* violation is listed in the {@link ClientAuthorizationException}'s response with a status by attribute key. The following rules are verified: <br>
* <ul>
* <li>The service contract must exist and allow writing identities</li>
* <li>The attribute must be writable</li>
* <li>The certification processus, if given in the request, must be in the list of authorized processus</li>
* </ul>
*
* @param identityChangeRequest
* {@link IdentityChangeRequest} with list of attributes
* @param serviceContract
* the service contract of the client requesting the change
* @throws ClientAuthorizationException
*/
public void validateCreateAuthorization( final IdentityChangeRequest identityChangeRequest, final ServiceContract serviceContract )
throws ClientAuthorizationException
{
if ( !serviceContract.getAuthorizedCreation( ) )
{
throw new ClientAuthorizationException( "The service contract of the sent client code doesn't allow creating identities",
Constants.PROPERTY_REST_ERROR_CLIENT_AUTHORIZATION_CREATE );
}
if ( StringUtils.isNotEmpty( identityChangeRequest.getIdentity( ).getConnectionId( ) ) && !serviceContract.getAuthorizedAccountUpdate( ) )
{
throw new ClientAuthorizationException( "You cannot specify a GUID when requesting for a creation",
Constants.PROPERTY_REST_ERROR_IDENTITY_CREATE_WITH_GUID );
}
if ( identityChangeRequest.getIdentity( ).getMonParisActive( ) != null && !serviceContract.getAuthorizedAccountUpdate( ) )
{
throw new ClientAuthorizationException( "You cannot set the 'mon_paris_active' flag when requesting for a creation",
Constants.PROPERTY_REST_ERROR_IDENTITY_CREATE_WITH_MON_PARIS_FLAG );
}
this.validateWritableAndCertifiableAttributes( identityChangeRequest.getIdentity( ).getAttributes( ), serviceContract );
}
/**
* Validates the {@link IdentityChangeRequest} against the {@link ServiceContract} associated to the {@link ClientApplication} requesting the change. Each
* violation is listed in the {@link ClientAuthorizationException}'s response with a status by attribute key. The following rules are verified: <br>
* <ul>
* <li>The service contract must exist and allow writing identities</li>
* <li>The attribute must be writable</li>
* <li>The certification processus, if given in the request, must be in the list of authorized processus</li>
* </ul>
*
* @param identityChangeRequest
* {@link IdentityChangeRequest} with list of attributes
* @param serviceContract
* service contract of the client requesting the change
* @throws ClientAuthorizationException
*/
public void validateUpdateAuthorization( final IdentityChangeRequest identityChangeRequest, final IdentityDto existingIdentityToUpdate,
final ServiceContract serviceContract ) throws ClientAuthorizationException
{
if ( !serviceContract.getAuthorizedUpdate( ) )
{
throw new ClientAuthorizationException( "The service contract of the sent client code doesn't allow updating identities",
Constants.PROPERTY_REST_ERROR_CLIENT_AUTHORIZATION_UPDATE );
}
if ( identityChangeRequest.getIdentity( ).getMonParisActive( ) != null && !serviceContract.getAuthorizedAccountUpdate( ) )
{
throw new ClientAuthorizationException( "The client application is not authorized to update the 'mon_paris_active' flag",
Constants.PROPERTY_REST_ERROR_FORBIDDEN_MON_PARIS_ACTIVE_UPDATE );
}
if ( StringUtils.isNotEmpty( identityChangeRequest.getIdentity( ).getConnectionId( ) )
&& !Objects.equals( identityChangeRequest.getIdentity( ).getConnectionId( ), existingIdentityToUpdate.getConnectionId( ) )
&& !serviceContract.getAuthorizedAccountUpdate( ) )
{
throw new ClientAuthorizationException( "The client application is not authorized to update the connection_id",
Constants.PROPERTY_REST_ERROR_CLIENT_AUTHORIZATION_UPDATE_GUID );
}
this.validateWritableAndCertifiableAttributes( identityChangeRequest.getIdentity( ).getAttributes( ), serviceContract );
}
/**
* Validates the {@link IdentityMergeRequest} against the {@link ServiceContract} associated to the {@link ClientApplication} requesting the change. Each
* violation is listed in the {@link IdentityMergeResponse} with a status by attribute key. The following rules are verified: <br>
* <ul>
* <li>The {@link ClientApplication} must be authorized to perform the merge</li>
* </ul>
*
* @param request
* {@link IdentityMergeRequest} with list of attributes
* @param serviceContract
* service contract of the client requesting the change
* @throws ClientAuthorizationException
*/
public void validateMergeAuthorization( final IdentityMergeRequest request, final ServiceContract serviceContract ) throws ClientAuthorizationException
{
// check if service contract is allowed to merge
if ( !serviceContract.getAuthorizedMerge( ) )
{
throw new ClientAuthorizationException( "The client application is not authorized to merge identities",
Constants.PROPERTY_REST_ERROR_MERGE_UNAUTHORIZED );
}
// check if service contract is allowed to update identity
if ( request.getIdentity( ) != null )
{
if ( !serviceContract.getAuthorizedUpdate( ) )
{
throw new ClientAuthorizationException( "The service contract of the sent client code doesn't allow updating identities",
Constants.PROPERTY_REST_ERROR_CLIENT_AUTHORIZATION_UPDATE );
}
// check if service contract is allowed to update attributes
// (the requested certification process are not mandatory in merge case)
this.validateWritableAndCertifiableAttributes( request.getIdentity( ).getAttributes( ), serviceContract, false );
}
}
/**
* checks if the service contract grants the right to delete identities
*
* @param serviceContract
* the service contract
* @throws ClientAuthorizationException
* if the service contract does not grant the delete authorization
*/
public void validateDeleteAuthorization( final ServiceContract serviceContract ) throws ClientAuthorizationException
{
if ( !serviceContract.getAuthorizedDeletion( ) )
{
throw new ClientAuthorizationException( "The client application is not authorized to request the deletion of an identity.",
Constants.PROPERTY_REST_ERROR_DELETE_UNAUTHORIZED );
}
}
/**
* Validates the {@link IdentityChangeRequest} against the {@link ServiceContract} associated to the {@link ClientApplication} requesting the change. Each
* violation is listed in the {@link IdentityChangeResponse} with a status by attribute key. The following rules are verified: <br>
* <ul>
* <li>The {@link ClientApplication} must be authorized to perform the import</li>
* <li>The attribute must be writable</li>
* <li>The certification processus, if given in the request, must be in the list of authorized processus</li>
* </ul>
*
* @param request
* {@link IdentityChangeRequest} with list of attributes
* @param serviceContract
* the service contract of the client requesting the change
* @throws ClientAuthorizationException
* in case of error
*/
public void validateImportAuthorization( final IdentityChangeRequest request, final ServiceContract serviceContract ) throws ClientAuthorizationException
{
if ( !serviceContract.getAuthorizedImport( ) )
{
throw new ClientAuthorizationException( "The client application is not authorized to import identities ",
Constants.PROPERTY_REST_ERROR_IMPORT_UNAUTHORIZED );
}
this.validateWritableAndCertifiableAttributes( request.getIdentity( ).getAttributes( ), serviceContract );
}
/**
* Checks if the service contract grants the right to uncertify an identity
*
* @param serviceContract
* the service contract
* @throws ClientAuthorizationException
*/
public void validateUncertifyAuthorization( final ServiceContract serviceContract ) throws ClientAuthorizationException
{
if ( !serviceContract.getAuthorizedDecertification( ) )
{
throw new ClientAuthorizationException( "Unauthorized operation", Constants.PROPERTY_REST_ERROR_UNAUTHORIZED_OPERATION );
}
}
/**
* Checks if the service contract grants the right to export identities
*
* @param serviceContract
* the service contract
* @throws ClientAuthorizationException
*/
public void validateExportAuthorization( final ServiceContract serviceContract ) throws ClientAuthorizationException
{
if ( !serviceContract.getAuthorizedExport( ) )
{
throw new ClientAuthorizationException( "Unauthorized operation", Constants.PROPERTY_REST_ERROR_UNAUTHORIZED_OPERATION );
}
}
/**
* Check if the service contracts contains the certifications allowing the attributes update.
*
* @param attributes
* @param serviceContract
* @throws ClientAuthorizationException if the service contract does not allow the update
*/
private void validateWritableAndCertifiableAttributes( final List<AttributeDto> attributes, final ServiceContract serviceContract )
throws ClientAuthorizationException
{
validateWritableAndCertifiableAttributes( attributes, serviceContract, true );
}
/**
* Check if the service contracts contains the certifications allowing the attributes update.
*
* @param attributes
* @param serviceContract
* @throws ClientAuthorizationException if the service contract does not allow the update
*/
private void validateWritableAndCertifiableAttributes( final List<AttributeDto> attributes, final ServiceContract serviceContract, boolean checkCertififiers )
throws ClientAuthorizationException
{
final List<AttributeStatus> attrStatusList = new ArrayList<>( );
for ( final AttributeDto attributeDto : attributes )
{
// check if attribute is writable
boolean canWriteAttribute = serviceContract.getAttributeRights( ).stream( )
.anyMatch( attributeRight -> StringUtils.equals( attributeRight.getAttributeKey( ).getKeyName( ), attributeDto.getKey( ) )
&& attributeRight.isWritable( ) );
if ( !canWriteAttribute )
{
attrStatusList.add( this.buildAttributeStatus( attributeDto, AttributeChangeStatus.UNAUTHORIZED ) );
continue;
}
// check if the certification process of the attribute is allowed in the service contract
if ( checkCertififiers && attributeDto.getCertifier( ) != null )
{
canWriteAttribute = serviceContract.getAttributeCertifications( ).stream( ).anyMatch(
attributeCertification -> StringUtils.equals( attributeCertification.getAttributeKey( ).getKeyName( ), attributeDto.getKey( ) )
&& attributeCertification.getRefAttributeCertificationProcessus( ).stream( )
.anyMatch( processus -> StringUtils.equals( processus.getCode( ), attributeDto.getCertifier( ) ) ) );
if ( !canWriteAttribute )
{
attrStatusList.add( this.buildAttributeStatus( attributeDto, AttributeChangeStatus.INSUFFICIENT_RIGHTS ) );
}
}
}
if ( !attrStatusList.isEmpty( ) )
{
final ClientAuthorizationException exception = new ClientAuthorizationException( "The request violates service contract definition",
Constants.PROPERTY_REST_ERROR_SERVICE_CONTRACT_VIOLATION );
exception.getResponse( ).getStatus( ).setAttributeStatuses( attrStatusList );
throw exception;
}
}
/**
* Exports all service contracts
*
* @throws IdentityStoreException
* in case of error
*/
public List<ServiceContractDto> exportAllServiceContracts( ) throws IdentityStoreException
{
return this.exportAllServiceContracts( null );
}
/**
* Exports all service contracts associated with the provided client code
*
* @param clientCode
* the client code
* @throws IdentityStoreException
* in case of error
*/
public List<ServiceContractDto> exportAllServiceContracts( final String clientCode ) throws IdentityStoreException
{
final List<ServiceContractDto> result = new ArrayList<>( );
final List<ServiceContract> serviceContracts = clientCode == null ? ServiceContractHome.getAllServiceContractsList( )
: ClientApplicationHome.selectServiceContracts( ClientApplicationHome.findByCode( clientCode ).getId( ) );
if ( CollectionUtils.isEmpty( serviceContracts ) )
{
throw new ResourceNotFoundException( "No service contract found", Constants.PROPERTY_REST_ERROR_NO_SERVICE_CONTRACT_FOUND );
}
serviceContracts.forEach( serviceContract -> result.add( this.enrichAndConvertToDto( serviceContract ) ) );
return result;
}
public ServiceContractDto exportServiceContract( final int serviceContractId ) throws IdentityStoreException
{
final Optional<ServiceContract> result = ServiceContractHome.findByPrimaryKey( serviceContractId );
if ( !result.isPresent( ) )
{
throw new ResourceNotFoundException( "Service contract not found", Constants.PROPERTY_REST_ERROR_SERVICE_CONTRACT_NOT_FOUND );
}
return this.enrichAndConvertToDto( result.get( ) );
}
private ServiceContractDto enrichAndConvertToDto( final ServiceContract serviceContract )
{
serviceContract.setAttributeRights( ServiceContractHome.selectApplicationRights( serviceContract ) );
serviceContract.setAttributeCertifications( ServiceContractHome.selectAttributeCertifications( serviceContract ) );
serviceContract.setAttributeRequirements( ServiceContractHome.selectAttributeRequirements( serviceContract ) );
// TODO amélioration générale à mener sur ce point
for ( final AttributeCertification certification : serviceContract.getAttributeCertifications( ) )
{
for ( final RefAttributeCertificationProcessus processus : certification.getRefAttributeCertificationProcessus( ) )
{
processus.setLevel(
AttributeCertificationDefinitionService.instance( ).get( processus.getCode( ), certification.getAttributeKey( ).getKeyName( ) ) );
}
}
return DtoConverter.convertContractToDto( serviceContract );
}
}