Modify input attestation path (#133)
* complete siop validation * refactor, but all tests fail * tests are passing now? * now tests fail * almost there * new tests * cleanup * Add flag indicating whether or not InputModelAttestations populated * getting there * fixes * generate a new package * make hasAttestations a function * revert * bug fixes * create a new package * fix Co-authored-by: Guillermo Proano <gproano@microsoft.com>
This commit is contained in:
Родитель
284738407d
Коммит
8273d2219a
|
@ -1,3 +1,10 @@
|
|||
# version 0.12.1-preview.25
|
||||
## Siop Validation refinements
|
||||
**Type of change:** Feature
|
||||
**Customer impact:** medium
|
||||
- Add a flag to RulesContent signaling whether or not it contains input attestations
|
||||
- Allow callers to specify tokensToValidate
|
||||
|
||||
# version 0.12.1-preview.17
|
||||
## Complete Siop Validation
|
||||
**Type of change:** bug fix
|
||||
|
|
|
@ -3,15 +3,15 @@
|
|||
* Licensed under the MIT License. See License in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TokenType, IExpectedSiop, ITokenValidator, ClaimToken, AuthenticationErrorCode, AuthenticationErrorDescription } from '../index';
|
||||
import ErrorHelpers from '../error_handling/ErrorHelpers';
|
||||
import { AuthenticationErrorCode, AuthenticationErrorDescription, ClaimToken, IExpectedSiop, ITokenValidator, TokenType } from '../index';
|
||||
import { IValidationResponse } from '../input_validation/IValidationResponse';
|
||||
import ValidationOptions from '../options/ValidationOptions';
|
||||
import IValidatorOptions from '../options/IValidatorOptions';
|
||||
import { SiopValidation } from '../input_validation/SiopValidation';
|
||||
import ValidationQueue from '../input_validation/ValidationQueue';
|
||||
import ValidationQueueItem from '../input_validation/ValidationQueueItem';
|
||||
import { SiopValidation } from '../input_validation/SiopValidation';
|
||||
import IValidatorOptions from '../options/IValidatorOptions';
|
||||
import ValidationOptions from '../options/ValidationOptions';
|
||||
import VerifiableCredentialConstants from '../verifiable_credential/VerifiableCredentialConstants';
|
||||
import ErrorHelpers from '../error_handling/ErrorHelpers';
|
||||
const errorCode = (error: number) => ErrorHelpers.errorCode('VCSDKSTVa', error);
|
||||
|
||||
/**
|
||||
|
@ -35,8 +35,10 @@ export default class SiopTokenValidator implements ITokenValidator {
|
|||
const options = new ValidationOptions(this.validatorOption, this.expected.type);
|
||||
const validator = new SiopValidation(options, this.expected);
|
||||
let validationResult = await validator.validate(queueItem.tokenToValidate);
|
||||
|
||||
if (validationResult.result) {
|
||||
validationResult = this.getTokens(validationResult, queue);
|
||||
// we might already know what token we have
|
||||
validationResult = this.getTokens(validationResult, queue, queueItem.tokenToValidate.type);
|
||||
}
|
||||
|
||||
validationResult = this.validateReplayProtection(validationResult);
|
||||
|
@ -78,25 +80,31 @@ export default class SiopTokenValidator implements ITokenValidator {
|
|||
* Get tokens from current item and add them to the queue.
|
||||
* @param validationResponse The response for the requestor
|
||||
* @param queue with tokens to validate
|
||||
* @param knownTokenType a token type declared by the caller
|
||||
*/
|
||||
public getTokens(validationResponse: IValidationResponse, queue: ValidationQueue): IValidationResponse {
|
||||
public getTokens(validationResponse: IValidationResponse, queue: ValidationQueue, knownTokenType?: TokenType): IValidationResponse {
|
||||
|
||||
// Check type of SIOP
|
||||
let type: TokenType;
|
||||
if (validationResponse.payloadObject[VerifiableCredentialConstants.ATTESTATIONS]) {
|
||||
type = TokenType.siopPresentationAttestation;
|
||||
} else if (validationResponse.payloadObject[VerifiableCredentialConstants.PRESENTATION_SUBMISSION]) {
|
||||
if (validationResponse.payloadObject[VerifiableCredentialConstants.PRESENTATION_SUBMISSION]) {
|
||||
type = TokenType.siopPresentationExchange;
|
||||
} else if (knownTokenType === TokenType.siopIssuance || knownTokenType === TokenType.siopPresentationAttestation || validationResponse.payloadObject[VerifiableCredentialConstants.ATTESTATIONS]) {
|
||||
type = TokenType.siopPresentationAttestation;
|
||||
} else {
|
||||
type = TokenType.siop;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case TokenType.siopPresentationAttestation:
|
||||
|
||||
const attestations = validationResponse.payloadObject[VerifiableCredentialConstants.ATTESTATIONS];
|
||||
if (attestations) {
|
||||
// Decode tokens
|
||||
try {
|
||||
validationResponse.tokensToValidate = ClaimToken.getClaimTokensFromAttestations(attestations);
|
||||
// caller may have alredy filled tokensToValidator, if so return that
|
||||
if (!validationResponse.tokensArePopulated) {
|
||||
validationResponse.tokensToValidate = ClaimToken.getClaimTokensFromAttestations(attestations);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return {
|
||||
|
|
|
@ -339,7 +339,7 @@ export default class Validator {
|
|||
}
|
||||
|
||||
private isSiop(type: TokenType | undefined) {
|
||||
return type === TokenType.siopIssuance || type === TokenType.siopPresentationAttestation
|
||||
return type === TokenType.siopIssuance || type === TokenType.siopPresentationAttestation;
|
||||
}
|
||||
|
||||
private setValidationResult(queue: ValidationQueue): IValidationResult {
|
||||
|
|
|
@ -72,6 +72,11 @@ export interface IValidationResponse extends IResponse {
|
|||
*/
|
||||
tokensToValidate?: { [key: string]: ClaimToken };
|
||||
|
||||
/**
|
||||
* flag indicating whether or not tokens have been populated
|
||||
*/
|
||||
tokensArePopulated?: boolean;
|
||||
|
||||
/**
|
||||
* All claims found in input tokens
|
||||
*/
|
||||
|
|
|
@ -3,22 +3,24 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { RulesValidationError } from '../error_handling/RulesValidationError';
|
||||
import { IdTokenAttestationModel } from './IdTokenAttestationModel';
|
||||
import { SelfIssuedAttestationModel } from './SelfIssuedAttestationModel';
|
||||
import { VerifiablePresentationAttestationModel } from './VerifiablePresentationAttestationModel';
|
||||
import { IdTokenAttestationModel } from './IdTokenAttestationModel';
|
||||
import { RulesValidationError } from '../error_handling/RulesValidationError';
|
||||
|
||||
/**
|
||||
* Model for attestations for input contract
|
||||
*/
|
||||
export class IssuanceAttestationsModel {
|
||||
private _hasAttestations: boolean = false;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param selfIssued SelfIssuedAttestationModel instance
|
||||
* @param presentations an array of VerifiablePresentationModel instances
|
||||
* @param idTokens an array of IdTokenAttestationModel instances
|
||||
*/
|
||||
constructor (
|
||||
constructor(
|
||||
public selfIssued?: SelfIssuedAttestationModel,
|
||||
public presentations?: VerifiablePresentationAttestationModel[],
|
||||
public idTokens?: IdTokenAttestationModel[]) {
|
||||
|
@ -45,10 +47,17 @@ export class IssuanceAttestationsModel {
|
|||
return allIndexClaims;
|
||||
}
|
||||
|
||||
/**
|
||||
* indicates whether or not there are attestations defined
|
||||
*/
|
||||
public get hasAttestations(): boolean {
|
||||
return this._hasAttestations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives an IssuanceAttestationModel for input from a Rules attestation model
|
||||
*/
|
||||
forInput (): IssuanceAttestationsModel {
|
||||
forInput(): IssuanceAttestationsModel {
|
||||
return new IssuanceAttestationsModel(
|
||||
this.selfIssued === undefined ? undefined : <SelfIssuedAttestationModel>this.selfIssued.forInput(),
|
||||
this.presentations === undefined ? undefined : this.presentations.map(presentation => <VerifiablePresentationAttestationModel>presentation.forInput()),
|
||||
|
@ -59,18 +68,19 @@ export class IssuanceAttestationsModel {
|
|||
* Populate an instance of IssuanceAttestationsModel from any instance
|
||||
* @param input object instance to populate from
|
||||
*/
|
||||
populateFrom (input: any): void {
|
||||
const outputAttestations = new Set<string>();
|
||||
let totalOutputAttestations = 0;
|
||||
populateFrom(input: any): void {
|
||||
const outputClaims = new Set<string>();
|
||||
let totalOutputClaims = 0;
|
||||
|
||||
if (input.selfIssued !== undefined) {
|
||||
this._hasAttestations = true;
|
||||
this.selfIssued = new SelfIssuedAttestationModel();
|
||||
this.selfIssued.populateFrom(input.selfIssued);
|
||||
|
||||
if (this.selfIssued.mapping) {
|
||||
const outputAttestationKeys = Object.keys(this.selfIssued.mapping);
|
||||
totalOutputAttestations += outputAttestationKeys.length;
|
||||
outputAttestationKeys.forEach(outputAttestation => outputAttestations.add(outputAttestation));
|
||||
totalOutputClaims += outputAttestationKeys.length;
|
||||
outputAttestationKeys.forEach(outputAttestation => outputClaims.add(outputAttestation));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,12 +92,14 @@ export class IssuanceAttestationsModel {
|
|||
|
||||
if (p.mapping) {
|
||||
const outputAttestationKeys = Object.keys(p.mapping);
|
||||
totalOutputAttestations += outputAttestationKeys.length;
|
||||
outputAttestationKeys.forEach(outputAttestation => outputAttestations.add(outputAttestation));
|
||||
totalOutputClaims += outputAttestationKeys.length;
|
||||
outputAttestationKeys.forEach(outputAttestation => outputClaims.add(outputAttestation));
|
||||
}
|
||||
|
||||
return p;
|
||||
});
|
||||
|
||||
this._hasAttestations = this._hasAttestations || this.presentations.length > 0;
|
||||
}
|
||||
|
||||
if (input.idTokens !== undefined) {
|
||||
|
@ -98,17 +110,19 @@ export class IssuanceAttestationsModel {
|
|||
|
||||
if (t.mapping) {
|
||||
const outputAttestationKeys = Object.keys(t.mapping);
|
||||
totalOutputAttestations += outputAttestationKeys.length;
|
||||
outputAttestationKeys.forEach(outputAttestation => outputAttestations.add(outputAttestation));
|
||||
totalOutputClaims += outputAttestationKeys.length;
|
||||
outputAttestationKeys.forEach(outputAttestation => outputClaims.add(outputAttestation));
|
||||
}
|
||||
|
||||
return t;
|
||||
});
|
||||
|
||||
this._hasAttestations = this._hasAttestations || this.idTokens.length > 0;
|
||||
}
|
||||
|
||||
// Ensure uniqueness of attestation mapping keys. Non-uniqueness leads to data loss.
|
||||
if (totalOutputAttestations !== outputAttestations.size) {
|
||||
if (totalOutputClaims !== outputClaims.size) {
|
||||
throw new RulesValidationError('Attestation mapping names must be unique.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "verifiablecredentials-verification-sdk-typescript",
|
||||
"version": "0.12.1-preview.17",
|
||||
"version": "0.12.1-preview.25",
|
||||
"description": "Typescript SDK for verifiable credentials",
|
||||
"main": "dist/lib/index.js",
|
||||
"types": "dist/lib/index.d.ts",
|
||||
|
|
|
@ -81,25 +81,6 @@ describe('Rule processor', () => {
|
|||
TokenGenerator.fetchMock.reset();
|
||||
}
|
||||
});
|
||||
it('should process RequestAttestationsOneVcSaIdtokenResponseOk with missing attestations in SIOP', async () => {
|
||||
try {
|
||||
const model: any = new RequestAttestationsOneVcSaIdtokenResponseOk();
|
||||
model.preSiopResponseOperations = [
|
||||
{
|
||||
path: '$.attestations',
|
||||
operation: () => undefined
|
||||
}
|
||||
];
|
||||
let [validator, _, responderResponse, responder] = await doRequest(model);
|
||||
|
||||
let response = await validator.validate(responderResponse);
|
||||
expect(response.result).toBeFalsy(response.detailedError);
|
||||
expect(response.code).toEqual('VCSDKSTVa05');
|
||||
expect(response.status).toEqual(401);
|
||||
} finally {
|
||||
TokenGenerator.fetchMock.reset();
|
||||
}
|
||||
});
|
||||
|
||||
it('should process RequestAttestationsOneVcSaIdtokenResponseOk: The jti claim is missing', async () => {
|
||||
try {
|
||||
|
|
|
@ -153,6 +153,7 @@ describe('RulesModel', () => {
|
|||
expect(Object.keys(roundtripSelfIssuedMapping).length).toEqual(Object.keys(<any>RULES.attestations?.selfIssued?.mapping).length);
|
||||
// when id is not specified, it's the same as the name
|
||||
expect((<BaseAttestationModel>roundtripSelfIssued).id).toEqual((<BaseAttestationModel>roundtripSelfIssued).name);
|
||||
expect(roundtrip.attestations?.hasAttestations).toEqual(true);
|
||||
|
||||
// analyze the contents of input claim
|
||||
const roundtripAlias = <InputClaimModel>roundtrip.attestations?.selfIssued?.mapping?.alias;
|
||||
|
|
Загрузка…
Ссылка в новой задаче