import * as _ from 'lodash' ; import { Injectable } from '@nestjs/common' ; import { ConfigService } from '@fc/config' ; import { EidasResponse } from '@fc/eidas' ; import { EidasLightProtocolConfig } from '../dto' ; import { EidasJsonToXmlException } from '../exceptions' ; import { IParsedToken , IPathsObject } from '../interfaces' ; import { LightProtocolCommonsService } from './light-protocol-commons.service' ; import { LightProtocolXmlService } from './light-protocol-xml.service' ; @ Injectable ( ) export class LightResponseService { constructor ( private readonly config , private readonly lightCommons , private readonly lightXml ) { } param { EidasResponse } data formatResponse ( eidasResponse ) { let pathsObject = this . lightXml . jsonToPathsObject ( eidasResponse ) ; pathsObject = this . lightXml . replaceInPaths ( pathsObject , /^/ , 'lightResponse.' ) ; pathsObject = this . dissociateDefinitionsAndValues ( pathsObject ) ; pathsObject = this . lightXml . upperCaseFirstCharForProps ( pathsObject , [ 'definition' , ] ) ; pathsObject = this . lightXml . prefixProps ( pathsObject , [ 'definition' ] , 'http://eidas.europa.eu/attributes/naturalperson/' ) ; pathsObject = this . lightXml . replaceInPaths ( pathsObject , 'attributes.' , 'attributes.attribute.' ) ; pathsObject = this . lightXml . addDeclarationFields ( pathsObject ) ; pathsObject = this . lightXml . prefixProps ( pathsObject , [ 'subjectNameIdFormat' ] , 'urn:oasis:names:tc:SAML:2.0:nameid-format:' ) ; pathsObject = this . lightXml . prefixProps ( pathsObject , [ 'statusCode' , 'subStatusCode' ] , 'urn:oasis:names:tc:SAML:2.0:status:' ) ; pathsObject = this . lightXml . prefixProps ( pathsObject , [ 'levelOfAssurance' ] , 'http://eidas.europa.eu/LoA/' ) ; pathsObject = this . lightXml . replaceInPaths ( pathsObject , /^lightResponse\.(.*)$/ , 'lightResponse.$1._text' ) ; const json = this . lightXml . pathsObjectToJson ( pathsObject ) ; try { return this . lightXml . jsonToXml ( json ) ; } catch ( error ) { throw new EidasJsonToXmlException ( error ) ; } } generateToken ( id , issuer , date ? ) { const { lightResponseProxyServiceSecret } = this . config . get < EidasLightProtocolConfig > ( 'EidasLightProtocol' ) ; return this . lightCommons . generateToken ( id , issuer , lightResponseProxyServiceSecret , date ) ; } param { string } xml parseResponse ( xml ) { const json = this . lightXml . xmlToJson ( xml ) ; let pathsObject = this . lightXml . jsonToPathsObject ( json ) ; pathsObject = this . lightXml . removeDeclarationFields ( pathsObject ) ; pathsObject = this . lightXml . replaceInPaths ( pathsObject , 'value._text' , 'value.0._text' ) ; pathsObject = this . lightXml . replaceInPaths ( pathsObject , 'lightResponse.' , '' ) ; pathsObject = this . lightXml . replaceInPaths ( pathsObject , '._text' , '' ) ; pathsObject = this . lightXml . replaceInPaths ( pathsObject , 'attributes.attribute' , 'attributes' ) ; pathsObject = this . lightXml . stripUrlAndUrnForProps ( pathsObject , [ 'levelOfAssurance' , 'subjectNameIdFormat' , 'definition' , 'statusCode' , 'subStatusCode' , ] ) ; pathsObject = this . lightXml . lowerCaseFirstCharForProps ( pathsObject , [ 'definition' , ] ) ; pathsObject = this . mapValuesToDefinition ( pathsObject ) ; pathsObject = this . lightXml . replaceInPaths ( pathsObject , /^attributes\.[0-9]+/ , 'attributes' ) ; pathsObject = this . failureAttributeToBoolean ( pathsObject ) ; return this . lightXml . pathsObjectToJson ( pathsObject ) as unknown as EidasResponse ; } parseToken ( token ) { const { lightResponseConnectorSecret } = this . config . get < EidasLightProtocolConfig > ( 'EidasLightProtocol' ) ; return this . lightCommons . parseToken ( token , lightResponseConnectorSecret ) ; } private getMapValuesToDefinitionForPath ( state ) { return ( keyPath , value ) : Array < [ string , unknown ] > => { if ( keyPath . match ( /definition/ ) && typeof value === 'string' ) { state . currentKey = keyPath . replace ( 'definition' , value ) ; } else if ( keyPath . match ( /value/ ) ) { const [ index ] = keyPath . match ( /value\.([0-9]+)/ ) [ 1 ] ; return [ [ `${ state . currentKey }.${ index }` , value ] ] ; } else { return [ [ keyPath , value ] ] ; } } ; } private mapValuesToDefinition ( pathsObject ) { const state = { currentKey : undefined , } ; return this . lightXml . forEachPath ( pathsObject , this . getMapValuesToDefinitionForPath ( state ) ) ; } private getDissociateDefinitionsAndValuesForPath ( state ) { return ( keyPath , value ) : Array < [ string , unknown ] > => { if ( ! keyPath . match ( /attributes\.[a-zA-Z]+/ ) ) { return [ [ keyPath , value ] ] ; } const [ , definition ] = keyPath . match ( /attributes\.([a-zA-Z]+)/ ) ; if ( state . lastDefinition && definition !== state . lastDefinition ) { state . index ++ ; } state . lastDefinition = definition ; const toReplace = new RegExp ( `${ definition }\\.[0-9]+` ) ; const definitionKey = keyPath . replace ( toReplace , `${ state . index }\.definition` ) ; const valueKey = keyPath . replace ( definition , `${ state . index }\.value` ) ; return [ [ definitionKey , definition ] , [ valueKey , value ] , ] ; } ; } private dissociateDefinitionsAndValues ( pathsObject ) { const state = { lastDefinition : undefined , index : 0 , } ; return this . lightXml . forEachPath ( pathsObject , this . getDissociateDefinitionsAndValuesForPath ( state ) ) ; } private failureAttributeToBooleanForPath ( keyPath , value ) { if ( keyPath . match ( 'status.failure' ) ) { return [ [ keyPath , value === 'true' ] ] ; } else { return [ [ keyPath , value ] ] ; } } private failureAttributeToBoolean ( pathsObject ) { return this . lightXml . forEachPath ( pathsObject , this . failureAttributeToBooleanForPath ) ; } }